设计模式系列2--三大工厂模式

image

今天学习下最多见的工厂模式,工厂模式细分下来有三大类:javascript

1. 简单工厂
 2. 工厂模式
 3. 抽象工厂模式复制代码

他们的目标都是同样的:封装对象的建立。可是实现手段和使用场景倒是不相同。使用的时候三个模式也能够互相替换使用,致使很容易混淆三者。java

下面咱们来具体看看三者的使用。sql


简单工厂模式

准确的说简单工厂不是一个模式,而是一种编程习惯。可是平时使用的很是多,咱们就把他归到模式一类了。数据库

一、定义

提供一个建立对象实例的功能,而无需关心具体实现。被建立的类型可使接口、抽象类、具体类。编程

二、UML结构图及说明

image

obstractClass:能够实现为抽象类或者具体接口,看实际须要选择,定义具体类须要实现的功能
concreteClass:实现抽象类所定义功能的具体类,可能会有多个
simpleFactory:简单工厂,选择合适的具体类来建立对象返回
client:经过simplefactory来获取具体的对象设计模式

若是对UML图不了解,能够先看看这篇文章:UML类图几种关系的总结app

三、实际场景运用

3.一、需求

假设咱们要实现一个电脑组装的功能,组装电脑很重要的一个地方就是根据客户指定的cpu类型来安装。假设咱们有三种类型的cpu供客户选择:apple,intel,AMD。sqlserver

3.二、普通实现

在客户端加入以下方法:学习

client.m文件
=====================

#import "simpleFactory.h"
#import "interCpu.h"
#import "appleCpu.h"
#import "AMDCpU.h"

@implementation client

-(Cpu *)selectCpuWithType:(NSString *)type{
    Cpu *cpu = nil;
    if ([type isEqualToString:@"intel"]) {
        cpu = [interCpu new];

    }else if([type isEqualToString:@"AMD"]){
        cpu = [AMDCpU new];

    }else{
        cpu = [appleCpu new];

    }
    return  cpu;
}

@end复制代码

好比像使用inter类型的cpu,只须要以下代码:ui

[self selectCpuWithType@"interCpu"];复制代码

这里我只是展示了核心代码,忽略了其余代码。你须要建立一个CPU的父类,而后建立三个子类继承它,分别是interCpu、AMDCpu、appleCpu。

上面的代码能够完成功能,根据客户传入的type类型来建立相应的cpu具体对象。

3.三、问题

虽然上述代码能够完成功能,可是有以下问题:

一、若是要加入其余cpu类型,或者更改cpu类型,那么必须修改客户端代码。违反了开闭原则(不了解的童鞋能够去看设计模式开篇漫谈

二、客户端知道全部的具体cpu类,耦合度过高。客户端必须知道全部具体的cpu类,那么任何一个类的改动均可能会影响到客户端。

3.四、解决问题

客户端必须了解全部的具体cpu类才能建立对象,可是这会致使上述一系列问题。那么解决办法就是把这些对象的建立封装起来,对客户端不可见,那么以后如何改动具体类都不会影响到客户端。这能够经过简单工厂来实现。

下面咱们来看看使用简单工厂重写后的代码

引入简单工厂类:

simpleFactory.h文件

=======================

#import <Foundation/Foundation.h>
#import "Cpu.h"

@interface simpleFactory : NSObject
-(Cpu *)selectCpuWithType:(NSString *)type;

@end


~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

simpleFactory.m文件

=======================


#import "simpleFactory.h"
#import "interCpu.h"
#import "appleCpu.h"
#import "AMDCpU.h"

@implementation simpleFactory

-(Cpu *)selectCpuWithType:(NSString *)type{
    Cpu *cpu = nil;
    if ([type isEqualToString:@"intel"]) {
        cpu = [interCpu new];

    }else if([type isEqualToString:@"AMD"]){
        cpu = [AMDCpU new];

    }else{
        cpu = [appleCpu new];

    }
    return  cpu;
}

@end复制代码

客户端调用代码:

#import <Foundation/Foundation.h>
#import "simpleFactory.h"
#import "Cpu.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        simpleFactory *factory = [simpleFactory new];
        Cpu *cpu = [factory selectCpuWithType:@"interCpu"];
        [cpu installCpu];
    }
    return 0;
}复制代码

此时无论是增长仍是减小或者修改cpu类型,客户端代码都不用改动,下降了客户端和具体cpu类的耦合,也遵循了开闭原则

四、反思

细心的一点的童鞋可能发现,你这不是逗我吗,仅仅是把原本客户端的代码移到了简单工厂类而已,有什么改变吗?

理解这个问题的关键在于理解简单工厂所在的位置。

前面咱们把建立具体cpu对象的代码放在客户端,致使一系列问题。咱们的目标就是让客户端从建立具体对象中解耦出来,让客户端不知道对象建立的具体过程。而简单工厂就是和具体对象封装在一块儿,算是一个封装体内,因此简单工厂知道具体的实现类是没有关系的。如今客户端只要知道简单工厂和一个抽象类cpu,就能够建立具体对象了,实现了解耦。

五、改进

虽然上面使用简单工厂后,让客户端实现了解耦,可是若是实现类改变了,咱们仍是须要需改简单工厂。有没有什么办法作到即便实现类改变也不须要改变简单工厂的代码呢?

在java中可使用反射或者IoC/DI来实现,在iOS种咱们有更简单的方法,一个方法足矣,具体见代码

-(Cpu *)selectCpuWithType:(NSString *)type{
    Cpu *cpu = (Cpu *)[NSClassFromString(type)new];
    if ([cpu isKindOfClass:[Cpu class]] && cpu) {
        return  cpu;
    }else{
        return nil;
    }
}复制代码

客户端代码不须要改动,是否是简单了不少?

六、简单工厂优缺点

  • 优势

    1. 帮助封装

      简单工厂虽然简单,可是帮咱们实现了封装对象建立的过程,让咱们能够实现面向接口编程。

    2. 解耦

      客户端不须要知道具体实现类,也不须要知道建立过程。只须要知道简单工厂类就能够建立具体对象,实现了解耦

  • 缺点

    1.增长客户端复杂度

    若是是经过参数来选择建立具体的对象,那么客户端就必须知道每一个参数的含义,也就暴露了内部实现

    2.不方便扩展

    若是实现类改变,那么仍是须要修改简单工厂,能够经过文中的方法来避免这个问题。或者使用下节咱们讲的工厂方法来解决

七、简单工厂本质

简单工厂的本质:选择实现

简单的工厂的本质在于选择,而不是实现,实现是由具体类完成的,不要在简单工厂完成。简单工厂的目的是让客户端经过本身这个中介者来选择具体的实现,从而让客户端和具体实现解耦,任何实现方面的变化都被简单工厂屏蔽,客户端不会知道。

简单工厂的实现难点在于如何“选择实现”,前面讲到的是静态传递参数。其实还能够在运行过程当中从内存或者数据库动态选择参数来实现,具体代码就不演示了,只是读取参数的方式不一样,其余都同样。

八、什么时候使用简单工厂

  1. 想彻底封装隔离具体实现

让外部只能经过抽象类或者接口来操做,上面的例子中,就是只能操做抽象类cpu,而不能操做具体类。此时可使用简单工厂,让客户端经过简单工厂来选择建立具体的类,不须要建立的具体过程。

  1. 想把建立对象的职责集中管理起来

一个简单工厂能够建立许多相关或者不相关的对象,因此能够把对象的建立集中到简单工厂来集中管理。

完整代码见文末。


工厂模式

一、问题

让咱们回到最原始的代码:

client.m文件
=====================

#import "simpleFactory.h"
#import "interCpu1179.h"
#import "appleCpu1179.h"
#import "AMDCpU1179.h"

@implementation client

-(Cpu *)selectCpuWithType:(NSString *)type{
    Cpu *cpu = nil;
    if ([type isEqualToString:@"intel1179"]) {
        cpu = [interCpu1179 new];

    }else if([type isEqualToString:@"intel753"]){
        cpu = [interCpu753 new];

    }else if([type isEqualToString:@"AMD1179"]){
        cpu = [AMDCpU1179 new];

    }else if([type isEqualToString:@"AMD753"]){
        cpu = [AMDCpu753 new];

    }else if([type isEqualToString:@"apple1179"]){
        cpu = [appleCpu1179 new];

    }else if([type isEqualToString:@"apple753"]){
        cpu = [appleCpu753 new];

    }else{
        return nil;
    }return  cpu;
}

@end复制代码

仔细看这段代码,就会发现一个问题:依赖于具体类。由于必须在这里完成对象建立,因此不得不依赖于具体类:interCpu、appleCpu、AMDCpu。

这会致使什么问题呢?简单来讲就是违反了依赖倒置原则,让高层组件client依赖于底层组件cpu。违反这个原则的后果就是一旦底层组件改动,那么高层组件也就必须改动,违反了开闭原则。联系到上面的这个例子就是若是增长或者修改一个cpu子类,那么就必须改动上面的代码,即便使用了简单工厂模式,仍是要修改简单工厂的代码。

咱们先来看看什么是依赖致使原则:

定义:

要依赖抽象,不要依赖具体

展开来讲就是:不能让高层组件依赖低层组件,并且无论高层仍是低层组件,都应该依赖于抽象。

那么如何才能避免违反这一原则呢?下面有三条建议能够参考下:

  • 变量不能够持有具体类的引用,好比new一个对象
  • 不要让类派生自具体类,否则就会依赖于具体类,最好派生自抽象类
  • 不要覆盖基类中已经实现的方法,若是覆盖了基类方法,就说明该类不适合作基类,基类方法应该是被子类共享而不是覆盖。

可是要彻底遵照上面三条,那就无法写代码了。因此合适变通才是,而工厂模式就是为了遵循依赖倒置原则而生的。

下面就来看看使用工厂模式如何解决这个问题。


二、定义

定义了一个建立对象的接口,由子类决定实例化哪个类,让类的实例化延迟到子类执行。

三、UML结构图及说明

image

先记住工厂模式实现了依赖倒置原则,至于如何实现的,暂且按下不表,咱们先来看代码

四、实际场景运用

仍是和简单工厂的一样的需求,可是咱们根据cpu的针脚个数增长了cpu的分类,好比intelCpu117九、intelCpu753。另外两个类型的cpu也是如此,分为1179和753两个类型的cpu。可是此次咱们用工厂模式来实现。

定义一个工厂基类,定义一个工厂方法

#import <Foundation/Foundation.h>
#import "Cpu.h"

@interface factory : NSObject
-(Cpu*)createCpuWithType:(NSInteger)type;

@end


=============================
#import "factory.h"

@implementation factory
-(Cpu *)createCpuWithType:(NSInteger)type{
    @throw ([NSException exceptionWithName:@"继承错误" reason:@"子类必须重写该方法" userInfo:nil]);
    return nil;
}
@end复制代码

下面是具体工厂,继承自工厂基类,实现工厂方法来建立具体的cpu对象

#import <Foundation/Foundation.h>
#import "factory.h"

@interface intelFactory : factory

@end

===========================

#import "intelFactory.h"
#import "interCpu753.h"
#import "interCpu1179.h"
#import "Cpu.h"

@implementation intelFactory
-(Cpu *)createCpuWithType:(NSInteger)type{
    Cpu *cpu = nil;
    if (type == 753) {
        cpu = [interCpu753 new];
    }else{
        cpu = [interCpu1179 new];
    }
    return cpu;
}
@end复制代码

上面演示的是intelCpu工厂,另外的AMD和apple的cpu具体工厂类相似,就不贴代码了。

客户端调用:

#import <Foundation/Foundation.h>
#import "factory.h"
#import "Cpu.h"
#import "intelFactory.h"
#import "appleFactory.h"
#import "AMDFactory.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        factory *factory = nil;
        factory = [intelFactory new];
        Cpu *cpu1 = [factory createCpuWithType:753];
        [cpu1 installCpu];
        Cpu *cpu2 = [factory createCpuWithType:1179];
        [cpu2 installCpu];

        factory = [AMDFactory new];
        Cpu *cpu3 = [factory createCpuWithType:753];
        [cpu3 installCpu];
        Cpu *cpu4 = [factory createCpuWithType:1179];
        [cpu4 installCpu];


    }
    return 0;
}复制代码

若是此时又多了一个cpu类型,好比高通的cpu,那么只须要新建一个高通cpu的工厂类,继承自factory类,而后实现工厂方法,就能够了。客户端也能够根据本身的须要选择使用哪一个工厂,不用修改原有代码。符合开闭原则:对修改关闭,对扩展开放。

五、如何遵循依赖倒置原则

咱们先来看看没有使用工厂方法,各个类之间的依赖关系

image

能够看到高层组件client依赖于具体的低层组件cpu类,违反了依赖倒置原则。通常咱们把功能的使用者归到高层组件,把功能的提供者归到低层组件。

再来看看使用工厂方法后各个类之间的依赖关系

image

能够看到高层组件client依赖于抽象类cpu,低层组件也就是各类cpu具体类也依赖于抽象类factory,符合依赖倒置原则。其实说白了,就是要针对接口编程,而不是针对实现编程

那么倒置在哪里呢?

对比两个图,就会发现具体cpu类的箭头从原来向下变成了向上,也就是说依赖关系发生了倒置。咱们来看看为何会这样。

第一个图里面,由于咱们直接在client里面去初始化各个cpu类,倒置client就必须依赖这些具体类,依赖关系向下。

第二个图里面,每一个cpu具体类,都继承自抽象cpu类,而且实现了抽象cpu的方法installCpu,此时具体cpu类就依赖于抽象cpu类,依赖关系向上。

如今明白为何叫作依赖倒置了吧?这一切都是工厂方法的功劳。

有人要说,这个用简单工厂也能够实现的呀。是的没错,简单工厂也能实现,其实若是直接在工厂方法的抽象cpu类里面实现对象的建立,那么此时工厂模式就是简单工厂。可是工厂模式有一个简单工厂模式没有的功能:遵循开闭原则。若是此时要增长或者修改一个cpu具体类,那么简单工厂的代码就必须修改,而工厂方法只须要扩展就好了,不用修改原有代码。

六、工厂模式优缺点

  • 优势

    1. 能够在不知道具体实现的状况下编程

      工厂模式可让你在实现功能时候,不须要关心具体对象,只须要使用对象的抽象接口便可,上面例子中client使用的就是cpu抽 象类,而不是具体的cpu类。

    2. 更容易扩展新版本

      若是须要加入新的实现,只须要扩展一个新类,而后继承抽象接口实现工厂方法便可。遵循了开闭原则。

  • 缺点

    具体产品和工厂方法耦合,由于在工厂方法中须要建立具体实例,因此它们会耦合

七、什么时候使用工厂模式

经过工厂模式定义咱们知道,工厂模式主要是把对象的建立延迟到子类执行。如何实现的呢?

拿上面的例子来讲,当咱们调用抽象类factory的方法createCpuWithType的时候,真正执行的是factory的子类,好比intelFactory。作到这点是面向对象语言的基本特征之一:多态,它能够实现父类的同一个方法在不一样的子类中有不一样的表现。

了解了工厂模式的本质,咱们就知道在上面状况下可使用它了

  • 一个类不想知道它所须要建立的对象所属的类,好比client不须要知道intelCpu1179这个具体类
  • 一个类但愿由他的子类来指定它所建立的对象,好比factory但愿IntelFactory建立具体cpu对象

抽象工厂

一、业务场景

假设咱们写了一套系统,底层使用了两套数据库:sqlserver和access数据库。可是针对业务逻辑的代码不可能写两套,这样很是麻烦,也不方便扩展新的数据库。咱们须要提供一个统一的接口给业务层操做,切换数据库也不须要修改业务层逻辑。

简化下需求,假设咱们每一个数据库都有user和department两张表,业务逻辑代码以下:

//业务逻辑
        [user insert:@"张三"];
        [user getUser];
        [deparment insert:@"财务"];
        [deparment getDepartment];复制代码

