【JAVA设计模式】工厂方法模式(Factory Method)无标题文章

定义

定义一个用于创建对象的接口,让子类决定实例化哪一个类,Factory Method 使一个类的实例化延迟到其子类

正如名字所示,工厂方法模式与简单工厂有几分相似。关于差异,后文将会介绍。

案例

例如有这样一个应用:实现一个导出数据的应用框架,来让客户选择导出方式,比如导出成文本格式,数据格式,xml格式等等。
依然,为了面向接口编程。我们首先定义一个接口,抽象了导出数据功能。

/**
 * 各种导出形式的操作都遵循此接口
 * Created by jerry on 16-4-21.
 */
public interface ExportFileApi {
    /**
     * 导出内容
     * @param data 导出数据
     * @return 是否成功
     */
    public boolean export(String data);
}

现在如果我想要实现把数据导出文本格式或者导出成其他格式,那么实现此接口即可。

/**
 * 将数据导出到txt文件中去
 * Created by jerry on 16-4-21.
 */
public class ExportTxtFile implements ExportFileApi{

    @Override
    public boolean export(String data) {
        System.out.println("导出数据: "+ data + " 到txt中");
        return true;
    }
}
/**
 * 导出数据到数据库中
 * Created by jerry on 16-4-21.
 */
public class ExportDB implements ExportFileApi{

    @Override
    public boolean export(String data) {
        System.out.println("导出数据: " +data+ " 到数据库中");
        return true;
    }
}

问题

试想一个问题,当我们在设计时,我们还没有想好将来究竟是用哪个具体实现,例如究竟是导出到文件呢?还是数据库呢?还是其他?换句话说,关于具体采用哪种,我想之后再做决定。这时,为了让框架层代码能够不受影响,就需要工厂方法模式。

使用模式的情况

工厂方法模式的主要功能就是让父类在不知道具体实现的情况下,完成自身的功能调用,而具体实现延迟到子类来实现。通常情况下,父类是一个抽象类,里面包含了一个抽象方法,此方法即工厂方法。

/**
 * 实现导出数据操作功能
 * Created by jerry on 16-4-21.
 */
public abstract class ExportOperate {

    /**
     * 导出数据
     * @param data
     * @return
     */
    public boolean export(String data) {
        ExportFileApi api = factoryMethod();
        return api.export(data);
    }

    /**
     * 工厂方法,创建导出的文件对象的接口对象
     * @return
     */
    protected abstract ExportFileApi factoryMethod();
}

这样以后,继承ExportOperate的类必须复写factoryMethod()方法,即由子类来完成具体实现,在父类中无须关注到底是哪个具体实例负责具体的实现,父类只须面向接口编程,大大减少了耦合度。

/**
 * 在这里真正创建具体对象
 * Created by jerry on 16-4-21.
 */
public class ExportTxtFileOperate extends ExportOperate {

    @Override
    protected ExportFileApi factoryMethod() {
        return new ExportTxtFile();
    }
}
/**
 * Created by jerry on 16-4-21.
 */
public class ExportDBOperate extends ExportOperate {

    @Override
    protected ExportFileApi factoryMethod() {
        return new ExportDB();
    }
}

客户端调用如下:

/**
 * 测试类
 * Created by jerry on 16-4-21.
 */
public class Client {

    public static void main(String[] args) {
        ExportOperate operate = new ExportDBOperate();
        operate.export("test");
    }
}

参数化工厂方法

工厂方法的设计中,可以使用参数,这样就可以通过参数决定到底使用哪一个具体的实现。
如果采用参数化工厂方法,那么通常工厂方法不再抽象,而是变为普通方法,并且提供默认的实现。此时,既然有了默认实现,工厂方法所在类也无须变为抽象,普通类即可。这样客户端可以直接使用这个类。

/**
 * 实现导出数据操作功能
 * Created by jerry on 16-4-21.
 */
public class ExportOperateByPara {

    /**
     * 导出数据
     * @param data
     * @param type 类型选择
     * @return
     */
    public boolean export(int type, String data) {
        ExportFileApi api = factoryMethod(type);
        return api.export(data);
    }

