设计模式 ─── 对象创建

对象创建,就是我们在创建对象时可能会用到的设计模式,这边要根据自己的需求去选择。对象创建有以下几种设计模式:
① 原型
② 工厂方法
③ 抽象工厂
④ 生成器
⑤ 单例

一. 原型

原型模式是一种非常简单的设计模式。客户端知道抽象Prototype类。在运行时,抽象Prototype子类的任何对象都可以按照客户端的意愿被复制。因此,无需手工创建制造同一类型的多个实例。
Prototype声明了复制自身的接口。作为Prototype的子类,ConcretePrototype实现了复制自身的clone操作。客户端可以通过请求原型复制自身,创建一个新的对象。(OC中的深拷贝就是使用了原型模式,NSObject的子类实现NSCopying协议及其- (id)copy方法就能实现拷贝功能)

原型模式.png

在以下情况,需要考虑使用原型模式:
(1)有很多相关的类,其行为略有不同,而且主要差异在于内部属性,如名称、图像等。
(2)需要使用组合(树型)对象作为其他东西的基础,例如,使用组合对象作为组件来构建另一个组合对象。

二. 工厂方法

原型模式使用copy方法创建同一类型的对象,工厂方法是用来决定生成何种对象。
工厂方法也称虚构造器,它定义了创建对象的接口,让子类决定实例化哪一个类。工厂方法使得一个类的实例化延迟到其子类。
抽象的Pruduct(产品)定义了工厂方法创建的对象的接口。ConcretePruduct实现了Pruduct接口。Creator定义了返回Pruduct对象的工厂方法。它也可以为工厂方法定义一个默认实现,返回默认的ConcretePruduct对象。Creator的其他操作可以调用此工厂方法创建Pruduct对象。ConcreteCreator是Creator的子类,它重载了Creator的工厂方法,以返回ConcretePruduct的实例(ConcretePruduct1)。
假如需要返回另一种ConcretePruduct的实例(ConcretePruduct2),就需要创建相应的ConcreteCreator(ConcreteCreator2)来生产。所以,这边有两个抽象部分:Pruduct和Creator。

工厂方法.png

与直接创建新的对象相比,工厂方法模式让客户端可以要求由工厂方法创建的对象拥有一组共同的行为。所以往类层次结构中引入新的具体产品并不需要修改客户端代码,因为返回的任何具体产品并不需要修改客户端代码,因为返回的任何具体对象的接口都跟客户端一直在用的从前的接口相同。
设计模式的一个重要原则就是:别改代码,只需要添代码,以前所有的老代码,都是有价值的,需要尽力保留。工厂方法封装了对象创建的细节,将对象创建代码和其他部分脱离,减少相干性。工厂方法有利于同类对象创建的统一管理和控制,你所关心的仅仅是它返回的接口方法,不必关心实现细节。

举个例子🌰:一个界面根据情况可能有两种背景图(view1和view2,创建步骤假设比较复杂),你可以通过创建背景图的基类(view,定义背景图通用的创建方法和属性)和背景图的工厂的基类(factory,定义生产背景图的方法)。有两个factory的子类,各自通过重载父类的生产方法创建对应view的实例(比如factory1生产view1,factory2生产view2)。这样客户端使用的是工厂类,消除了与特定的背景图类的耦合,新增不同背景图时,也可以添加对应的view3和factory3。

对比工厂方法,还有一种简约型的工厂:简单工厂。把对象的创建放到一个工厂类中,通过参数来创建不同的对象。这个缺点是每添一个对象,就需要对简单工厂进行修改(尽管不是删代码,仅仅是添一个switch case,但仍然违背了“不改代码”的原则)。
所以,工厂方法模式对简单工厂模式进行了抽象。有一个抽象的Factory类(可以是抽象类和接口),这个类将不再负责具体的产品生产,而是只制定一些规范,具体的生产工作由其子类去完成。在这个模式中,工厂类和产品类往往可以依次对应。即一个抽象工厂对应一个抽象产品,一个具体工厂对应一个具体产品,这个具体的工厂就负责生产对应的产品。

