看透 "工厂方法" 模式

1.引入

考虑一个应用程序类Application,支持创建文本文档,编辑文本文档等操作。

public class TextDocument{  
        public void editDocument(){
                .........
        }

        public void saveDocument(){
                .........
        }
}

public class Application{
        private TextDocument mDoc;

        public TextDocument createDocument(){
                mDoc = new TextDocument();
        }

        public boolean isDocumentAvailable(){
                return mDoc != null;
        }

        public void editDocument(){
                mDoc.editDocument();
        }

        public void saveDocument(){
                mDoc.saveDocument();
        }
}

对象关系如下图所示:

对象关系.png

现在,如果我们想添加对image文档的支持,我们如何设计程序呢。从设计模式的核心思想(封装变化,解耦具体实现)出发,扩展程序我们需要两步。
1.封装变化 将程序中可能因为需求变化导致代码变动的部分封装到具体类中。针对上述示例,如果添加对image文档的支持,创建文档部分的代码需要调整,所以我们将创建TextDocument的操作封装到类中,命名 TextDocumentFactory

public class TextDocument{  
        public void editDocument(){
                .........
        }

        public void saveDocument(){
                .........
        }
}

public class TextDocumentFactory{
        public TextDocument createDocument(){
                return new TextDocument();
        }
}

public class Application{
        private TextDocument mDoc;

        public TextDocument createDocument(TextDocumentFactory factory){
                mDoc =factory.createDocument();
        }

        public boolean isDocumentAvailable(){
                return mDoc != null;
        }

        public void editDocument(){
                mDoc.editDocument();
        }

        public void saveDocument(){
                mDoc.saveDocument();
        }
}

对象关系如下图所示:

对象关系引入TextDocumentFactory.png

2.解耦具体实现 根据面向接口编程原则,在Application和TextDocumentFactory之间引入一个抽象接口类DocumentFactory,就可以隔离Application和具体的XXXDocumentFactory之间的耦合关系。通过创建不同的DocumentFactory子类,我们就可以支持不同的文档类型。

public interface Document{
        void editDocument();
        void saveDocument();
}

/**
* text 文档
**/
public class TextDocument implements Document {  
        public void editDocument(){
                .........
        }

        public void saveDocument(){
                .........
        }
}

/**
* image 文档
**/
public class ImageDocument implements Document {  
        public void editDocument(){
                .........
        }

        public void saveDocument(){
                .........
        }
}

public interface DocumentFactory{
        Document createDocument();
}

/**
*  text 文档 工厂
**/
public class TextDocumentFactory implements DocumentFactory {
        public TextDocument createDocument(){
                return new TextDocument();
        }
}

/**
*  image 文档 工厂
**/
public class ImageDocumentFactory implements DocumentFactory {
        public TextDocument createDocument(){
                return new TextDocument();
        }
}

public class Application{
        private TextDocument mDoc;

        public TextDocument createDocument(TextDocumentFactory factory){
                mDoc =factory.createDocument();
        }

        public boolean isDocumentAvailable(){
                return mDoc != null;
        }

        public void editDocument(){
                mDoc.editDocument();
        }

        public void saveDocument(){
                mDoc.saveDocument();
        }
}

对象关系如下图所示:

工厂方法模式.png

这样,我们不仅很好支持了image文档的操作,还为其他各种文档的支持预留了扩展。

反思文章刚开始作者遇到的问题,你会发现,在开发项目的过程中,经常会遇到类似的情况。解决问题的思路很重要,但是,我们依然为上面的解决方案(类之间的关系模式)定义了一个名字---工厂方法模式

2.定义

基类中包含一个创建对象的接口(方法),具体创建何种类型的对象延迟到子类实现。类之间的这种关系模式,我们称为工厂方法模式。

3.实现

从细节上来说,工厂方法有三种不同的实现方式。
1.抽象工厂不提供工厂方法的缺省实现。它避免了不得不实例化不可预见类的问题。
2.抽象工厂提供工厂方法的缺省实现。它遵循的准则是,"用一个独立的操作创建对象,这样子类才能重定义它们的创建方式"
3.参数化工厂方法。工厂方法接收一个标识要被创建的对象种类的参数,这样,工厂方法就可以创建多种产品。重定义一个参数化的工厂方法使你可以简单而有选择性的扩展或改变一个工厂生产的产品。
一个参数化的工厂方法具有如下的一般形式,此处 MyDocument和YourDocument是Document的子类:

public class DocumentFactory {
        public Document createDocument(DocumentId id){
            if (id == MINE) return new MyDocument();
            if (id == YOURS) return new YourDocument();

            return null;
        }
}

我们可以通过创建DocumentFactory子类的方式交换MyDocument和YourDocument并且支持一个新的子类TheirDocument:

public class MyDocumentFactory extends DocumentFactory {
        public Document createDocument(DocumentId id){
            if (id == YOURS) return new MyDocument();
            if (id == MINE) return new YourDocument();
                 // switch YOURS and MINE


            if (id == THEIRS) return new TheirDocument();

            return super.createDocument(id);
        }
}

注意这个操作所做的最后一件事是调用父类的createDocument。这是因为MyDocumentFactory.createDocument仅在对YOURS , MINE 和THEIRS的处理上和父类不同。它对其他类不感兴趣。因此 MyDocumentFactory 扩展了所创建产品的种类,并且将除少数产品以外所有产品的创建职责延迟给了父类。

4.效果

工厂方法不再将与特定实现有关的类绑定到你的代码中。代码仅处理Document接口;因此它可以与用户定义的任何ConcreteDocument类一起使用。
工厂方法的一个潜在缺点在于客户可能仅仅为了创建一个特定的ConcreteDocument对象,就不得不创建DocumentFactory的子类。

为子类提供hook。用工厂方法在一个类的内部创建对象通常比直接创建对象更灵活。Factory Method给子类一个hook以提供对象的扩展版本。
在Document的例子中,Document类可以定义一个称为createFileDialog的工厂方法,该方法为打开一个已有的文档创建默认的文件对话框对象。Document的子类可以重定义这个工厂方法以定义一个与特定应用相关的文件对话框。在这种情况下,工厂方法就不再抽象了而是提供了一个合理的缺省实现。

5.后记

很多人建议我多举几个工厂方法的例子,加深理解。但是我的理论是,看透一个例子,理解其中的精髓,用剩下的时间,多用用不同的模式设计程序,你才可以事半功倍。建议读懂,经常重复温习上面的文章,每一个字对于理解工厂方法模式都有一定的分量,加油!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容