設計模式-工廠模式

參考資料

圖解設計模式

大話設計模式

設計模式之禪

github我見過最好的設計模式

設計原則回顧

設計原則 解釋
開閉原則 對擴展開放,對修改關閉
依賴倒置原則 通過抽象讓哥哥模組互不影響,松耦合,面向介面編程
單一職責原則 一個介面,類,方法只做一件事
介面隔離原則 保證純潔性,不應該依賴於自己不需要的介面,有時候沒辦法可以通過適配器來解決
迪米特法則 最少知道原則,一個類對其所依賴的類知道的越少越好
里氏替換原則 子類可以擴展父類的功能但是不能改變父類原有的功能
合成復用原則 盡量使用聚合和組合,少用繼承
  • 巧用Json工具類來做json轉化
  • JDBC這一塊是固定的,可以使用模板方法

工廠模式

簡單工廠

案例

日曆類

比如說日曆類中獲取對應日曆的方法,通過傳入參數來進行對應的對象的生成

img

日誌

又或者說 logfactory中的log

img

image-20201230113326481

public class CourseFactory {
  //    public ICourse create(String name){
  //        if("java".equals(name)){
  //            return new JavaCourse();
  //        }else if("python".equals(name)){
  //            return new PythonCourse();
  //        }else {
  //            return null;
  //        }
  //    }

  //    public ICourse create(String className){
  //        try {
  //            if (!(null == className || "".equals(className))) {
  //                return (ICourse) Class.forName(className).newInstance();
  //            }
  //
  //        }catch (Exception e){
  //            e.printStackTrace();
  //        }
  //        return null;
  //    }
	//泛型約束
  public ICourse create(Class<? extends ICourse> clazz){
    try {
      if (null != clazz) {
        return clazz.newInstance();
      }
    }catch (Exception e){
      e.printStackTrace();
    }
    return null;
  }

}

Mybatis SqlsesssionFactory創建Executor

org.apache.ibatis.session.Configuration#newExecutor(Transaction, ExecutorType)

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }
  • 通過不同的ExecurtorType來創建Executor

優點

只需要傳入一個正確的參數,就可以獲取我們所需要的對象,無需知道其創建的細節

缺點

工廠類的職責相對過重,增加新的產品時需要修改工廠類的判斷邏輯,不符合開閉原則

問題

為什麼一定要用工廠模式,為什麼工廠模式比直接new Object()好呢?

  • new Object可能還得去配置和組裝對象的很多額外資訊
    • 但是當前可能並不需要知道這麼多額外資訊,且不希望自己來組裝,所以通過工廠來獲取
    • 例如用戶只想要一輛車,可能並不想知道車門的材料是什麼廠商的,車輪是從哪裡進口的
  • 其次如果說工廠裡面生產的東西發生一些小的變更,只需要直接在工廠的地方修改就行了,如果用戶自己來new的,那麼所有用戶自己創建的地方都需要修改,其實主要還是高內聚低耦合

工廠方法

定義一個創建對象的介面,但讓實現這個介面的類來決定實例化哪個類,工廠方法讓類的實例化推遲到子類中進行。
  • 缺點
    • 類的個數容易過多,增加了程式碼結構的複雜度
    • 增加了系統的抽象性和理解難度

例子

如果是簡單當前可能創建不同的工廠產品可能需要組裝各種屬性,可能從java上來說就是需要set各種各樣的東西,就會導致程式碼很長,而且一旦後期需要維護和變更其中一個工廠,可能會改和影響到其他工廠,所以這時候為了開閉原則,我們可以把工廠抽象出來,對應的工廠實現自己的工廠創建。

關於課程的例子

image-20201230142015018

工廠抽象

public interface ICourseFactory {
  ICourse create();
}

課程抽象

public interface ICourse {
  /**
       * 錄製影片
       * @return
       */
  void record();
}

關於女媧造人的例子

image-20201230142352816

抽象方法中已經不再需要傳遞相關參數了,因為每一個具體的工廠都已經非常明確自己的職責:創建自己負責的產品類對象。

工廠方法+單例模式

單例模式程式碼

public class Singleton {
  //不允許通過new產生一個對象
  private Singleton(){ }
  public void doSomething(){
    //業務處理
  }
}

工廠方法+反射破壞構造私有化來創建

我們可以通過工廠來構建單例對象

public class SingletonFactory {

  private static Singleton singleton;

  static {
    try {
      Class cl = Class.forName(Singleton.class.getName());
      //獲得無參構造、
      java.lang.reflect.Constructor constructor =cl.getDeclaredConstructor();
      //設置無參構造是可訪問的
      constructor.setAccessible(true);
      //產生一個實例對象
      singleton = (Singleton)constructor.newInstance();
    } catch (Exception e) {
      //異常處理
    }

  }
  public static Singleton getSingleton(){ return singleton; }

}

通過獲得類構造器,然後設置訪問許可權,生成一個對象,然後提供外部訪問,保證記憶體中的對象唯一。

當然,其他類也可以通過反射的方式建立一個單例對象,確實如此,

但是一個項目或團隊是有章程和規範的,何況已經提供了一個獲得單例對象的方法,為什麼還要重新創建一 個新對象呢?

除非是有人作惡。

Spring中使用工廠方法+反射破壞構造私有化

org.springframework.core.io.support.SpringFactoriesLoader#instantiateFactory

private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {
  try {
    Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader);
    if (!factoryClass.isAssignableFrom(instanceClass)) {
      throw new IllegalArgumentException(
        "Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");
    }
    Constructor<?> constructor = instanceClass.getDeclaredConstructor();
    ReflectionUtils.makeAccessible(constructor);
    return (T) constructor.newInstance();
  }
  catch (Throwable ex) {
    throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), ex);
  }
}