在以下情况,需要考虑使用工厂方法:(其实了解上面工厂方法的作用就能衡量了)
(1)编译时无法准确预期要创建的对象的类。
(2)类想让其子类决定在运行时创建什么。
(3)类有若干辅助类为其子类,而你想将返回哪个子类这一信息局部化。

三. 抽象工厂

工厂方法只适用于生产单个产品,涉及到一系列产品就需要抽象工厂了。
抽象工厂提供一个固定的接口,用于创建一系列有关联或相依存的对象,而不必指定其具体类或其创建的细节。客户端与从工厂得到的具体对象之间没有耦合。
如类图所示,客户端Client只知道AbstractFactory和AbstractProduct,甚至产品也不知道将负责创建它们。工厂方法把实际的创建过程推迟到重载它的子类中,最初的抽象方法什么也不创建(这也可以解释为什么叫抽象工厂的原因,实际创建对象使用的还是各自具体的工厂)。

抽象工厂.png

举个例子🌰:以界面更换风格为例(按钮,工具栏,背景图都改变),这时候就不能使用之前的工厂方法。我们需要抽象的有工厂类和三种控件类。我们在使用时,只知道这两种抽象类,我们创建抽象工厂来生产抽象控件。

// 抽象工厂
BrandingFactory * factory = [BrandingFactory factory];

// 通过抽象工厂创建抽象背景图
UIView * view = [factory brandedView];
// 通过抽象工厂创建抽象按钮
UIButton * button = [factory brandedMainButton];
// 通过抽象工厂创建抽象工具栏
UIToolbar * toolbar = [factory brandedToolbar];

实际上,抽象工厂类调用的还是各自的具体工厂。可以说,+ (BrandingFactory *) factory方法只是为了返回能够创建产品的工厂。

@implementation BrandingFactory
// 抽象工厂类中调用的还是具体工厂
+ (BrandingFactory *) factory{
#if defined (USE_ACME)
    return [[[AcmeBrandingFactory alloc] init] autorelease];
#elif defined (USE_SIERRA)
    return [[[SierraBrandingFactory alloc] init] autorelease];
#else
    return nil;
#endif
}
- (UIView *) brandedView{
    return nil;
}
- (UIButton *) brandedMainButton{
    return nil;
}
- (UIToolbar *) brandedToolbar{
    return nil;
}

只有各自风格具体工厂中,才会创建对应风格的控件。

@implementation AcmeBrandingFactory

- (UIView *) brandedView{
    return [[[AcmeView alloc] init] autorelease];
}
- (UIButton *) brandedMainButton{
    return [[[AcmeMainButton alloc] init] autorelease];
}
- (UIToolbar *) brandedToolbar{
    return [[[AcmeToolbar alloc] init] autorelease];
}

NSNumber(类簇)就是抽象工厂实现的一种形式,NSNumber生存出NSCFBoolean和NSCFNumber的具体工厂类,它们重载了NSNumber中声明的公有工厂方法以生存各自的产品。

对比普通工厂,抽象工厂是具体工厂的组合,内部是通过具体工厂来创建抽象产品,所以它隔离了具体类的生产,使得客户并不需要知道什么被创建。它也可以生产多系列产品(普通工厂适用于一个类方法创建出一个具体产品,抽象工厂一个类方法创建出具体工厂)。增加新的具体工厂和产品族很方便,但是增加新的产品等级结构很复杂,需要修改抽象工厂和所有的具体工厂类。
所以说,抽象工厂是生产工厂的工厂,普通工厂是生产具体产品的工厂。有时候,一开始使用具体工厂,而后重构成使用多个具体工厂的抽象工厂。

抽象工厂和普通工厂.png

四. 生成器

