工厂模式——IOS版

一、前言

  1. 工厂模式种类:简单工厂、工厂方法、抽象工厂
  2. IOS中的应用:类簇

二、简单工厂

  1. 介绍:
    简单工厂模式是属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式,但不属于23种GOF设计模式之一。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现。

  2. 延伸:
    试想一下,当我们在codeing的时候,在A类里面只要NEW了一个B类的对象,那么A类就会从某种程度上依赖B类。如果在后期需求发生变化或者是维护的时候,需要修改B类的时候,我们就需要打开源代码修改所有与这个类有关的类了,做过重构的朋友都知道,这样的事情虽然无法完全避免,但确实是一件让人心碎的事情。

  3. 模拟场景:
    欧美主导的以赛车为主题的系列电影《速度与激情》系列相信大家都看过,里面的男主角范·迪塞尔在每一集里面做不同的事情都是开不同的车子,相信大家都觉得很酷吧。
    人家酷也没办法,谁叫人家是大佬呢。这里我们试想一下,如果这是一套程序,我们该怎么设计?每次不同的画面或者剧情范·迪塞尔都需要按照导演的安排开不一样的车,去参加赛车需要开的是跑车,可能导演就会说下一场戏:范·迪塞尔下一场戏需要开跑车(参数),要去参加五环首届跑车拉力赛,这时候场务(工厂类)接到导演的命令(跑车参数)后需要从车库开出一辆跑车(具体产品)交到范·迪塞尔手上让他去准备五环首届跑车拉力赛。这套程序的整个生命周期就算完成了。(什么?没完成?难不成你还真想来个五环首届跑车拉力赛了啊:)
    根据导演不同的指令,开的车是不一样的,但是车都是在车库中存在的。车都属于同一种抽象,车库里所有的车都有自己的特征,这些特征就是条件。导演发出指令的时候,只要告诉场务特征,场务就知道提什么车。这就简单工厂模式的典型案例。

  4. 代码演示:

///抽象产品类代码: 
@protocol Car <NSObject>
@required
- (void)car;
@end

///具体产品类代码:
/**
具体产品类: 跑车
 */
@interface SportCar : NSObject <Car>
@end
@implementation SportCar
- (void)car  {
    NSLog(@"场务把跑车交给范·迪塞尔");
}
@end

/**
具体产品类: 越野车
 */
@interface JeepCar : NSObject <Car>
@end
@implementation JeepCar
- (void)car {
    NSLog(@"场务把越野车交给范·迪塞尔");
}
@end

/** 
具体产品类: 两箱车
 */
@interface HatchbackCar : NSObject <Car>
@end
@implementation HatchbackCar
- (void)car {
    NSLog(@"场务把两箱车交给范·迪塞尔");
}
@end

///简单工厂核心代码:
@protocol Car;
typedef NS_ENUM(NSInteger, CarType) {
    SportCarType = 0,
    JeepCarType =1,
    HatchbackCarType =2
};
@interface SimpleFactory : NSObject
+ (id<Car>)carWithType:(CarType)carType;
@end
@implementation SimpleFactory
+ (id<Car>)carWithType:(CarType)carType {
    switch(carType) {
       case SportCarType:
           return [[SportCar alloc] init];
       case JeepCarType:
           return [[JeepCar alloc] init];
       case HatchbackCarType:
           return [[HatchbackCar alloc] init];
       default:
           return nil;
    }
}
@end

///客户端调用代码: 
NSLog(@"范·迪塞尔下一场戏开跑车。");
id<Car> car = [SimpleFactory carWithType:SportCarType];
[car car];
  1. 简单工厂UML类图:


    简单工厂UML类图
  2. 简单工厂的优点/缺点:
  • 简单工厂模式能够根据外界给定的信息,决定究竟应该创建哪个具体类的对象,打破了调用者和具体产品类之间的依赖。明确区分了各自的职责和权力,有利于整个软件体系结构的优化。
  • 很明显工厂类集中了所有实例的创建逻辑,违反了开闭原则。

