01.21 工廠模式詳解

工廠模式詳解

簡單工廠

簡單工廠模式(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>

工廠方法適用於以下場景:

  1. 創建對象需要大量重複的代碼。
  2. 客戶端(應用層)不依賴於產品類實例如何被創建、實現等細節。
  3. 一個類通過其子類來指定創建哪個對象。

工廠方法的缺點

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

抽象工廠模式

抽象工廠模式(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 也加入到課程中,那麼我們的代碼從抽象工廠,到具體工廠要全部調整,很顯然不符合開閉原則。因此抽象工廠也是有缺點的:

  1. 規定了所有可能被創建的產品集合,產品族中擴展新的產品困難,需要修改抽象工廠的接口。
  2. 增加了系統的抽象性和理解難度。


分享到:


相關文章: