看透 "工厂方法" 模式

作者: 刘硕jessie | 来源:发表于2018-06-07 10:10 被阅读285次

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.后记

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

相关文章

网友评论

  • Ant_way:刚哥平台过来的,受限文章引入的例子不错,但是我觉得文章有些略简。没有把简单工厂说的很透彻。
    请叫我大苏:@Andoter的学习笔记 同刚哥过来。他这篇主题想讲的是工厂方法模式,我和你同感,感觉举的例子用抽象工厂模式就可以了,没必要用到工厂方法,老哥,例子再稍微补充着场景

本文标题:看透 "工厂方法" 模式

本文链接:https://www.haomeiwen.com/subject/pofssftx.html