三、工厂方法模式

  1. 介绍:
    工厂方法模式Factory Method,又称多态性工厂模式。在工厂方法模式中,核心的工厂类不再负责所有的产品的创建,而是将具体创建的工作交给子类去做。该核心类成为一个抽象工厂角色,仅负责给出具体工厂子类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节。

  2. 定义:
    工厂方法模式是简单工厂模式的衍生,解决了许多简单工厂模式的问题。首先完全实现‘开-闭 原则’,实现了可扩展。其次更复杂的层次结构,可以应用于产品结果复杂的场合。

  3. 延伸:
    在上面简单工厂的引入中,我们将实例化具体对象的工作全部交给了专门负责创建对象的工厂类(场务)中,这样就可以在我们得到导演的命令后创建对应的车(产品)类了。但是剧组的导演是性情比较古怪的,可能指令也是无限变化的。这样就有了新的问题,一旦导演发出的指令时我们没有预料到的,就必须得修改源代码。这也不是很合理的,工厂方法就是为了解决这类问题的。

  4. 模拟场景:
    还是上面范·迪塞尔要去参加五环首届跑车拉力赛的场景。因为要拍摄《速度与激情8》,导演组车的种类增多了,阵容也更加豪华了,加上导演古怪的性格可能每一场戏绝对需要试驾几十种车。如果车库没有的车(具体产品类)可以由场务(具体工厂类)直接去4S店取,这样没增加一种车(具体产品类)就要对应的有一个场务(具体工厂类),他们互相之间有着各自的职责,互不影响,这样可扩展性就变强了。

  5. 代码演示:

///工厂接口代码:
@protocol Car;
@protocol FactoryMethod <NSObject>
- (id<Car>)CrateCar;
@end
///产品接口代码:
@protocol Car <NSObject>
@required
- (void)car;
@end
///具体工厂代码:
@interface SportFactory : NSObject <FactoryMethod>
@end
@implementation SportFactory
- (id<Car>)CrateCar {
   return [[SportCar alloc] init];
}
@end

@interface JeepFactory : NSObject <FactoryMethod>
@end
@implementation JeepFactory
- (id<Car>)CrateCar {
   return [[JeepCar alloc] init];
}
@end

@interface HatchbackFactory : NSObject <FactoryMethod>
@end
@implementation HatchbackFactory
- (id<Car>)CrateCar {
   return [[HatchbackCar alloc] init];
}
@end

///具体产品代码:
/**
具体产品类: 跑车
 */
@interface SportCar : NSObject <Car>
@end
@implementation SportCar
- (void)car  {
    NSLog(@"场务把跑车交给范·迪塞尔");
}
@end

/**
具体产品类: 越野车
 */
@interface JeepCar : NSObject <Car>
@end
@implementation JeepCar
- (void)car {
    NSLog(@"场务把越野车交给范·迪塞尔");
}
@end

/** 
具体产品类: 两箱车
 */
@interface HatchbackCar : NSObject <Car>
@end
@implementation HatchbackCar
- (void)car {
    NSLog(@"场务把两箱车交给范·迪塞尔");
}
@end

///客户端代码: 
///工厂方法
NSLog(@"范·迪塞尔下一场戏开越野车。");
id<Car> jeepCar = [JeepFactory CrateCar];
[jeepCar car];
   
NSLog(@"范·迪塞尔下一场戏开跑车车。");
id<Car> sportCar = [SportFactory CrateCar];
[sportCar car];

NSLog(@"范·迪塞尔下一场戏开两箱车。");
id<Car> hatchbackCar = [HatchbackFactory CrateCar];
[hatchbackCar car];
  1. 工厂方法UML类图:


    工厂方法UML类图
  2. 工厂方法的优点/缺点:

  • 优点:
    • 子类提供挂钩。基类为工厂方法提供缺省实现,子类可以重写新的实现,也可以继承父类的实现。-- 加一层间接性,增加了灵活性
    • 屏蔽产品类。产品类的实现如何变化,调用者都不需要关心,只需关心产品的接口,只要接口保持不变,系统中的上层模块就不会发生变化。
    • 典型的解耦框架。高层模块只需要知道产品的抽象类,其他的实现类都不需要关心,符合迪米特法则,符合依赖倒置原则,符合里氏替换原则。
    • 多态性:客户代码可以做到与特定应用无关,适用于任何实体类。
  • 缺点:需要Creator和相应的子类作为factory method的载体,如果应用模型确实需要creator和子类存在,则很好;否则的话,需要增加一个类层次。

