工廠模式詳解
簡單工廠
簡單工廠模式(Simple Factory Pattern)是指由一個工廠對象決定創建出哪一種產品類的實例。簡單工廠適用於工廠類負責創建的對象較少的場景,且客戶端只需要傳入工廠類的參數,對於如何創建對象的邏輯不需要關心。
接下來我們來看代碼,還是以課程為例。我們定義一個課程標準 ICourse 接口:
<code>public interface ICourse { //錄製視頻 void record();}/<code>
創建一個 Java 課程的實現 JavaCourse 類:
<code>public class JavaCourse implements ICourse { @Override public void record() { System.out.println("錄製java視頻"); }}/<code>
看客戶端調用代碼,一般我們會這樣寫:
<code>public class SimpleFactoryTest { public static void main(String[] args) { ICourse course = new JavaCourse(); course.record(); }}/<code>
看上面的代碼,父類 ICourse 指向子類 JavaCourse 的引用,應用層代碼需要依賴JavaCourse,如果業務擴展,我繼續增加 PythonCourse 甚至更多,那麼我們客戶端的依賴會變得越來越臃腫。因此,我們要想辦法把這種依賴減弱,把創建細節隱藏。雖然目前的代碼中,我們創建對象的過程並不複雜,但從代碼設計角度來講不易於擴展。現在,我們用簡單工廠模式對代碼進行優化。先增加課程 PythonCourse 類:
<code>public class PythonCourse implements ICourse { @Override public void record() { System.out.println("錄製Python視頻"); }}/<code>
創建 CourseFactory 工廠類:
<code>public class CourseFactory { public static ICourse create(String name){ if("java".equals(name)){ return new JavaCourse(); }else if("python".equals(name)){ return new PythonCourse(); }else { return null; } }}/<code>
修改測試代碼:
<code>public class SimpleFactoryTest { public static void main(String[] args) { //ICourse course = new JavaCourse(); ICourse course = CourseFactory.create("java"); course.record(); }}/<code>
現在客戶端調用是不是就簡單了呢,但如果我們業務繼續擴展,要增加前端課程,那麼工廠中的 create()就要根據產品的增加每次都要修改代碼邏輯。不符合開閉原則。因此,我們對簡單工廠還可以繼續優化,可以採用反射技術:
<code>public class CourseFactory { public static ICourse create(String className){ //這裡沒有引入springUtil相關的jar包,也沒有自己封裝,將就吧 if(className != null && !"".equals(className)){ try { return (ICourse) Class.forName(className).newInstance(); } catch (Exception e) { e.printStackTrace(); } } return null; }}/<code>
修改測試代碼:
<code>public class SimpleFactoryTest { public static void main(String[] args) { //ICourse course = new JavaCourse(); //ICourse course = CourseFactory.create("java"); ICourse course = CourseFactory.create("com.cl.factory.simplefactory.JavaCourse"); course.record(); }}/<code>
優化之後,產品不斷豐富不需要修改 CourseFactory 中的代碼。但是,有個問題是,方法參數是字符串,可控性有待提升,而且還需要強制轉型。我們再修改一下代碼:
<code>public class CourseFactory { public static ICourse create(Class extends ICourse> clazz){ if(clazz != null){ try { return clazz.newInstance(); } catch (Exception e) { e.printStackTrace(); } } return null; }}/<code>
優化測試代碼:
<code>public class SimpleFactoryTest { public static void main(String[] args) { //ICourse course = new JavaCourse(); //ICourse course = CourseFactory.create("java"); //ICourse course = CourseFactory.create("com.cl.factory.simplefactory.JavaCourse"); ICourse course = CourseFactory.create(JavaCourse.class); course.record(); }}/<code>
簡單工廠到這裡就結束了。這裡再簡單舉例說下簡單工廠的應用場景
<code>//這個代碼大家應該很熟悉Logger logger = LoggerFactory.getLogger(CacheManager.class);//看下它的實現public static Logger getLogger(String name) { ILoggerFactory iLoggerFactory = getILoggerFactory(); return iLoggerFactory.getLogger(name); } public static Logger getLogger(Class> clazz) { Logger logger = getLogger(clazz.getName()); if (DETECT_LOGGER_NAME_MISMATCH) { Class> autoComputedCallingClass = Util.getCallingClass(); if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) { Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(), autoComputedCallingClass.getName())); Util.report("See http://www.slf4j.org/codes.html#loggerNameMismatch for an explanation"); } } return logger; }/<code>
工廠方法模式
工廠方法模式(Fatory Method Pattern)是指定義一個創建對象的接口,但讓實現這個接口的類來決定實例化哪個類,工廠方法讓類的實例化推遲到子類中進行。在工廠方法模式中用戶只需要關心所需產品對應的工廠,無須關心創建細節,而且加入新的產品符合開閉原則。 工廠方法模式主要解決產品擴展的問題,在簡單工廠中,隨著產品鏈的豐富,如果每個課程的創建邏輯有區別的話,工廠的職責會變得越來越多,有點像萬能工廠,並不便於維護。根據單一職責原則我們將職能繼續拆分,專人幹專事。Java 課程由 Java 工廠創建,Python 課程由 Python 工廠創建,對工廠本身也做一個抽象。來看代碼,先創建ICourseFactory 接口:
<code>public interface ICourseFactory { ICourse crete();}/<code>
在分別創建子工廠,JavaCourseFactory 類:
<code>public class JavaCourseFactory implements ICourseFactory { @Override public ICourse crete() { return new JavaCourse(); }}/<code>
PythonCourseFactory 類:
<code>public class PythonCourseFactory implements ICourseFactory { @Override public ICourse crete() { return new PythonCourse(); }}/<code>
看測試代碼:
<code>public class SimpleFactoryTest { public static void main(String[] args) { ICourseFactory courseFactory = new JavaCourseFactory(); ICourse course = courseFactory.crete(); course.record(); courseFactory = new PythonCourseFactory(); course = courseFactory.crete(); course.record(); }}/<code>
工廠方法適用於以下場景:
- 創建對象需要大量重複的代碼。
- 客戶端(應用層)不依賴於產品類實例如何被創建、實現等細節。
- 一個類通過其子類來指定創建哪個對象。
工廠方法的缺點
- 類的個數容易過多,增加複雜度。
- 增加了系統的抽象性和理解難度。
抽象工廠模式
抽象工廠模式(Abastract Factory Pattern)是指提供一個創建一系列相關或相互依賴對象的接口,無須指定他們具體的類。客戶端(應用層)不依賴於產品類實例如何被創建、實現等細節,強調的是一系列相關的產品對象(屬於同一產品族)一起使用創建對象需要大量重複的代碼。需要提供一個產品類的庫,所有的產品以同樣的接口出現,從而使客戶端不依賴於具體實現。
接下來我們來看一個具體的業務場景而且用代碼來實現。還是以課程為例,每個課程不僅要提供課程的錄播視頻,而且還要提供課堂筆記。相當於現在的業務變更為同一個課程不單純是一個課程信息,要同時包含錄播視頻、課堂筆記甚至還要提供源碼才能構成一個完整的課程。在產品等級中增加兩個產品 IVideo 錄播視頻和 INote 課堂記。
IVideo 接口:
<code>public interface IVideo { void record();}/<code>
INote 接口:
<code>public interface INote { void edit();}/<code>
接下來,創建 Java 產品族,Java 視頻 JavaVideo 類:
<code>public class JavaVideo implements IVideo { @Override public void record() { System.out.println("java視頻"); }}/<code>
Java 筆記 JavaNote 類:
<code>public class JavaNote implements INote { @Override public void edit() { System.out.println("java筆記"); }}/<code>
然後創建一個抽象工廠 CourseFactory 類(最好新建一個包,為了和工廠方法區別開):
<code>public interface CourseFactory { INote createNote(); IVideo createVideo();}/<code>
創建 Java 產品族的具體工廠 JavaCourseFactory:
<code>public class JavaCourseFactory implements CourseFactory { @Override public INote createNote() { return new JavaNote(); } @Override public IVideo createVideo() { return new JavaVideo(); }}/<code>
然後創建 Python 產品,Python 視頻 PythonVideo 類:
<code>public class PythonVideo implements IVideo { @Override public void record() { System.out.println("python視頻"); }}/<code>
擴展產品等級 Python 課堂筆記 PythonNote 類:
<code>public class PythonNote implements INote { @Override public void edit() { System.out.println("python筆記"); }}/<code>
創建 Python 產品族的具體工廠 PythonCourseFactory:
<code>public class PythonCourseFactory implements CourseFactory { @Override public INote createNote() { return new PythonNote(); } @Override public IVideo createVideo() { return new PythonVideo(); }}/<code>
下面是測試代碼:
<code>public class AbstractFactoryTest { public static void main(String[] args) { JavaCourseFactory javaCourseFactory = new JavaCourseFactory(); javaCourseFactory.createNote().edit(); javaCourseFactory.createVideo().record(); PythonCourseFactory pythonCourseFactory = new PythonCourseFactory(); pythonCourseFactory.createNote().edit(); pythonCourseFactory.createVideo().record(); }}/<code>
上面的代碼完整地描述了兩個產品族 Java 課程和 Python 課程,也描述了兩個產品等級視頻和筆記。抽象工廠非常完美清晰地描述這樣一層複雜的關係。但是,不知道大家有沒有發現,如果我們再繼續擴展產品等級,將源碼 Source 也加入到課程中,那麼我們的代碼從抽象工廠,到具體工廠要全部調整,很顯然不符合開閉原則。因此抽象工廠也是有缺點的:
- 規定了所有可能被創建的產品集合,產品族中擴展新的產品困難,需要修改抽象工廠的接口。
- 增加了系統的抽象性和理解難度。
閱讀更多 科技伍小黑 的文章