設(shè)計(jì)模式-工廠模式

參考資料

圖解設(shè)計(jì)模式

大話設(shè)計(jì)模式

設(shè)計(jì)模式之禪

github我見(jiàn)過(guò)最好的設(shè)計(jì)模式

設(shè)計(jì)原則回顧

設(shè)計(jì)原則 解釋
開(kāi)閉原則 對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉
依賴倒置原則 通過(guò)抽象讓哥哥模塊互不影響,松耦合,面向接口編程
單一職責(zé)原則 一個(gè)接口,類,方法只做一件事
接口隔離原則 保證純潔性,不應(yīng)該依賴于自己不需要的接口,有時(shí)候沒(méi)辦法可以通過(guò)適配器來(lái)解決
迪米特法則 最少知道原則,一個(gè)類對(duì)其所依賴的類知道的越少越好
里氏替換原則 子類可以擴(kuò)展父類的功能但是不能改變父類原有的功能
合成復(fù)用原則 盡量使用聚合和組合,少用繼承
  • 巧用Json工具類來(lái)做json轉(zhuǎn)化
  • JDBC這一塊是固定的,可以使用模板方法

工廠模式

簡(jiǎn)單工廠

案例

日歷類

比如說(shuō)日歷類中獲取對(duì)應(yīng)日歷的方法,通過(guò)傳入?yún)?shù)來(lái)進(jìn)行對(duì)應(yīng)的對(duì)象的生成

img

日志

又或者說(shuō) 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創(chuàng)建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;
  }
  • 通過(guò)不同的ExecurtorType來(lái)創(chuàng)建Executor

優(yōu)點(diǎn)

只需要傳入一個(gè)正確的參數(shù),就可以獲取我們所需要的對(duì)象,無(wú)需知道其創(chuàng)建的細(xì)節(jié)

缺點(diǎn)

工廠類的職責(zé)相對(duì)過(guò)重,增加新的產(chǎn)品時(shí)需要修改工廠類的判斷邏輯,不符合開(kāi)閉原則

問(wèn)題

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

  • new Object可能還得去配置和組裝對(duì)象的很多額外信息
    • 但是當(dāng)前可能并不需要知道這么多額外信息,且不希望自己來(lái)組裝,所以通過(guò)工廠來(lái)獲取
    • 例如用戶只想要一輛車,可能并不想知道車門(mén)的材料是什么廠商的,車輪是從哪里進(jìn)口的
  • 其次如果說(shuō)工廠里面生產(chǎn)的東西發(fā)生一些小的變更,只需要直接在工廠的地方修改就行了,如果用戶自己來(lái)new的,那么所有用戶自己創(chuàng)建的地方都需要修改,其實(shí)主要還是高內(nèi)聚低耦合

工廠方法

定義一個(gè)創(chuàng)建對(duì)象的接口,但讓實(shí)現(xiàn)這個(gè)接口的類來(lái)決定實(shí)例化哪個(gè)類,工廠方法讓類的實(shí)例化推遲到子類中進(jìn)行。
  • 缺點(diǎn)
    • 類的個(gè)數(shù)容易過(guò)多,增加了代碼結(jié)構(gòu)的復(fù)雜度
    • 增加了系統(tǒng)的抽象性和理解難度

例子

如果是簡(jiǎn)單當(dāng)前可能創(chuàng)建不同的工廠產(chǎn)品可能需要組裝各種屬性,可能從java上來(lái)說(shuō)就是需要set各種各樣的東西,就會(huì)導(dǎo)致代碼很長(zhǎng),而且一旦后期需要維護(hù)和變更其中一個(gè)工廠,可能會(huì)改和影響到其他工廠,所以這時(shí)候?yàn)榱碎_(kāi)閉原則,我們可以把工廠抽象出來(lái),對(duì)應(yīng)的工廠實(shí)現(xiàn)自己的工廠創(chuàng)建。

關(guān)于課程的例子

image-20201230142015018

工廠抽象

public interface ICourseFactory {
  ICourse create();
}

課程抽象

public interface ICourse {
  /**
       * 錄制視頻
       * @return
       */
  void record();
}

關(guān)于女?huà)z造人的例子

image-20201230142352816

抽象方法中已經(jīng)不再需要傳遞相關(guān)參數(shù)了,因?yàn)槊恳粋€(gè)具體的工廠都已經(jīng)非常明確自己的職責(zé):創(chuàng)建自己負(fù)責(zé)的產(chǎn)品類對(duì)象。

工廠方法+單例模式