四、抽象工厂模式

  1. 介绍:
    抽象工厂模式是所有形态的工厂模式中最为抽象和最具一般性的一种形态。抽象工厂模式是指当有多个抽象角色时,使用的一种工厂模式。抽象工厂模式可以向客户端提供一个接口,使客户端在不必指定产品的具体的情况下,创建多个产品族中的产品对象。根据里氏替换原则,任何接受父类型的地方,都应当能够接受子类型。因此,实际上系统所需要的,仅仅是类型与这些抽象产品角色相同的一些实例,而不是这些抽象产品的实例。换言之,也就是这些抽象产品的具体子类的实例。工厂类负责创建抽象产品的具体子类的实例。

  2. 定义:
    为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类。

  3. 模拟场景:
    我们还是继续范·迪塞尔的例子,上面只讲到了道具组通过工厂获取到自己想要的比赛用车,并没有涉及赛车的生产过程,一辆赛车从原料到成品是要经历过复杂的工艺无数人的努力而成的,想必老范(范·迪塞尔名字太长,一下都简称老范)对这点也特别赞同吧,在五环首届跑车拉力赛中经常出现老范“作弊”的行为,他在比赛中对车改装而提高性能的场景总能让他在比赛中笑傲群雄。一辆车的性能如何和原料是有直接关系的,工厂需要控制原料的供应,不然赛车性能无法得到最优,不同车型使用的原料也不尽相同。

  4. 场景分析:
    一辆赛车由发送机、底盘、车身和电器设备组成,工厂在生产时要对不同车型使用不同的原料,这样才能满足老范对赛车性能的要求。那么生产跑车、越野、两箱车使用的原料各异,跑车的原料来自意大利、越野车的原料来自中国、两箱车的原料来自泰国。

  5. 代码演示:

///汽车模板
@protocol Car <NSObject>
- (void)run;
- (void)stop;
- (void)carConfiguration;
@end

/**
 具体产品类: 跑车
 */
@interface SportCar : NSObject <Car>
@property (nonatomic, strong) Engine *engine;
@property (nonatomic, strong) Chassis *chassis;
@property (nonatomic, strong) Body *body;
@property (nonatomic, strong) Equipment *equipment;
- (instancetype)initWithEngine:(Engine *)engine chassis:(Chassis *)chassis body:(Body *)body equipment:(Equipment *)equipment;
@end
@implementation SportCar
- (instancetype)initWithEngine:(Engine *)engine chassis:(Chassis *)chassis body:(Body *)body equipment:(Equipment *)equipment
{
    if (self = [super init]) {
        _engine = engine;
        _chassis = chassis;
        _body = body;
        _equipment = equipment;
    }
    return self;
}

- (void)run {
    NSLog(@"跑车启动!");
}

- (void)stop {
    NSLog(@"跑车停车!");
}

- (void)carConfiguration {
    NSLog(@"跑车【Engine:%@, Chassis:%@, Body:%@, Equipment:%@】", self.engine, self.chassis, self.body, self.equipment);
}
@end

/**
 具体产品类: 越野车
 */
@interface JeepCar : NSObject <Car>
@property (nonatomic, strong) Engine *engine;
@property (nonatomic, strong) Chassis *chassis;
@property (nonatomic, strong) Body *body;
@property (nonatomic, strong) Equipment *equipment;
- (instancetype)initWithEngine:(Engine *)engine chassis:(Chassis *)chassis body:(Body *)body equipment:(Equipment *)equipment;
@end
@implementation JeepCar
- (instancetype)initWithEngine:(Engine *)engine chassis:(Chassis *)chassis body:(Body *)body equipment:(Equipment *)equipment
{
    if (self = [super init]) {
        _engine = engine;
        _chassis = chassis;
        _body = body;
        _equipment = equipment;
    }
    return self;
}

- (void)run {
    NSLog(@"越野车启动!");
}

- (void)stop {
    NSLog(@"越野车停车!");
}

