生活中的设计模式之工厂模式

定义

define an interface or abstract class for creating an object but let the subclasses decide which class to instantiate.

定义一个创建对象的接口或抽象类,但让子类决定实例化那个具体的类。

实列

生活中,有很多的企业、店铺、公司会为我们生产各种各样的商品,作为消费者的我们只关心如何使用这些产品,而不需要知道它们的制作过程。
比如说,包子店它会生产肉包、菜包、豆腐包等各式各样的包子,如果我们决定早上吃豆腐包中午吃豆沙包,都只需要告知店铺便可以获得生产好的包子,而不需要自己生产。
工厂模式中的工厂就好比上面的包子店,但在工厂模式中我们把这类相似的商品称为产品,它们不仅在形态上相似,而且创建过程也非常相似。

故事

前不久,我入职了一家开发射击类游戏的公司,并负责公共组件枪支库的开发。
枪支库中有手枪、冲锋枪、步枪等不同类型的枪支产品,它们都继承同一个抽象类Gun。
这个抽象类声明了三个操作,一个是shoot用于射击目标对象,一个是setBullet用于装子弹,还有一个是load用于上膛。
公司其它部门的同事,如果要将这些产品应用到不同的射击游戏中,那么他们需要先使用new实例化对象,然后给对象装上与之匹配的子弹并上膛,便可以调用shoot操作了。
伪代码如下:


public class Client {
    public static void main(String[] args) {
        String gunName = args[0];
        String target = args[1];
        if("HANDGUN".equals(gunName)){
            Handgun handgun = new Handgun();
            handgun.setBullet(new HandgunBullet());
            handgun.load();
            //射击目标
            handgun.shoot(target);
        }else if("SMG".equals(gunName)){
            SMG smg = new SMG();
            smg.setBullet(new SMGBullet());
            handgun.load();
            //射击目标
            smg.shoot(target);
        }else if("RIFLE".equals(gunName)){
            Rifle smg = new Rifle();
            smg.setBullet(new RifleBullet());
            handgun.load();
            //射击目标
            smg.shoot(target);
        }
    }
}

问题

从上面的代码中我们可以看出,这款游戏会根据用户选择的枪支来射击目标对象。
但是,在客户端使用new关键字创建产品会存在几个问题。首先是扩展问题,如果后续射击游戏需要增加新的枪支,那么就要修改客户端代码。
其次是耦合问题,因为枪支是游戏的基础类,一旦它发生变化,那么所有的客户端都很可能都要跟着变化,比如说重命名某个枪支类的名称。
最后是重复问题,装弹、上膛是射击前的初始化操作,如果枪支库被用于多款射击游戏,那么创建对象的过程就会重复。
所以,有没有一种方式可以让客户端不操心产品是如何创建的?这便是工厂模式。

方案

工厂模式是一种创建型设计模式,它会将同类产品的创建以及初始化操作封装到独立的类即工厂类(Facotry)中。
工厂会向外暴露一个创建产品的操作,客户端只需调用该操作就可以获得指定的对象,而不需要知道产品是如何创建以及如何初始化的。
这样,工厂便解藕了客户端和产品之间的直接依赖关系,以及复用了初始化过程。

实现

工厂模式分为简单工厂模式和工厂方法模式以及抽象工厂模式,下面我们来看看前两种模式。

简单工厂模式

首先,我们创建一个名为简单工厂的类,将对象的创建以及初始化操作封装起来。



public class SimpleFactory {

    public static Gun create(String gunName){
        Gun gun;
        if("HANDGUN".equals(gunName)){
            gun = new Handgun();
            gun.setBullet(new HandgunBullet());
        }else if("SMG".equals(gunName)){
            gun = new SMG();
            gun.setBullet(new SMGBullet());
        }else if("RIFLE".equals(gunName)){
            gun = new Rifle();
            gun.setBullet(new RifleBullet());
        }
        gun.load();
        return gun;
    }
}

然后,修改客户端代码,将创建对象的任务委托给简单工厂来创建,这样客户端就能多态地使用对象了。


public class Client1 {
    public static void main(String[] args) {
        String gunName = args[0];
        String target = args[1];
        Gun gun = SimpleFactory.create(gunName);
        gun.shoot(target);
    }
}

可以看出,简单工厂模式的实现非常的简单,但是它没有解决扩展的问题,当新增产品时还是需要修改简单工厂类。

工厂方法模式

在工厂方法模式中,不同的产品由不同的工厂类创建,而不是像简单工厂一样由一个工厂创建,工厂和产品之间通常是一对一的关系,这些工厂都继承自一个共同的抽象类。
下面我们看看工厂模式又是如何实现的:

首先,我们创建一个抽象工厂的类,定义产品的创建流程以及声明子类要实现的差异化操作。


/**抽象工厂类*/
public abstract class AbstractFactory {
    /**定义创建对象的标准流程*/
    public Gun create(){
        Gun gun= doCreate();
        //上膛
        gun.load();
        return gun;
    }
    /**子类差异化实现*/
    protected abstract Gun doCreate();


}

然后,创建具体的工厂类实现差异的部分,比如,我们为手枪单独创建一个工厂。

/**手枪工厂*/
public class HandgunFactory extends AbstractFactory{
    @Override
    protected Gun doCreate() {
        Handgun handgun = new Handgun();
        //装子弹
        handgun.setBullet(new HandgunBullet());
        return handgun;
    }
}

最后,我们在来看看客户端如何使用工厂方法模式创建产品。


public class Client {
    public static void main(String[] args) {
        String gunName = args[0];
        String target = args[1];

        //具体工厂的创建可以使用Java的反射技术
        AbstractFactory factory ;
        if("HANDGUN".equals(gunName)){
            factory = new HandgunFactory();
        }else if("SMG".equals(gunName)){
            factory = new HandgunFactory();
        }else if("RIFLE".equals(gunName)){
            factory = new HandgunFactory();
        }

        Gun handgun = factory.create();
        //射击目标
        handgun.shoot(target);
    }
}

很明显,工厂方法模式没有简单工厂模式那样简单,甚至看上去比不使用工厂还复杂。
但是,用一个工厂创建一种产品,能解决扩展性方面的问题。前提是具体工厂的创建得使用反射,这里我们就不再深入,读者可以自行了解一下。

结构

avatar

产品(Product):这里的产品指的是继承或实现同一接口或父类的对象,它们同属一个家族。

抽象工厂(AbstractFactory):它是对创建对象行为的抽象,声明了可相互替代产品的创建接口。如果创建涉及对象初始化,那么可以在抽象类中定义共同的创建流程。

具体工厂(ConcreteFactory):它实现自抽象工厂,负责具体产品的创建。

总结

当程序需要根据不同的条件创建不同的产品,而且产品的数量会持续增加时,为了避免客户端和具体产品的耦合,那么我们应该考虑使用工厂方法模式。
工厂方法模式可以解决简单工厂解决不了的问题:扩展性问题,它可以在增加新的产品时不修改客户端代码,做到插件式地动态扩展产品。

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

推荐阅读更多精彩内容