單例模式代碼

public class Singleton {
  //不允許通過(guò)new產(chǎn)生一個(gè)對(duì)象
  private Singleton(){ }
  public void doSomething(){
    //業(yè)務(wù)處理
  }
}

工廠方法+反射破壞構(gòu)造私有化來(lái)創(chuàng)建

我們可以通過(guò)工廠來(lái)構(gòu)建單例對(duì)象

public class SingletonFactory {

  private static Singleton singleton;

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

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

}

通過(guò)獲得類構(gòu)造器,然后設(shè)置訪問(wèn)權(quán)限,生成一個(gè)對(duì)象,然后提供外部訪問(wèn),保證內(nèi)存中的對(duì)象唯一。

當(dāng)然,其他類也可以通過(guò)反射的方式建立一個(gè)單例對(duì)象,確實(shí)如此,

但是一個(gè)項(xiàng)目或團(tuán)隊(duì)是有章程和規(guī)范的,何況已經(jīng)提供了一個(gè)獲得單例對(duì)象的方法,為什么還要重新創(chuàng)建一 個(gè)新對(duì)象呢?

除非是有人作惡。

Spring中使用工廠方法+反射破壞構(gòu)造私有化

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中已經(jīng)有這個(gè)對(duì)象 
    if(prMap.containsKey(type)){ 
      product = prMap.get(type);
    }else{
      if(type.equals("Product1")){
        product = new ConcreteProduct1(); 
      }else{ 
        product = new ConcreteProduct2();
      } 
      //同時(shí)把對(duì)象放到緩存容器中 
      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;
}

代碼還比較簡(jiǎn)單,通過(guò)定義一個(gè)Map容器,容納所有產(chǎn)生的對(duì)象, 如果在Map容器中已經(jīng)有的對(duì)象,則直接取出返回;如果沒(méi)有,則根據(jù) 需要的類型產(chǎn)生一個(gè)對(duì)象并放入到Map容器中,以方便下次調(diào)用。

延遲加載框架是可以擴(kuò)展的,例如限制某一個(gè)產(chǎn)品類的最大實(shí)例化 數(shù)量,可以通過(guò)判斷Map中已有的對(duì)象數(shù)量來(lái)實(shí)現(xiàn),這樣的處理是非常 有意義的,例如JDBC連接數(shù)據(jù)庫(kù),都會(huì)要求設(shè)置一個(gè)MaxConnections 最大連接數(shù)量,該數(shù)量就是內(nèi)存中最大實(shí)例化的數(shù)量。

延遲加載還可以用在對(duì)象初始化比較復(fù)雜的情況下,例如硬件訪 問(wèn),涉及多方面的交互,則可以通過(guò)延遲加載降低對(duì)象的產(chǎn)生和銷毀帶 來(lái)的復(fù)雜性。

創(chuàng)建對(duì)象需要大量重復(fù)的代碼

客戶端不依賴于產(chǎn)品類實(shí)例如何被創(chuàng)建,實(shí)現(xiàn)等細(xì)節(jié)。一個(gè)類通過(guò)其子類來(lái)指定創(chuàng)建哪個(gè)對(duì)象

最佳實(shí)踐

工廠方法模式在項(xiàng)目中使用得非常頻繁,以至于很多代碼中都包含工廠方法模式。該模式幾乎盡人皆知,但不是每個(gè)人都能用得好。熟能生巧,熟練掌握該模式,多思考工廠方法如何應(yīng)用,而且工廠方法模式還可以與其他模式混合使用(例如模板方法模式、單例模式、原型模式等),變化出無(wú)窮的優(yōu)秀設(shè)計(jì),這也正是軟件設(shè)計(jì)和開(kāi)發(fā)的樂(lè)趣所在。

缺點(diǎn)

  1. 類的個(gè)數(shù)容易過(guò)多,增加復(fù)雜度
  2. 增加了系統(tǒng)的抽象性和理解難度

問(wèn)題

工廠方法它解決了簡(jiǎn)單工廠的哪個(gè)問(wèn)題?

  • 解決了不同工廠生成不同產(chǎn)品需要裝配過(guò)多的代碼在同一個(gè)類里面,違背了開(kāi)閉原則,通過(guò)工廠方法來(lái)解決快速擴(kuò)展的問(wèn)題

抽象工廠

客戶端無(wú)需了解其所調(diào)用工廠的具體類信息。

圖解抽象工廠