- (void)carConfiguration {
    NSLog(@"越野车【Engine:%@, Chassis:%@, Body:%@, Equipment:%@】", self.engine, self.chassis, self.body, self.equipment);
}
@end

/**
 具体产品类: 两箱车
 */
@interface HatchbackCar : NSObject <Car>
@property (nonatomic, strong) Engine *engine;
@property (nonatomic, strong) Chassis *chassis;
@property (nonatomic, strong) Body *body;
@property (nonatomic, strong) Equipment *equipment;
- (instancetype)initWithEngine:(Engine *)engine chassis:(Chassis *)chassis body:(Body *)body equipment:(Equipment *)equipment;
@end
@implementation HatchbackCar
- (instancetype)initWithEngine:(Engine *)engine chassis:(Chassis *)chassis body:(Body *)body equipment:(Equipment *)equipment
{
    if (self = [super init]) {
        _engine = engine;
        _chassis = chassis;
        _body = body;
        _equipment = equipment;
    }
    return self;
}

- (void)run {
    NSLog(@"两厢车启动!");
}

- (void)stop {
    NSLog(@"两厢车停车!");
}

- (void)carConfiguration {
    NSLog(@"两箱车【Engine:%@, Chassis:%@, Body:%@, Equipment:%@】", self.engine, self.chassis, self.body, self.equipment);
}
@end

///汽车耗材模板
@protocol CarMaterialFactory <NSObject>
- (Engine *)getEngine;
- (Chassis *)getChassis;
- (Body *)getBody;
- (Equipment *)getEquipment;
@end

@interface SportCarMeterialFactory : NSObject <CarMaterialFactory>
@end
@implementation SportCarMeterialFactory

- (Engine *)getEngine {
    return [[SportEngine alloc] init];
}

- (Chassis *)getChassis {
    return [[SportChassis alloc] init];
}

- (Body *)getBody {
    return [[SportBody alloc] init];
}

- (Equipment *)getEquipment {
    return [[SportEquipment alloc] init];
}
@end

@interface JeepCarMeterialFactory : NSObject <CarMaterialFactory>
@end
@implementation JeepCarMeterialFactory

- (Engine *)getEngine {
    return [[JeepEngine alloc] init];
}

- (Chassis *)getChassis {
    return [[JeepChassis alloc] init];
}

- (Body *)getBody {
    return [[JeepBody alloc] init];
}

- (Equipment *)getEquipment {
    return [[JeepEquipment alloc] init];
}
@end

@interface HatchbackCarMeterialFactory : NSObject <CarMaterialFactory>
@end

@implementation HatchbackCarMeterialFactory
- (Engine *)getEngine {
    return [[HatchbackEngine alloc] init];
}

- (Chassis *)getChassis {
    return [[HatchbackChassis alloc] init];
}

- (Body *)getBody {
    return [[HatchbackBody alloc] init];
}

- (Equipment *)getEquipment {
    return [[HatchbackEquipment alloc] init];
}
@end

///汽车工厂
@protocol FactoryMethod <NSObject>

+ (id<Car>)CrateCar;
@end

@interface SportFactory : NSObject <FactoryMethod>
@end
@implementation SportFactory
+ (id<Car>)CrateCar {
    id<CarMaterialFactory> sportCarMaterialFactory = [[SportCarMeterialFactory alloc] init];
    return [[SportCar alloc] initWithEngine:sportCarMaterialFactory.getEngine chassis:sportCarMaterialFactory.getChassis body:sportCarMaterialFactory.getBody equipment:sportCarMaterialFactory.getEquipment];
}
@end

@interface JeepFactory : NSObject <FactoryMethod>
@end
@implementation JeepFactory

+ (id<Car>)CrateCar {
    id<CarMaterialFactory> jeepCarMaterialFactory = [[JeepCarMeterialFactory alloc] init];
    return [[JeepCar alloc] initWithEngine:jeepCarMaterialFactory.getEngine chassis:jeepCarMaterialFactory.getChassis body:jeepCarMaterialFactory.getBody equipment:jeepCarMaterialFactory.getEquipment];
}
@end

