一篇搞定工厂模式【简单工厂、工厂方法模式、抽象工厂模式】

(一) 举一个模拟 Spring IOC 的例子

(1) 以前的程序

首先,按照我们常规的方式先模拟,我们先将一套基本流程走下来

A:Service 层

/**

* 账户业务层接口

*/publicinterfaceAccountService{voidaddAccount();}/**

* 账户业务层实现类

*/publicclassAccountServiceImplimplementsAccountService{privateAccountDao accountDao =newAccountDaoImpl();publicvoidaddAccount(){        accountDao.addAccount();    }}

B:Dao 层

/**

* 账户持久层接口

*/publicinterfaceAccountDao{voidaddAccount();}/**

* 账户持久层实现类

*/publicclassAccountDaoImplimplementsAccountDao{publicvoidaddAccount(){        System.out.println("添加用户成功!");    }}

C:调用

由于,我们创建的Maven工程并不是一个web工程,我们也只是为了简单模拟,所以在这里,创建了一个 Client 类,作为客户端,来测试我们的方法

publicclassClient{publicstaticvoidmain(String[] args){AccountService  as =newAccountServiceImpl();as.addAccount();    }}

运行的结果,就是在屏幕上输出一个添加用户成功的字样

D:分析:new 的问题

上面的这段代码,应该是比较简单也容易想到的一种实现方式了,但是它的耦合性却是很高的,其中这两句代码,就是造成耦合性高的根由,因为业务层(service)调用持久层(dao),这个时候业务层将很大的依赖于持久层的接口(AccountDao)和实现类(AccountDaoImpl)

privateAccountDao accountDao =newAccountDaoImpl();AccountService as =newAccountServiceImpl();

这种通过 new 对象的方式,使得不同类之间的依赖性大大增强,其中一个类的问题,就会直接导致出现全局的问题,如果我们将被调用的方法进行错误的修改,或者说删掉某一个类,执行的结果就是:

编译期就出现了错误,而我们作为一个开发者,我们应该努力让程序在编译期不依赖,而运行时才可以有一些必要的依赖(依赖是不可能完全消除的)

所以,我们应该想办法进行解耦,要解耦就要使调用者被调用者之间没有什么直接的联系,那么工厂模式就可以帮助我们很好的解决这个问题

(2) 工厂模式改进

A:BeanFactory

具体怎么实现呢?在这里可以将 serivice 和 dao 均配置到配置文件中去(xml/properties),通过一个类读取配置文件中的内容,并使用反射技术创建对象,然后存起来,完成这个操作的类就是我们的工厂

注:在这里我们使用了 properties ,主要是为了实现方便,xml还涉及到解析的一些代码,相对麻烦一些,不过我们下面要说的 Spring 就是使用了 xml做配置文件

bean.properties:先写好配置文件,将 service 和 dao 以 key=value 的格式配置好

accountService=cn.ideal.service.impl.AccountServiceImplaccountDao=cn.ideal.dao.impl.AccountDaoImpl

BeanFactory