工廠方法+單例延遲初始化

image-20201230143435385

public class ProductFactory {

  private static final Map<String,Product> prMap = new HashMap(); 
  public static synchronized Product createProduct(String type) {

    Product product =null;

    //如果Map中已經有這個對象 
    if(prMap.containsKey(type)){ 
      product = prMap.get(type);
    }else{
      if(type.equals("Product1")){
        product = new ConcreteProduct1(); 
      }else{ 
        product = new ConcreteProduct2();
      } 
      //同時把對象放到快取容器中 
      prMap.put(type,product);

    } return product;

  }

}

Dubbo中SPI工廠方法+單例

private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64);

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
  if (type == null) {
    throw new IllegalArgumentException("Extension type == null");
  }
  if (!type.isInterface()) {
    throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
  }
  if (!withExtensionAnnotation(type)) {
    throw new IllegalArgumentException("Extension type (" + type +
                                       ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
  }

  ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
  if (loader == null) {
    EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
    loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
  }
  return loader;
}

程式碼還比較簡單,通過定義一個Map容器,容納所有產生的對象, 如果在Map容器中已經有的對象,則直接取出返回;如果沒有,則根據 需要的類型產生一個對象並放入到Map容器中,以方便下次調用。

延遲載入框架是可以擴展的,例如限制某一個產品類的最大實例化 數量,可以通過判斷Map中已有的對象數量來實現,這樣的處理是非常 有意義的,例如JDBC連接資料庫,都會要求設置一個MaxConnections 最大連接數量,該數量就是記憶體中最大實例化的數量。

延遲載入還可以用在對象初始化比較複雜的情況下,例如硬體訪 問,涉及多方面的交互,則可以通過延遲載入降低對象的產生和銷毀帶 來的複雜性。

創建對象需要大量重複的程式碼

客戶端不依賴於產品類實例如何被創建,實現等細節。一個類通過其子類來指定創建哪個對象

最佳實踐

工廠方法模式在項目中使用得非常頻繁,以至於很多程式碼中都包含工廠方法模式。該模式幾乎盡人皆知,但不是每個人都能用得好。熟能生巧,熟練掌握該模式,多思考工廠方法如何應用,而且工廠方法模式還可以與其他模式混合使用(例如模板方法模式、單例模式、原型模式等),變化出無窮的優秀設計,這也正是軟體設計和開發的樂趣所在。

缺點

  1. 類的個數容易過多,增加複雜度
  2. 增加了系統的抽象性和理解難度

問題

工廠方法它解決了簡單工廠的哪個問題?

  • 解決了不同工廠生成不同產品需要裝配過多的程式碼在同一個類裡面,違背了開閉原則,通過工廠方法來解決快速擴展的問題

抽象工廠

客戶端無需了解其所調用工廠的具體類資訊。

圖解抽象工廠

提供一個創建一系列相關或相互依賴對象的介面,無須指定他們具體的類
客戶端不依賴於產品類實例如何被創建、實現等細節
強調一系列相關的產品對象(屬於同一產品族)一起使用創建對象需要大量重複的程式碼

image-20201230165226624

  • 產品族
    • 代表同一個品牌,不同產品
  • 產品等級結構
    • 同一產品不同品牌

例子

image-20201230192218653

public class AbstractFactoryTest {

  public static void main(String[] args) {
    JavaCourseFactory factory = new JavaCourseFactory();
    factory.createNote().edit();
    factory.createVideo().record();
  }
}

通用設計

image-20201230193403492

我們可以通過指定不同的抽象工廠來創建對應的產品

適用場景

  • 如果程式碼需要與多個不同系列的相關產品交互, 但是由於無法提前獲取相關資訊, 或者出於對未來擴展性的考慮, 你不希望程式碼基於產品的具體類進行構建, 在這種情況下, 你可以使用抽象工廠。
  • 抽象工廠為你提供了一個介面, 可用於創建每個系列產品的對象。 只要程式碼通過該介面創建對象, 那麼你就不會生成與應用程式已生成的產品類型不一致的產品。
  • 如果你有一個基於一組抽象方法的類, 且其主要功能因此變得不明確, 那麼在這種情況下可以考慮使用抽象工廠模式。
  • 在設計良好的程式中, 每個類僅負責一件事。 如果一個類與多種類型產品交互, 就可以考慮將工廠方法抽取到獨立的工廠類或具備完整功能的抽象工廠類中。

優缺點

優點 缺點
你可以確保同一工廠生成的產品相互匹配。 由於採用該模式需要嚮應用中引入眾多介面和類, 程式碼可能會比之前更加複雜。
你可以避免客戶端和具體產品程式碼的耦合。
單一職責原則。 你可以將產品生成程式碼抽取到同一位置, 使得程式碼易於維護。
開閉原則。 嚮應用程式中引入新產品變體時, 你無需修改客戶端程式碼。
  • 不符合開閉原則
    • 如果在對應的工廠上面新增一個功能,所有實現的地方都需要去修改慎用
  • 不依賴於產品實例如何被創建,實現的細節
  • 強調一系列相關的產品(統一產品族)一起使用穿件對象需要大量重複的的重複程式碼的場景
  • 擴展性很強

問題

舉例說出抽象工廠的應用場景,我如果要增加一個答疑的產品應該怎麼修改程式碼

  • 比如說女媧造人,需要有男人和女人,也需要有白種人,黑種人,黃種人,那麼我們可以定義一個頂層介面Humanfactory然後FemaleFactoryMaleFactory都實現對應的HumanFactory的介面,然後對應的工廠創建對應的產品
  • image-20201230193534527
  • 增加答疑的產品的話需要新增一個答疑的介面,所有工廠都需要去修改補充對應的實現,所以改動會非常大
    我的筆記倉庫地址gitee 快來給我點個Star吧