@interface HatchbackFactory : NSObject <FactoryMethod>
@end
@implementation HatchbackFactory

+ (id<Car>)CrateCar {
    id<CarMaterialFactory> hatchbackCarMaterialFactory = [[HatchbackCarMeterialFactory alloc] init];
    return [[HatchbackCar alloc] initWithEngine:hatchbackCarMaterialFactory.getEngine chassis:hatchbackCarMaterialFactory.getChassis body:hatchbackCarMaterialFactory.getBody equipment:hatchbackCarMaterialFactory.getEquipment];
}
@end

///客户端代码: 
///工厂方法
NSLog(@"范·迪塞尔下一场戏开越野车。");
id<Car> jeepCar = [JeepFactory CrateCar];
[jeepCar car];
   
NSLog(@"范·迪塞尔下一场戏开跑车车。");
id<Car> sportCar = [SportFactory CrateCar];
[sportCar car];

NSLog(@"范·迪塞尔下一场戏开两箱车。");
id<Car> hatchbackCar = [HatchbackFactory CrateCar];
[hatchbackCar car];
  1. 抽象工厂UML类图:


    抽象工厂UML类图

抽象工厂模式符合了六大原则中的开闭原则、里氏代换原则、依赖倒转原则

  1. 抽象工厂的优点/缺点:
    • 优点:
      • 抽象工厂模式隔离了具体类的生产,使得客户并不需要知道什么被创建。
      • 当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
      • 增加新的具体工厂和产品族很方便,无须修改已有系统,符合“开闭原则”。
    • 缺点:增加新的产品等级结构很复杂,需要修改抽象工厂和所有的具体工厂类,对“开闭原则”的支持呈现倾斜性。(不过说这个缺点好像有点吹毛求疵了)

五、OC中的类簇

  1. 基于类簇封装车家族
    在上面的范·迪塞尔例子中只提供了三种车型,即跑车、越野和两厢车。我们知道今天的车型种类繁多,还有MINI、SUV、卡车、公交车等等。某些时候我们并不关注我们开的是什么车,只想用它来帮助我们完成任务,用卡车拉货,用公交载客,如果我们要记住所有这些车型那需要和庞大的家族打交道。可以借助OC的类簇设计思想设计我们队车家族的封装。
@protocol Car <NSObject>
- (void)run;
- (void)stop;
- (void)carConfiguration;
@end

@interface Car : NSObject <Car>
+ (instancetype)carForSport;
+ (instancetype)carForCrosscountry;
+ (instancetype)carForTransport;
@end
@implementation Car
+ (instancetype)carForSport {
    return [SportFactory CrateCar];
}

+ (instancetype)carForCrosscountry {
    return [JeepFactory CrateCar];
}

+ (instancetype)carForTransport {
    return [HatchbackFactory CrateCar];
}

- (void)run {
    ///子类实现
}

- (void)stop {
    ///子类实现
}

- (void)carConfiguration {
    ///子类实现
}
@end
  1. OC中的类簇NSNumber
    我们都知道在iOS中类簇的使用是非常普遍的,比如 NSNumber 、 NSString 、 NSArray 等等都是类簇。我们以 NSNumber 举例来说, 我们都知道NSNumber可以存储多种类型的数字,如Int/Float/Double等等,一种方式是把NSNumber作为基类,然后分别去实现各自的子类,像这样:


    image.png

    初看起来也没什么问题,但如果子类很多,像这样:


    image.png

    这对使用者来说显然不够方便,得记住这么多类。如果使用类簇,问题就变得简单了,把Number作为抽象基类,子类各自实现存取方式,然后在基类中定义多个初始化方式,像这样:
    image.png

    现在只需要记住一个类就可以了。NSNumber的初始化伪代码大概像这样:
- (id)numberWithBool 
{ 
    return [[__NSCFBoolean alloc] init]; 
} 
 
- (id)numberWithLong 
{ 
    return [[__NSCFNumber alloc] init]; 
} 
//...

六、总结

我们使用设计模式目的无非只有三个:a)缩短开发时间;b)降低维护成本;c)在应用程序之间和内部轻松集成。具体什么时候使用何种设计模式还得因项目而异。

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

推荐阅读更多精彩内容