下面咱们就来看看如何使用抽象工厂来实现这个需求

二、需求实现

2.一、建立抽象工厂接口

咱们先建立一个抽象接口,在iOS里面咱们使用协议实现。

IFactory.h文件
========================
@class IUser;
@class IDepartment;

@protocol IFactory <NSObject>
@required
-(IUser*)createUser;
-(IDepartment *)createDepartment;

@end复制代码

2.二、建立具体工厂

下面咱们来建立两个具体的工厂,分别针对两个数据库,实现抽象工厂的方法,来建立具体的表对象

#import <Foundation/Foundation.h>
#import "IFactory.h"
#import "IUser.h"

@interface SqlServerFactory : NSObject<IFactory>

@end

======================

#import "SqlServerFactory.h"
#import "SqlServerUser.h"
#import "SqlServerDepartment.h"

@implementation SqlServerFactory

-(IUser *)createUser{
    return [SqlServerUser new];
}

-(IDepartment *)createDepartment{
    return [SqlServerDepartment new];
}
@end复制代码

AccessFactory类建立方法相似。

2.三、建立产品

如今咱们须要建立具体工厂须要的产品,这里是两张表:user和department。可是这两张表有分为两个体系,sqlserver的user和department表,access的user和department表。

咱们把user表抽象为基类,下面分别实现sqlserver和access的子类user表。department表同理,再也不贴代码了。

抽象产品类

#import <Foundation/Foundation.h>

@interface IUser : NSObject
-(void)insert:(NSString *)user;
-(void)getUser;
@end

=======================
#import "IUser.h"

@implementation IUser
-(void)insert:(NSString *)user{
    @throw ([NSException exceptionWithName:@"继承错误" reason:@"子类没有实现父类方法" userInfo:nil]);
}

-(void)getUser{
    @throw ([NSException exceptionWithName:@"继承错误" reason:@"子类没有实现父类方法" userInfo:nil]);
}
@end复制代码

具体产品类

#import <Foundation/Foundation.h>
#import "IUser.h"

@interface SqlServerUser : IUser

@end

==================


#import "SqlServerUser.h"

@implementation SqlServerUser
-(void)insert:(NSString *)user{
    NSLog(@"向sqlserver数据库插入用户:%@", user);
}

-(void)getUser{
    NSLog(@"从sqlserver数据库获取到一条用户数据");
}
@end复制代码
#import <Foundation/Foundation.h>
#import "IUser.h"

@interface AccessUser : IUser

@end

=========================

#import "AccessUser.h"

@implementation AccessUser
-(void)insert:(NSString *)user{
    NSLog(@"向access数据库插入用户:%@", user);
}

-(void)getUser{
    NSLog(@"从access数据库获取到一条用户数据");
}

@end复制代码

2.四、客户端调用

#import <Foundation/Foundation.h>
#import "IFactory.h"
#import "IUser.h"
#import "IDepartment.h"
#import "SqlServerFactory.h"
#import "AccessFactory.h"


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        id<IFactory> DBFactory = [AccessFactory new];
        IUser *user = [DBFactory createUser];
        IDepartment *deparment = [DBFactory createDepartment];

        //业务逻辑
        [user insert:@"张三"];
        [user getUser];
        [deparment insert:@"财务"];
        [deparment getDepartment];

    }
    return 0;
}复制代码

输出:

2016-11-22 17:38:30.667 抽象工厂模式[56330:792839] 向access数据库插入用户:张三
2016-11-22 17:38:30.668 抽象工厂模式[56330:792839] 从access数据库获取到一条用户数据
2016-11-22 17:38:30.668 抽象工厂模式[56330:792839] 向access数据库插入部门:财务
2016-11-22 17:38:30.668 抽象工厂模式[56330:792839] 从access数据库获取到一条部门数据复制代码

此时若是须要切换到sqlserver数据库,只须要更改以下代码

id<IFactory> DBFactory = [AccessFactory new];
改成:
id<IFactory> DBFactory = [SqlServerFactory new];复制代码