    /**
     * 工厂方法,根据参数创建导出的文件对象的接口对象
     * @param type
     * @return
     */
    protected ExportFileApi factoryMethod(int type) {
        ExportFileApi api = null;

        if (type == 1) {
            api = new ExportTxtFile();
        } else if (type == 2) {
            api = new ExportDB();
        }
        return api;
    }

}

由于有了默认实现,直接使用即可,客户端调用如下:

/**
 * 测试类2
 * Created by jerry on 16-4-21.
 */
public class Client2 {

    public static void main(String[] args) {
        ExportOperateByPara operate = new ExportOperateByPara();
        operate.export(2,"test");
    }
}

之所以使用参数化工厂方法,是因为其扩展起来会非常方便,已有的代码不会变,只要新加入一个子类来提供新的工厂方法实现,然后在客户端使用这个新的子类即可。
例如,我们要扩展一个导出xml文件的类:

/**
 * 导出到xml
 * Created by jerry on 16-4-21.
 */
public class ExportXml implements ExportFileApi {

    @Override
    public boolean export(String data) {
        System.out.println("导出数据: " + data + " 到xml文件");
        return true;
    }
}

加入新的实现类,这里可以让子类选择性覆盖,进行功能增强,或者不想覆盖还可以返回去让父类来实现。

/**
 * Created by jerry on 16-4-21.
 */
public class ExportXmlOperate extends ExportOperateByPara {

    /**
     * 覆写工厂方法
     * @param type
     * @return
     */
    @Override
    protected ExportFileApi factoryMethod(int type) {
        ExportFileApi api = null;
        // 这里是一个强大功能,可以让子类选择性覆盖,不想覆盖的方法可以返回去让父类实现
        if (type == 3) {
            api = new ExportXml();
        } else {
            api = super.factoryMethod(type);
        }
        return api;
    }
}
/**
 * 测试类2
 * Created by jerry on 16-4-21.
 */
public class Client2 {

    public static void main(String[] args) {
        ExportOperateByPara operate = new ExportXmlOperate();
        operate.export(1,"test1");
        operate.export(2,"test2");
        operate.export(3,"test3");
    }
}

程序运行如下:

导出数据: test1 到txt中
导出数据: test2 到数据库中
导出数据: test3 到xml文件

这个易扩展性的获得,是因为工厂方法给子类提供了一个钩子,使得子类可以操作父类的方法,使得扩展新的对象版本变得非常容易。

与简单工厂模式的区别

如果把ExportOperateByPara中的export方法删掉,那么发现此类就是一个简单工厂类。所以两者其实非常相似,在具体实现上都是“选择实现”。但是不同点在于:简单工厂直接在工厂类里面进行“选择实现”,而工厂方法会把这个工作延迟到子类来实现。

总结

本质:延迟到子类来选择实现。

何时使用工厂方法模式:

  1. 如果一个类需要创建某个接口的对象,但是有不知道具体的实现。此时可选用工厂方法模式,把创建对象的工作延迟到子类中实现。
  2. 如果一个类本身就希望由它的子类来创建所需的对象的时候,应该使用工厂方法模式。

我的博客:http://shenchao.me/
Github: https://github.com/jerry-sc/designPattern
Reference:《研磨设计模式》

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

推荐阅读更多精彩内容

  • 1 场景问题# 1.1 导出数据的应用框架## 考虑这样一个实际应用:实现一个导出数据的应用框架,来让客户选择数据...
    七寸知架构阅读 6,384评论 6 72
  • 设计模式汇总 一、基础知识 1. 设计模式概述 定义:设计模式(Design Pattern)是一套被反复使用、多...
    MinoyJet阅读 3,875评论 1 15
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,497评论 18 399
  • 简单工厂模式虽然简单,但存在一个很严重的问题。当系统中需要引入新产品时,由于静态工厂方法通过所传入参数的不同来创建...
    justCode_阅读 1,163评论 1 9
  • 每日我都以愉快的心情思念你 在这宇宙一块安静的地方 等候你的佳音 你是我无处不在的天使 主宰着我的生与死 你的喜怒...
    烧火一条柴阅读 312评论 0 2