提供一個(gè)創(chuàng)建一系列相關(guān)或相互依賴對(duì)象的接口,無(wú)須指定他們具體的類
客戶端不依賴于產(chǎn)品類實(shí)例如何被創(chuàng)建、實(shí)現(xiàn)等細(xì)節(jié)
強(qiáng)調(diào)一系列相關(guān)的產(chǎn)品對(duì)象(屬于同一產(chǎn)品族)一起使用創(chuàng)建對(duì)象需要大量重復(fù)的代碼

image-20201230165226624
  • 產(chǎn)品族
    • 代表同一個(gè)品牌,不同產(chǎn)品
  • 產(chǎn)品等級(jí)結(jié)構(gòu)
    • 同一產(chǎn)品不同品牌

例子

image-20201230192218653
public class AbstractFactoryTest {

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

通用設(shè)計(jì)

image-20201230193403492

我們可以通過(guò)指定不同的抽象工廠來(lái)創(chuàng)建對(duì)應(yīng)的產(chǎn)品

適用場(chǎng)景

  • 如果代碼需要與多個(gè)不同系列的相關(guān)產(chǎn)品交互, 但是由于無(wú)法提前獲取相關(guān)信息, 或者出于對(duì)未來(lái)擴(kuò)展性的考慮, 你不希望代碼基于產(chǎn)品的具體類進(jìn)行構(gòu)建, 在這種情況下, 你可以使用抽象工廠。
  • 抽象工廠為你提供了一個(gè)接口, 可用于創(chuàng)建每個(gè)系列產(chǎn)品的對(duì)象。 只要代碼通過(guò)該接口創(chuàng)建對(duì)象, 那么你就不會(huì)生成與應(yīng)用程序已生成的產(chǎn)品類型不一致的產(chǎn)品。
  • 如果你有一個(gè)基于一組抽象方法的類, 且其主要功能因此變得不明確, 那么在這種情況下可以考慮使用抽象工廠模式。
  • 在設(shè)計(jì)良好的程序中, 每個(gè)類僅負(fù)責(zé)一件事。 如果一個(gè)類與多種類型產(chǎn)品交互, 就可以考慮將工廠方法抽取到獨(dú)立的工廠類或具備完整功能的抽象工廠類中。

優(yōu)缺點(diǎn)

優(yōu)點(diǎn) 缺點(diǎn)
你可以確保同一工廠生成的產(chǎn)品相互匹配。 由于采用該模式需要向應(yīng)用中引入眾多接口和類, 代碼可能會(huì)比之前更加復(fù)雜。
你可以避免客戶端和具體產(chǎn)品代碼的耦合。
單一職責(zé)原則。 你可以將產(chǎn)品生成代碼抽取到同一位置, 使得代碼易于維護(hù)。
開(kāi)閉原則。 向應(yīng)用程序中引入新產(chǎn)品變體時(shí), 你無(wú)需修改客戶端代碼。
  • 不符合開(kāi)閉原則
    • 如果在對(duì)應(yīng)的工廠上面新增一個(gè)功能,所有實(shí)現(xiàn)的地方都需要去修改慎用
  • 不依賴于產(chǎn)品實(shí)例如何被創(chuàng)建,實(shí)現(xiàn)的細(xì)節(jié)
  • 強(qiáng)調(diào)一系列相關(guān)的產(chǎn)品(統(tǒng)一產(chǎn)品族)一起使用穿件對(duì)象需要大量重復(fù)的的重復(fù)代碼的場(chǎng)景
  • 擴(kuò)展性很強(qiáng)

問(wèn)題

舉例說(shuō)出抽象工廠的應(yīng)用場(chǎng)景,我如果要增加一個(gè)答疑的產(chǎn)品應(yīng)該怎么修改代碼

  • 比如說(shuō)女?huà)z造人,需要有男人和女人,也需要有白種人,黑種人,黃種人,那么我們可以定義一個(gè)頂層接口Humanfactory然后FemaleFactoryMaleFactory都實(shí)現(xiàn)對(duì)應(yīng)的HumanFactory的接口,然后對(duì)應(yīng)的工廠創(chuàng)建對(duì)應(yīng)的產(chǎn)品
  • image-20201230193534527
  • 增加答疑的產(chǎn)品的話需要新增一個(gè)答疑的接口,所有工廠都需要去修改補(bǔ)充對(duì)應(yīng)的實(shí)現(xiàn),所以改動(dòng)會(huì)非常大
    我的筆記倉(cāng)庫(kù)地址gitee 快來(lái)給我點(diǎn)個(gè)Star吧
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容