有时候,构建某些对象有不同方式。如果这些逻辑包含在构建这些对象的类的单一方法中,构建的逻辑会非常复杂(一大堆判断)。所以把构建过程分解为客户端 - 指导者 - 生成器的关系,过程将更容易管理和复用。
生成器模式将一个复杂对象的构建与它的表现分离,使得同样的构建过程可以创建不同的表现。
如类图所示,生成器模式有两个重要角色:指导者(Director)和生成器(Builder)。Builder如何在缺少某些特定信息的情况下建造产品,Director知道Builder应该建造什么,以参数向其提供缺少的信息来创建特定产品。Director和Builder是一种聚合的关系(Builder以局部变量存在),Director不负责Builder的生命周期,也不清楚具体Builder是什么。


生成器模式.png

举个例子🌰:我们要创建追逐游戏中的两个角色(玩家和敌人,属性值不一样)
在客户端中,我们只需要创建生成器和指导者,我们叫指导者生成一个角色(比如玩家),指导者会叫生成器(Builder一般会有抽象接口)去建造,建造完成将角色返回给生成器。对于我们来讲,我们只需要调用生成器的生成方法,直接可以获取到对应的角色。
它们之间的关系如下图:

客户端、指导者和生成器的交互.png

在客户端中,代码如下:

//创建生成器(负责创建对象)
CharacterBuilder *characterBuilder = [[[StandardCharacterBuilder alloc] init] autorelease];
// 创建指导者(指导者知道创建什么对象,它会叫生成器去创建对象)
ChasingGame *game = [[[ChasingGame alloc] init] autorelease];

//创建玩家
Character *player = [game createPlayer:characterBuilder];
//创建敌人
Character *enemy = [game createEnemy:characterBuilder];

在创建方法中,是Builder在创建角色。

- (Character *) createEnemy:(CharacterBuilder *) builder
{
  //创建新角色
  [builder buildNewCharacter];
  //给角色属性赋值
  [builder buildStrength:80.0];
  [builder buildStamina:65.0];
  [builder buildIntelligence:35.0];
  [builder buildAgility:25.0];
  [builder buildAggressiveness:95.0];
  // 返回角色(这边是返回敌人)
  return [builder character];
}

对比抽象工厂,生成器关注的是分步创建复杂对象,很多时候同一类型的对象可以以不同的方式创建。而抽象工厂的重点在于创建简单或复杂产品的套件。

生成器和抽象工厂.png

其实,如果没有Builder感觉有点像工厂方法,但是是使用类似的工厂方法,要嘛将一大堆创建逻辑放在产品类里,要嘛放在Director里,这些都带有无数个同一个类的各种表现的算法,这都不是好的方法。所以需要抽出一个Builder,来放置构建算法。

在以下情况,需要考虑使用生成器模式:
(1)需要创建涉及各个部件的复杂对象,创建对象的算法应该独立于部件的装配方式。常见的例子是构建组合对象。
(2)构建过程需要以不同的方法构建对象。

五. 单例

单例模式几乎是设计模式中最简单的形式了。它保证一个类仅有一个实例,并提供一个访问它的全局访问点。
单例模式提供了一个为人熟知的访问点,供客户端为共享资源生产唯一实例,并通过它对共享资源进行访问。虽然静态的全局对象引用或类方法也可以提供全局访问点,但是全局对象无法防止类被实例化一次以上,假如有人也定义了相同类型的静态变量,那程序中就有两个相同的全局变量了。而且类方法也缺少消除耦合的灵活性,如果类需要被子类化提供更好的服务(比如需要属性保存参数),它就不适用了。在cocoa框架中,单例也随处可见,UIApplication、NSFileManager等等。

单例模式.png

单例模式虽然简单,但是也容易被滥用。在使用单例创建一个类前要考虑是否真的需要使用单例,因为单例在程序中只存在一份,在程序运行中也一直存在内存中,所以不要什么都往单例上写。
举个例子🌰:实际开发中,笔者常用到定义一个UserManager单例来保存用户使用程序中的一些全局属性(比如UserModel),而不是把UserModel做成一个单例。要知道单例是会一直存在的,你没办法把它制成nil。假如切换用户,你用的是UserManager,就可以把其中的属性userModel置为nil。

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

推荐阅读更多精彩内容