可是抽象工厂有个缺点:你想下,若是此时我想增长一张工资表,那么就必须修改抽象工厂接口类IFactory和每一个具体工厂类SqlServerFactory、AccessFactory,违反了开闭原则。可是整体来瑕不掩瑜。

三、 实现原理分析

经过上面的例子,我想你们已经认识到抽象工厂的优雅之处,那么它是如何完成的呢?

咱们来把上面的例子作成UML图,这样看的更加清晰。

image

能够看到咱们建立了两个具体工厂,分别是sqlserverFactory和AccessFactory。咱们的产品有两个user和department,每一个产品也分为两个体系:sqlserver的access的。

若是选择sqlserverFactory,那么对应的两个工厂方法就生成sqlserver的user和department表。选择accessFactory也是如此。

因此咱们能够很方便在两个数据库之间切换,而不影响业务逻辑,由于业务逻辑都是面向抽象编程。再看下业务逻辑的代码

id<IFactory> DBFactory = [AccessFactory new];
        IUser *user = [DBFactory createUser];
        IDepartment *deparment = [DBFactory createDepartment];

        //业务逻辑
        [user insert:@"张三"];
        [user getUser];
        [deparment insert:@"财务"];
        [deparment getDepartment];复制代码

能够看到业务逻辑都是针对抽象类IUesr和IDepartment编程,因此他们的子类如何变化,不会影响到业务逻辑。

###四、 抽象工厂定义

提供一个建立一系列相关或者相互依赖的接口,而无需依赖具体类。

好好分析这句话,关键的地方就是:一系列相关或者相互依赖的接口。这决定了咱们使用抽象工厂的初衷,抽象工厂定义了一系列接口,这些接口必须是相互依赖或者相关的,而不是把一堆没有什么关联的接口放到一块儿。

回头看看咱们上面的抽象工厂类IFactory定义的接口,是用来建立两张表,这两张表是属于同一个数据库的,他们之间是相互关联和依赖的。

后面一句“无需依赖具体类”是怎么作到的呢?

能够看到抽象工厂类只是定义了接口,而真正去实现这些接口产生具体对象的是具体工厂。客户端面向的也是抽象工厂类编程,因此无需依赖具体类。

咱们能够把抽象工厂的定义的方法看作工厂方法,而后具体工厂去实现这些工厂方法,这不就是工厂模式吗?
因此说抽象工厂包含了具体工厂。

五、思考

工厂模式和抽象工厂模式最大的区别在于,后者的一系列工厂方法是相互依赖或者相关的,而工厂模式虽然也能够定义一些列工厂方法,可是他们之间是没有关联的。这是区分他们的重要依据。

其实若是抽象工厂里面只定义一个工厂方法,也就是只实现一个产品,那么久退换为工厂方法了。

记住:

工厂模式建立一种类型的产品,抽象工厂建立一些列相关的产品家族。

六、什么时候使用抽象工厂

  • 客户端只但愿知道抽象接口,而不关心具体产品的实现的时候
  • 一个系统须要有多个产品系列中的一个来配置的时候。也就是说能够动态切换产品系列,好比上面的切换两个数据库
  • 须要强调一系列产品的接口有关联的时候,以便联合使用它们。

三个模式对比

  • 抽象工厂模式和工厂模式

    工厂模式针对单独产品的建立,而抽象工厂注重一个产品系列的建立。若是产品系列只有一个产品的 话,那么抽象工厂就退换到工厂模式了。在抽象工厂中使用工厂方法来提供具体实现,这个时候他们联 合使用。

  • 工厂模式和简单工厂

    二者很是相似,都是用来作选择实现的。不一样的地方在于简单工厂在自身就作了选择实现。而工厂模式 则是把实现延迟到子类执行。若是把工厂方法的选择实现直接在父类实现,那么此时就退化为简单工厂 模式了。

  • 简单工厂和抽象工厂
    简单工厂用于作选择实现,每一个产品的实现之间没有依赖关系。而抽象工厂实现的一个产品系列,相互 之间有关联。这是他们的区别


Demo下载地址

简单工厂

工厂模式

抽象工厂模式

相关文章
相关标签/搜索