publicclassBeanFactory{//定义一个Properties对象privatestaticProperties properties;//使用静态代码块为Properties对象赋值static{try{//实例化对象properties =newProperties();//获取properties文件的流对象InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");            properties.load(in);        }catch(Exception e){thrownewExceptionInInitializerError("初始化properties失败");        }    }  }

简单的解释一下这部分代码(当然还没写完):首先就是要将配置文件中的内容读入,这里通过类加载器的方式操作,读入一个流文件,然后从中读取键值对,由于只需要执一次,所以放在静态代码块中,又因为 properties 对象在后面的方法中还要用,所以写在成员的位置

接着在 BeanFactory 中继续编写一个 getBean 方法其中有两句核心代码的意义就是:

通过方法参数中传入的字符串,找到对应的全类名路径,实际上也就是通过刚才获取到的配置内容,通过key 找到 value值

下一句就是通过 Class 的加载方法加载这个类,实例化后返回

publicstaticObjectgetBean(String beanName){    Object bean =null;try{//根据key获取valueString beanPath = properties.getProperty(beanName);        bean = Class.forName(beanPath).newInstance();    }catch(Exception e){        e.printStackTrace();    }returnbean;}

B:测试代码:

publicclassClient{publicstaticvoidmain(String[] args){        AccountService as =    (AccountService)BeanFactory.getBean("accountService");        as.addAccount();    }}

C:执行效果:

当我们按照同样的操作,删除掉被调用的 dao 的实现类,可以看到,这时候编译期错误已经消失了,而报出来的只是一个运行时异常,这样就解决了前面所思考的问题

我们应该努力让程序在编译期不依赖,而运行时才可以有一些必要的依赖(依赖是不可能完全消除的)

(3) 小总结:

为什么使用工厂模式替代了 new 的方式?

打个比方,在你的程序中,如果一段时间后,你发现在你 new 的这个对象中存在着bug或者不合理的地方,或者说你甚至想换一个持久层的框架,这种情况下,没办法,只能修改源码了,然后重新编译,部署,但是如果你使用工厂模式,你只需要重新将想修改的类,单独写好,编译后放到文件中去,只需要修改一下配置文件就可以了

我分享下我个人精简下的理解就是:

【new 对象依赖的是具体事物,而不 new 则是依赖抽象事物】

Break it down:

依赖具体事物,这个很好理解,你依赖的是一个具体的,实实在在内容,它与你系相关,所以有什么问题,都是连环的,可能为了某个点,我们需要修改 N 个地方,绝望

依赖抽象事物,你所调用的并不是一个直接就可以触手可及的东西,是一个抽象的概念,所以不存在上面那种情况下的连环反应

二 三种工厂模式

看完前面的例子,我想大家已经已经对工厂模式有了一个非常直观的认识了

说白了,工厂模式就是使用一种手段,代替了 new 这个操作

以往想要获取一个实例的时候要 new 出来,但是这种方式耦合性就会很高,我们要尽量的减少这种可避免的耦合负担,所以工厂模式就来了

工厂就是在调用者和被调用者之间起一个连接枢纽的作用,调用者和被调用者都只与工厂进行联系,从而减少了两者之间直接的依赖

工厂模式一共有三种 ① 简单工厂模式,② 工厂方法模式 ③ 抽象工厂模式

下面我们一个一个来说

(一) 简单工厂模式

(1) 实现

下面我们以一个车的例子来讲,首先我们有一个抽象的 Car 类

publicabstractclassCar{// 任何汽车都会跑publicabstractvoidrun();}

接着就是它的子类,我们先来两个,一个宝马类,一个奔驰类(为阅读方便写成了拼音命名,请勿模仿,不建议)

publicclassBaoMaextendsCar{@Overridepublicvoidrun(){        System.out.println("【宝马】在路上跑");    }}

publicclassBenChiextendsCar{@Overridepublicvoidrun(){        System.out.println("【奔驰】在路上跑");    }}

那如果我想要实例化这个类,实际上最原始的写法可以这样(也就是直接 new 出来)

publicclassTest{publicstaticvoidmain(String[] args){        Car baoMa =newBaoMa();        baoMa.run();        Car benChi =newBenChi();        benChi.run();    }}

如果使用简单工厂模式,就需要创建一个专门的工厂类,用来实例化对象

publicclassCarFactory{publicstaticCarcreateCar(String type){if("宝马".equals(type)) {returnnewBaoMa();        }elseif("奔驰".equals(type)) {returnnewBenChi();        }else{returnnull;        }    }}

真正去调用的时候,我只需要传入一个正确的参数,通过 CarFactory 创建出想要的东西就可以了,具体怎么去创建就不需要调用者操心了

publicclassTest{publicstaticvoidmain(String[] args){Car baoMa = CarFactory.createCar("宝马");        baoMa.run();        Car benChi = CarFactory.createCar("奔驰");        benChi.run();    }}

(2) 优缺点

先说一下优点

简单工厂模式的优点就在于其工厂类中含有必要的逻辑判断(例如 CarFactory 中判断是宝马还是奔驰),客户端只需要通过传入参数(例如传入 “宝马”),动态的实例化想要的类,客户端就免去了直接创建产品的职责,去除了与具体产品的依赖(都不需要知道具体类名了,反正我不负责创建)

但是其缺点也很明显

简单工厂模式的工厂类职责过于繁重,违背了高聚合原则,同时其内容多的情况下,逻辑太复杂。最关键的是,当我想要增加一个新的内容的时候,例如增加一个保时捷,我就不得不去修改 CarFactory 工厂类中的代码,这很显然违背了 “开闭原则”

所以,工厂模式他就来了

(二) 工厂模式

(1) 实现

依旧是一个汽车抽象类,一个宝马类和一个奔驰类是其子类

publicabstractclassCar{// 任何汽车都会跑publicabstractvoidrun();}

publicclassBaoMaextendsCar{@Overridepublicvoidrun(){        System.out.println("【宝马】在路上跑");    }}

publicclassBenChiextendsCar{@Overridepublicvoidrun(){        System.out.println("【奔驰】在路上跑");    }}

如果是简单工厂类,就会 有一个总的工厂类来实例化对象,为了解决其缺点,工厂类首先需要创建一个汽车工厂接口类

publicinterfaceCarFactory{// 可以获取任何车CarcreateCar();}

然后宝马和奔驰类分别实现它,内容就是创建一个对应宝马或者奔驰(实例化宝马类或者奔驰类)

publicclassBaoMaFactoryimplementsCarFactory{@OverridepublicCarcreateCar(){returnnewBaoMa();    }}

publicclassBenChiFactoryimplementsCarFactory{@OverridepublicCarcreateCar(){returnnewBenChi();    }}

想要获取车的时候,只需要通过多态创建出想要获得的那种车的工厂,然后通过工厂再创建出对应的车,例如我分别拿到奔驰和宝马就可以这样做:

publicclassTest{publicstaticvoidmain(String[] args){// 先去奔驰工厂拿到一台奔驰CarFactory benChiFactory =newBenChiFactory();// 4S店拿到一台奔驰,给了你Car benChi = benChiFactory.createCar();        benChi.run();// 先去宝马工厂拿到一台宝马CarFactory baoMaFactory =newBaoMaFactory();// 4S店拿到一台宝马,给了你Car baoMa = baoMaFactory.createCar();        baoMa.run();    }}

这种情况下,如果我还想要增加一台保时捷类型的车,创建出对应的保时捷类(继承 Car)以及对应保时捷工厂类后后,仍只需要通过以上方法调用即可

// 先去保时捷工厂拿到一台保时捷CarFactory baoShiJieFactory =newBaoShiJieFactory();// 4S店拿到一台保时捷,给了你Car baoShiJie = baoShiJieFactory.createCar();baoShiJie.run();

(2) 定义

工厂方法模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂方法使一个类的实例化延迟到其子类

看其结构图

(3) 优缺点

优点:

对象的创建,被明确到了各个子工厂类中,不再需要在客户端中考虑

新内容增加非常方便,只需要增加一个想生成的类和创建其的工厂类

不违背 “开闭原则”,后期维护,扩展方便

缺点:

代码量显著增加

(三) 抽象工厂模式

抽象工厂模式是一种比较复杂的工厂模式,下面先直接通过代码了解一下

还是说车,我们将车分为两种,一种是普通轿车,一种是卡车,前面的工厂方法模式中,如果不断的增加车的类型,这势必会造成工厂过多,但是对于常见的车来说,还可以寻找可抽取的特点,来进行抽象

所以在此基础之上,我们又分别设定了自动挡和手动挡两种类型,所以两两搭配,就有四种情况了(eg:自动挡卡车,手动挡轿车等等)

(1) 创建抽象产品

首先分别创建普通轿车和卡车的抽象类,然后定义两个方法(这里我就写成一样的了,可以根据轿车和卡车的特点写不同的方法)

publicabstractclassCommonCar{// 所有车都能,停车abstractvoidparking();// 所有车都能,换挡abstractvoidshiftGear();}

publicabstractclassTruck{// 所有车都能,停车abstractvoidparking();// 所有车都能,换挡abstractvoidshiftGear();}

(2) 实现抽象产品

说明: A是自动的意思,H是手动的意思,eg:CommonCarA 代表普通自动挡轿车

实现抽象产品——小轿车(自动挡)

publicclassCommonCarAextendsCommonCar{@Overridevoidparking(){        System.out.println("自动挡轿车A,停车挂P档");    }@OverridevoidshiftGear(){        System.out.println("自动挡轿车A,可换挡 P N D R");    }}

实现抽象产品——小轿车(手动挡)

publicclassCommonCarHextendsCommonCar{@Overridevoidparking(){        System.out.println("手动挡轿车H,停车挂空挡,拉手刹");    }@OverridevoidshiftGear(){        System.out.println("手动挡轿车H,可换挡 空 1 2 3 4 5 R");    }}

实现抽象产品——货车(自动挡)

publicclassTruckAextendsTruck{@Overridevoidparking(){        System.out.println("自动挡货车A,停车挂P档");    }@OverridevoidshiftGear(){        System.out.println("自动挡货车A,可换挡 P N D R");    }}

实现抽象产品——货车(手动挡)

publicclassTruckHextendsTruck{@Overridevoidparking(){        System.out.println("手动档货车H,停车挂空挡,拉手刹");    }@OverridevoidshiftGear(){        System.out.println("手动档货车H,可换挡 空 1 2 3 4 5 R");    }}

(3) 创建抽象工厂

publicinterfaceCarFactory{// 创建普通轿车CommonCarcreateCommonCar();// 创建货车TruckcreateTruckCar();}

(4) 实现抽象工厂

通过自动挡手动挡这两个抽象概念,创建出这两个工厂,创建具有特定实现类的产品对象

自动挡汽车工厂类

publicclassAutomaticCarFactoryimplementsCarFactory{@OverridepublicCommonCarAcreateCommonCar(){returnnewCommonCarA();    }@OverridepublicTruckAcreateTruckCar(){returnnewTruckA();    }}

手动挡汽车工厂类

publicclassHandShiftCarFactoryimplementsCarFactory{@OverridepublicCommonCarHcreateCommonCar(){returnnewCommonCarH();    }@OverridepublicTruckHcreateTruckCar(){returnnewTruckH();    }}

(5) 测试一下

publicclassTest{publicstaticvoidmain(String[] args){// 自动挡车工厂类CarFactory automaticCarFactory =newAutomaticCarFactory();// 手动挡车工厂类CarFactory handShiftCarFactory =newHandShiftCarFactory();        System.out.println("=======自动挡轿车系列=======");        CommonCar commonCarA = automaticCarFactory.createCommonCar();        commonCarA.parking();        commonCarA.shiftGear();        System.out.println("=======自动挡货车系列=======");        Truck truckA = automaticCarFactory.createTruckCar();        truckA.parking();        truckA.shiftGear();        System.out.println("=======手动挡轿车系列=======");        CommonCar commonCarH = handShiftCarFactory.createCommonCar();        commonCarH.parking();        commonCarH.shiftGear();        System.out.println("=======手动挡货车系列=======");        Truck truckH = handShiftCarFactory.createTruckCar();        truckH.parking();        truckH.shiftGear();    }}

运行结果:

=======自动挡轿车系列=======自动挡轿车A,停车挂P档自动挡轿车A,可换挡PNDR=======自动挡货车系列=======自动挡货车A,停车挂P档自动挡货车A,可换挡PNDR=======手动挡轿车系列=======手动挡轿车H,停车挂空挡,拉手刹手动挡轿车H,可换挡空12345R=======手动挡货车系列=======手动档货车H,停车挂空挡,拉手刹手动档货车H,可换挡空12345R

补充两个概念

产品等级结构产品的等级结构就是其继承结构,例如上述代码中,CommonCar(普通轿车) 是一个抽象类,其子类有 CommonCarA (自动挡轿车)和 CommonCarH(手动挡轿车),则 普通轿车抽象类就与具体自动挡或者手动挡的轿车构成一个产品等级结构。

产品族产品族是同一个工厂生产,位于不同产品等级结构中的一组产品,例如上述代码中,CommonCarA(自动挡轿车)和 TruckA(自动挡货车),都是AutomaticCarFactory(自动挡汽车工厂)这个工厂生成的

(6) 结构图

看着结构图,我们再捋一下

首先 AbstractProductA 和 AbstractProductB 是两个抽象产品,分别对应我们上述代码中的 CommonCar 和 Truck,为什么是抽象的,因为它们可以都有两种不同的实现,即自动挡轿车和自动货车,手动挡轿车和手动挡卡车

ProductA1 和 ProductA2 和 ProductB1 和 ProductB2 就是具体的实现,代表 CommonCarA 和 CommonCarH 和 TruckA 和 TruckH

抽象工厂 AbstractFactory 里包含了所有产品创建的抽象方法,ConcreteFactory1 和 ConcreteFactory2 就是具体的工厂,通常是在运行时再创建一个 ConcreteFactory 的实例,这个工厂再创建具有特定实现的产品对象,也就是说为了创建不同的产品对象,客户端应该使用不同的具体工厂

(7) 反射+配置文件实现优化

抽象工厂说白了就是通过内容抽象的方式,减少了工厂的数量,同时在具体工厂我们可以这么用

CarFactory factory =newAutomaticCarFactory();

具体工厂只需要在初始化的时候出现一次,这也使得修改一个具体工厂也是比较容易的

但是缺点也是非常明显,当我想扩展一,比如加一个拖拉机类型,我就需要修改 CarFactory接口,AutomaticCarFactory 类 HandShiftCarFactory 类,(当然,拖拉机貌似没有什么自动挡,我只是为了举例子),还需要增加拖拉机对应的内容

也就是说,增加的基础上,我还需要修改原先的三个类,这是一个非常显著的缺点

除此之外还有一个问题,如果很多地方都声明了

CarFactory factory = new AutomaticCarFactory();

并且进行了调用,如果我更换了这个工厂,就需要大量的进行修改,很显然这一点是有问题的,我们下面来使用反射优化一下

publicclassTest{publicstaticvoidmain(String[] args)throwsException{        Properties properties =newProperties();// 使用ClassLoader加载properties配置文件生成对应的输入流InputStream in = Test.class.getClassLoader().getResourceAsStream("config.properties");// 使用properties对象加载输入流properties.load(in);//获取key对应的value值String factory = properties.getProperty("factory");        CarFactory automaticCarFactory = (CarFactory) Class.forName(factory).newInstance();        System.out.println("======轿车系列=======");        CommonCar commonCarA = automaticCarFactory.createCommonCar();        commonCarA.parking();        commonCarA.shiftGear();        System.out.println("=======货车系列=======");        Truck truckA = automaticCarFactory.createTruckCar();        truckA.parking();        truckA.shiftGear();    }}

config.properties

factory=cn.ideal.factory.abstractFactory.AutomaticCarFactory#factory=cn.ideal.factory.abstractFactory.HandShiftCarFactory

运行结果:

=======轿车系列=======自动挡轿车A,停车挂P档自动挡轿车A,可换挡 P N D R=======货车系列=======自动挡货车A,停车挂P档自动挡货车A,可换挡 P N D R

亚马逊测评 www.yisuping.com

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