设计模式深入浅出(一)对象创建——工厂方法,抽象工厂

前言

好久没写博客了,之前AF的源码解析也没写完,后续会补上。

回到设计模式。

说实话,本人写的设计模式系列博客,也只是依照我目前浅薄的见解来写的,并不能保证完全的正确,也希望大家有不同的想法,可以提出共同探讨。

首先我们提到设计模式总是会有一种高大上的感觉,其实设计模式并不是那么高冷,也许我们平常已在不觉总用到了若干设计模式,只是不知道其名字而已。

那么什么是设计模式呢?用通俗的话来讲,就是用面向对象的思想来解决我们平日编程中所遇到的难以处理的情况,提高我们程序的健壮性与拓展性。

这里说到了面向对象思想,其实不外乎那说到烂的三点:继承,封装,多态。基本上当我们第一天接触面向对象思想的时候,就已经把这三点背的滚瓜烂熟。

但是要想真正理解这三点并熟练的运用,却是需要多年的经验的积累与不断实践。

而这23种设计模式,则是面向对象思想最好的体现,他们并不是凭空想象出来了,而是实践编程经验的总结,因此,每种设计模式,都是有实践意义的,并且有适用的场合。

在GoF的《设计模式》一书中,将23种设计模式按照应用场景分为了三大类:

  • 创建型模式(工厂方法,抽象工厂,builder,原型,单例):将对象创建出来
  • 结构型模式(适配器,桥接,组成,装饰,外观,享元,代理):在对象间建立松耦合以协作或访问
  • 行为模式(职责链,命令,解释器,迭代器,中介者,备忘录,观察者,状态,策略,模板,访问者):对象是如何工作的

    但窃以为,以上分类方式还是有些抽象,因此本系列博客将会以如下形式组织结构(参考《Objective-C编程之道》 Carlo Chung著):

  • 对象创建:工厂方法,抽象工厂,builder,原型,单例
  • 接口适配:适配器,桥接,外观
  • 对象去耦:中介者,观察者
  • 抽象集合:组合,迭代器
  • 行为扩展:访问者,装饰,职责链
  • 算法封装:模板,策略,命令
  • 性能与对象访问:享元,代理(代理的懒加载方法可以提升性能,因此放在了一起,但这只是代理的一个应用而言)
  • 对象状态:备忘录,状态

OK,先从第一个模式说起:

工厂方法

先说一个现实的例子:

在现实生活中,有许多手机厂商,苹果,三星,华为,小米等等。他们生产各自牌子的手机,外形,价格,配置都不尽相同。

但是,有没有这种情况,当我从小米换到华为手机,需要重新学习如何打电话呢?现实当然是没有的,不可能每个手机厂商都自定一套自己操作手机打电话的方式。

好,这就说明,每个手机厂商,在生产手机时,都默认符合一种标准,这种标准对用户来说是提供了一个统一的接口,让用户来操作手机。这种统一的标准的好处在于,当用户更换手机型号,品牌时,不需要再重新学习如何操作手机

而这种统一的标准,转换成面向对象编程语言就是统一的接口,而这种统一接口的实现方式,则是继承(或OC中的protocol)。也就是说,所有手机生产厂家的手机,是继承于统一父类的 : Super phone。

而不同厂商,为了让用户方便的用上本厂生产的手机,必需要向用户提供一个统一接口,用来生产手机,这个统一接口,在设计模式中称作工厂方法(factory method)。我们这有涉及到了统一接口的问题,自然又想到了将所有手机厂商继承于统一的父类:Super Phone Factory。Super factory 中定义了工厂方法:createNewPhone:用来生产产品——Super Phone

OK,这个手机厂商生产手机的问题,我们就可以抽象成如下类图:
这里写图片描述

那我们到这里再通用些,我们不仅局限于手机及手机厂商,我们将手机,抽象为产品 Product,将手机厂商抽象为创建者 Creator,那么上图被改造为:
这里写图片描述

好了,现在我们学习的第一个设计模式——工厂方法的UML图已经出来了。上面所说的生产手机的例子,也就是工厂模式的通俗理解。

我们再回顾一下工厂模式的要点:

  1. 抽象了产品product,与生产者Creator。
  2. 用户通过Creator的工厂方法,来获取对应的产品。

工厂方法有什么好处?回到我们上面的例子,当我们更换手机时,不需要重新学习手机的操作方法。也就是说,product的底层实现是对用户透明的(注意,这里的用户指的是你代码的使用者,比如与你协作的程序员或你自己,而不是APP的用户)。对用户层来说,他只需要知道抽象的prodcut类和Creator类即可,而不必关心底层的实现。

这样在用户层逻辑变得简单而易于实现,不同产品的不同被封装在了具体的product中,用户永远都是调用的抽象product的接口,具体的实现,则依赖于具体的product,这一部分是对用户透明的。

伪代码可能是这样的:

// 抽象的Product与Creator
@interface SuperPhone:NSObject
- (void)makeCall;
@end
@interface SuperPhoneFactory:NSObject
- (SuperPhone *)createNewPhone;
@end

// 华为工厂与华为手机
@interface HuaWeiFactory:SuperPhoneFactory
- (SuperPhone *)createNewPhone;
@end
@implementation HuaWeiFactory
- (SuperPhone *)createNewPhone {
    return [HuaWeiPhone new];
}
@end

@interface HuaWeiPhone:SuperPhone
@end

// 苹果工厂与iPhone
@interface AppleFactory:SuperPhoneFactory
- (SuperPhone *)createNewPhone;
@end
@implementation AppleFactory 
- (SuperPhone *)createNewPhone {
    return [iPhone new];
}
@end

@interface iPhone:SuperPhone
@end

// 这用MyPhoneFactory来对工厂方法的调用做一次封装
NS_ENUM(NSInteger, PhoneFactory){
PhoneFacotryHuaWei = 1,
PhoneFactoryApple = 2,
};

@interface MyPhoneFactory:NSObject
- (instancetype)initWithPhoneFactory:(SuperPhoneFactory *)factory;
- (SuperPhone *)createMyNewPhone;
@end

@interface MyPhoneFactory()
@property(nonatomic, strong) SuperPhoneFactory *factory;
@end
@implementation MyPhoneFactory
- (instancetype)initWithPhoneFactory:(SuperPhoneFactory *)factory {
    if(self = [super init]) {
        _factory = factory;
        }
}
- (SuperPhone *)createMyNewPhone {
    return [self.factory createNewPhone];
}
}
@end

// 下面是用户调用部分:
MyPhoneFactory *myPhoneFactory = [[MyPhoneFactory alloc] initWithFactory:[AppleFactory new]]; // 如果想更改手机型号,仅需init方法里面的参数改为其他factory即可,其余代码都不用变,就可正常使用

Persion *person = [Persion new];
person.phone = [myPhoneFactory createMyNewPhone];
[person.phone makeCall]; // 打电话

抽象工厂

说完了工厂模式,我们再进一步说下抽象工厂模式。

还是以上面的手机工厂的例子继续。上面的手机工厂中,仅提供了一种产品:手机。但是现在手机厂商多聪明啊,仅仅有手机是不够的,他们还会生产配套的耳机,数据线,手机套等更多的产品来获取利润。而耳机,数据线,手机套这些虽然大小,插口可能不同,但同样是遵循统一的协议的。(不然我们还需要重新学习如何使用不同厂商的耳机?)

也就是说,我们的工厂现在不仅生产一种产品,而是会生产多种产品,并且这多种产品,也是各自遵循自己的统一标准。那么,工厂模式的UML图可扩展为:
这里写图片描述

好了,这就是抽象工厂的类图。抽象工厂与工厂模区别在于:抽象工厂生产了一系列的产品,当更换抽象工厂时,会更换掉用户所使用的一系列产品。而工厂模式仅支持一种产品。可以说,工厂模式是抽象工厂模式的一个特例。

Cocoa Touch框架中的工厂模式

工厂模式应该算是应用的比较多的模式了。在Cocoa Touch框架中,NSNumber类,其实就应用了工厂模式。
还记的如何初始化一个NSNumber类吗?

 NSNumber *num1 = [NSNumber numberWithInteger:12];
 NSNumber *num2 = [NSNumber numberWithBool:YES];
 NSLog(@"%@ %@", [num1 class], [num2 class]);

打印出的结果为:
这里写图片描述

可以看到,NSNumber对于integer值和Bool值的实现,底层分别使用的NSCFNumber类和NSCFBoolean类。但Cocoa Touch框架暴露给我们的接口却是统一的NSNumber类型的‘产品’。这让我们在处理代码时,不会为了NSCFNumber 和NSCFBoolean两种类型来写两套代码。

还有另一个应用,也许比较隐秘,就是我们在自定义UITableViewCell的时候。
当我们要使用自定义的cell的时候,通常第一步是注册我们自定义的cell,这一步类似于注册不同的cell工厂:

 [_testTableView registerClass:[SWProductTableViewCell class] forCellReuseIdentifier:@"PRODUCT_CELL"];

并在UITableView的DataSource中,调用工厂方法:

dequeueReusableCellWithIdentifier:

返回对应的产品,自定义的cell。但对于UITableView来说,不论我们对cell做了多大更改,他始终是一种产品:UITableViewCell。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    SWProductTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"PRODUCT_CELL"];
    SWProductItem *productItem = [[SWProductItem alloc] init];
    productItem.productName = @"AE-12";
    productItem.price = indexPath.row;
    productItem.productPictures = [NSArray new];
    cell.productItem = productItem;

    return cell;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值