日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > asp.net >内容正文

asp.net

iOS开发 - 面向对象设计的设计模式(一):创建型模式(附 Demo UML类图)

發(fā)布時(shí)間:2024/1/1 asp.net 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 iOS开发 - 面向对象设计的设计模式(一):创建型模式(附 Demo UML类图) 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

繼上一篇的面向?qū)ο笤O(shè)計(jì)的設(shè)計(jì)原則,本篇是面向?qū)ο笤O(shè)計(jì)系列的第二個(gè)部分:面向?qū)ο笤O(shè)計(jì)的設(shè)計(jì)模式的第一篇文章。

另外,本篇博客的代碼和類圖都保存在我的GitHub庫(kù)中:中的Chapter2。

最開始說(shuō)一下什么是設(shè)計(jì)模式。關(guān)于設(shè)計(jì)模式的概念,有很多不同的版本,在這里說(shuō)一下我個(gè)人比較贊同的一個(gè)說(shuō)法:

設(shè)計(jì)模式用于在特定的條件下為一些重復(fù)出現(xiàn)的軟件設(shè)計(jì)問(wèn)題提供合理的、有效的解決方案。

去掉一些定語(yǔ)的修飾,這句話精簡(jiǎn)為:

設(shè)計(jì)模式為問(wèn)題提供方案。

簡(jiǎn)單來(lái)看,設(shè)計(jì)模式其實(shí)就是針對(duì)某些問(wèn)題的一些方案。在軟件開發(fā)中,即使很多人在用不同的語(yǔ)言去開發(fā)不同的業(yè)務(wù),但是很多時(shí)候這些人遇到的問(wèn)題抽象出來(lái)都是相似的。一些卓越的開發(fā)者將一些常出現(xiàn)的問(wèn)題和對(duì)應(yīng)的解決方案匯總起來(lái),總結(jié)出了這些設(shè)計(jì)模式。

因此掌握了這些設(shè)計(jì)模式,可以讓我們更好地去解決開發(fā)過(guò)程中遇到的一些常見問(wèn)題。而且對(duì)這些問(wèn)題的解決方案的掌握程度越好,我們就越能夠打破語(yǔ)言本身的限制去解決問(wèn)題,也就是增強(qiáng)“軟件開發(fā)的內(nèi)功”。

介紹設(shè)計(jì)模式最著名的一本書莫屬《設(shè)計(jì)模式 可復(fù)用面向?qū)ο筌浖幕A(chǔ)》這本書,書中共介紹了23個(gè)設(shè)計(jì)模式。而這些設(shè)計(jì)模式分為三大類:

  • 創(chuàng)建型設(shè)計(jì)模式:側(cè)重于對(duì)象的創(chuàng)建。
  • 結(jié)構(gòu)型設(shè)計(jì)模式:側(cè)重于接口的設(shè)計(jì)和系統(tǒng)的結(jié)構(gòu)。
  • 行為型設(shè)計(jì)模式:側(cè)重于類或?qū)ο蟮男袨椤?/li>

而本篇作為該系列的第一篇,講解的是設(shè)計(jì)模式中的6個(gè)創(chuàng)建型設(shè)計(jì)模式:

  • 簡(jiǎn)單工廠模式(Simple Factory Pattern)
  • 工廠方法模式(Factory Method Pattern)
  • 抽象工廠模式(Abstract Factory Pattern)
  • 單例模式(Singleton Pattern)
  • 生成器模式(Builder Pattern)
  • 原型模式(Prototype Pattern)
  • 注意:簡(jiǎn)單工廠模式不是 GoF總結(jié)出來(lái)的23種設(shè)計(jì)模式之一,不存在于《設(shè)計(jì)模式 可復(fù)用面向?qū)ο筌浖幕A(chǔ)》這本書中。

    在面向?qū)ο笤O(shè)計(jì)中,類與對(duì)象幾乎是構(gòu)成所有系統(tǒng)的基本元素,因此我認(rèn)為學(xué)好了創(chuàng)建型模式才是學(xué)會(huì)設(shè)計(jì)系統(tǒng)的第一步:因?yàn)槟銘?yīng)該知道如何去創(chuàng)建一些特定性質(zhì)的對(duì)象,這才是設(shè)計(jì)好的系統(tǒng)的開始。

    在講解這6個(gè)設(shè)計(jì)模式之前先說(shuō)一下該系列文章的講解方式:

    從更多維度來(lái)理解一件事物有助于更深刻地理解它,因此每個(gè)設(shè)計(jì)模式我都會(huì)從以下這幾點(diǎn)來(lái)講解:

    • 定義
    • 使用場(chǎng)景
    • 成員與類圖
    • 代碼示例
    • 優(yōu)點(diǎn)
    • 缺點(diǎn)
    • iOS SDK 和 JDK 中的應(yīng)用

    最后一項(xiàng):“iOS SDK 和 JDK中的應(yīng)用”講解的是該設(shè)計(jì)模式在Objective-C和java語(yǔ)言(JDK)中的應(yīng)用。

    首先我們看一下簡(jiǎn)單工廠模式:

    一. 簡(jiǎn)單工廠模式

    定義

    簡(jiǎn)單工廠模式(Simple Factory Pattern):專門定義一個(gè)類(工廠類)來(lái)負(fù)責(zé)創(chuàng)建其他類的實(shí)例。可以根據(jù)創(chuàng)建方法的參數(shù)來(lái)返回不同類的實(shí)例,被創(chuàng)建的實(shí)例通常都具有共同的父類。

    簡(jiǎn)單工廠模式又稱為靜態(tài)工廠方法(Static Factory Method)模式,它屬于類創(chuàng)建型模式。

    適用場(chǎng)景

    如果我們希望將一些為數(shù)不多的類似的對(duì)象的創(chuàng)建和他們的創(chuàng)建細(xì)節(jié)分離開,也不需要知道對(duì)象的具體類型,可以使用簡(jiǎn)單工廠模式。

    舉個(gè)形象點(diǎn)的例子:在前端開發(fā)中,常常會(huì)使用外觀各式各樣的按鈕:比如有的按鈕有圓角,有的按鈕有陰影,有的按鈕有邊框,有的按鈕無(wú)邊框等等。但是因?yàn)橥环N樣式的按鈕可以出現(xiàn)在項(xiàng)目的很多地方,所以如果在每個(gè)地方都把創(chuàng)建按鈕的邏輯寫一遍的話顯然是會(huì)造成代碼的重復(fù)(而且由于業(yè)務(wù)的原因有的按鈕的創(chuàng)建邏輯能比較復(fù)雜,代碼量大)。

    那么為了避免重復(fù)代碼的產(chǎn)生,我們可以將這些創(chuàng)建按鈕的邏輯都放在一個(gè)“工廠”里面,讓這個(gè)工廠來(lái)根據(jù)你的需求(傳入的參數(shù))來(lái)創(chuàng)建對(duì)應(yīng)的按鈕并返回給你。這樣一來(lái),同樣類型的按鈕在多個(gè)地方使用的時(shí)候,就可以只給這個(gè)工廠傳入其對(duì)應(yīng)的參數(shù)并拿到返回的按鈕即可。

    下面來(lái)看一下簡(jiǎn)單工廠模式的成員和類圖。

    成員與類圖

    成員

    簡(jiǎn)單工廠模式的結(jié)構(gòu)比較簡(jiǎn)單,一共只有三個(gè)成員:

    • 工廠(Factory):工廠負(fù)責(zé)實(shí)現(xiàn)創(chuàng)建所有產(chǎn)品實(shí)例的邏輯
    • 抽象產(chǎn)品(Product):抽象產(chǎn)品是工廠所創(chuàng)建的所有產(chǎn)品對(duì)象的父類,負(fù)責(zé)聲明所有產(chǎn)品實(shí)例所共有的公共接口。
    • 具體產(chǎn)品(Concrete Product):具體產(chǎn)品是工廠所創(chuàng)建的所有產(chǎn)品對(duì)象類,它以自己的方式來(lái)實(shí)現(xiàn)其共同父類聲明的接口。

    下面通過(guò)類圖來(lái)看一下各個(gè)成員之間的關(guān)系:

    模式類圖

    從類圖中可以看出,工廠類提供一個(gè)靜態(tài)方法:通過(guò)傳入的字符串來(lái)制造其所對(duì)應(yīng)的產(chǎn)品。

    代碼示例

    場(chǎng)景概述

    舉一個(gè)店鋪售賣不同品牌手機(jī)的例子:店鋪,即客戶端類向手機(jī)工廠購(gòu)進(jìn)手機(jī)售賣。

    場(chǎng)景分析

    該場(chǎng)景可以使用簡(jiǎn)單工廠的角色來(lái)設(shè)計(jì):

    • 抽象產(chǎn)品:Phone,是所有具體產(chǎn)品類的父類,提供一個(gè)公共接口packaging表示手機(jī)的裝箱并送到店鋪。
    • 具體產(chǎn)品:不同品牌的手機(jī),iPhone手機(jī)類(IPhone),小米手機(jī)類(MIPhone),華為手機(jī)類(HWPhone)。
    • 工廠:PhoneFactory根據(jù)不同的參數(shù)來(lái)創(chuàng)建不同的手機(jī)。
    • 客戶端類:店鋪類Store負(fù)責(zé)售賣手機(jī)。

    代碼實(shí)現(xiàn)

    抽象產(chǎn)品類Phone:

    //================== Phone.h ================== @interface Phone : NSObject//package to store - (void)packaging;@end

    具體產(chǎn)品類?IPhone:

    //================== IPhone.h ================== @interface IPhone : Phone@end//================== IPhone.m ================== @implementation IPhone- (void)packaging{NSLog(@"IPhone has been packaged"); }@end

    具體產(chǎn)品類?MIPhone:

    //================== MIPhone.h ================== @interface MIPhone : Phone@end//================== MIPhone.m ================== @implementation MIPhone- (void)packaging{NSLog(@"MIPhone has been packaged"); }@end

    具體產(chǎn)品類:HWPhone:

    //================== HWPhone.h ================== @interface HWPhone : Phone@end//================== HWPhone.m ================== @implementation HWPhone- (void)packaging{NSLog(@"HUAWEI Phone has been packaged"); }@end

    以上是抽象產(chǎn)品類以及它的三個(gè)子類:蘋果手機(jī)類,小米手機(jī)類和華為手機(jī)類。
    下面看一下工廠類?PhoneFactory:

    //================== PhoneFactory.h ================== @interface PhoneFactory : NSObject+ (Phone *)createPhoneWithTag:(NSString *)tag;@end//================== PhoneFactory.m ================== #import "IPhone.h" #import "MIPhone.h" #import "HWPhone.h"@implementation PhoneFactory+ (Phone *)createPhoneWithTag:(NSString *)tag{if ([tag isEqualToString:@"i"]) {IPhone *iphone = [[IPhone alloc] init];return iphone;}else if ([tag isEqualToString:@"MI"]){MIPhone *miPhone = [[MIPhone alloc] init];return miPhone;}else if ([tag isEqualToString:@"HW"]){HWPhone *hwPhone = [[HWPhone alloc] init];return hwPhone;}else{return nil;} }@end

    工廠類向外部(客戶端)提供了一個(gè)創(chuàng)造手機(jī)的接口createPhoneWithTag:,根據(jù)傳入?yún)?shù)的不同可以返回不同的具體產(chǎn)品類。因此客戶端只需要知道它所需要的產(chǎn)品所對(duì)應(yīng)的參數(shù)即可獲得對(duì)應(yīng)的產(chǎn)品了

    在本例中,我們聲明了店鋪類?Store為客戶端類:

    //================== Store.h ================== #import "Phone.h"@interface Store : NSObject- (void)sellPhone:(Phone *)phone;@end//================== Store.m ================== @implementation Store- (void)sellPhone:(Phone *)phone{NSLog(@"Store begins to sell phone:%@",[phone class]); }@end

    客戶端類聲明了一個(gè)售賣手機(jī)的接口sellPhone:。表示它可以售賣作為參數(shù)所傳入的手機(jī)。

    最后我們用代碼模擬一下這個(gè)實(shí)際場(chǎng)景:

    //================== Using by client ==================//1\. A phone store wants to sell iPhone Store *phoneStore = [[Store alloc] init];//2\. create phone Phone *iPhone = [PhoneFactory createPhoneWithTag:@"i"];//3\. package phone to store [iphone packaging];//4\. store sells phone after receving it [phoneStore sellPhone:iphone];

    上面代碼的解讀:

  • 最開始實(shí)例化一個(gè)商店,商店打算賣蘋果手機(jī)
  • 商店委托工廠給他制作一臺(tái)iPhone手機(jī),傳入對(duì)應(yīng)的字段i。
  • 手機(jī)生產(chǎn)好以后打包送到商店
  • 商店售賣手機(jī)
  • 在這里我們需要注意的是:商店從工廠拿到手機(jī)不需要了解手機(jī)制作的過(guò)程,只需要知道它要工廠做的是手機(jī)(只知道Phone類即可),和需要給工廠類傳入它所需手機(jī)所對(duì)應(yīng)的參數(shù)即可(這里的iPhone手機(jī)對(duì)應(yīng)的參數(shù)就是i)。

    下面我們看一下該例子對(duì)應(yīng)的 UML類圖,可以更直觀地看一下各個(gè)成員之間的關(guān)系:

    代碼對(duì)應(yīng)的類圖

    優(yōu)點(diǎn)

    • 客戶端只需要給工廠類傳入一個(gè)正確的(約定好的)參數(shù),就可以獲取你所需要的對(duì)象,而不需要知道其創(chuàng)建細(xì)節(jié),一定程度上減少系統(tǒng)的耦合。
    • 客戶端無(wú)須知道所創(chuàng)建的具體產(chǎn)品類的類名,只需要知道具體產(chǎn)品類所對(duì)應(yīng)的參數(shù)即可,減少開發(fā)者的記憶成本。

    缺點(diǎn)

    • 如果業(yè)務(wù)上添加新產(chǎn)品的話,就需要修改工廠類原有的判斷邏輯,這其實(shí)是違背了開閉原則的。
    • 在產(chǎn)品類型較多時(shí),有可能造成工廠邏輯過(guò)于復(fù)雜。所以簡(jiǎn)單工廠模式比較適合產(chǎn)品種類比較少而且增多的概率很低的情況。

    iOS SDK 和 JDK 中的應(yīng)用

    • Objective-C中的類簇就是簡(jiǎn)單工廠設(shè)計(jì)模式的一個(gè)應(yīng)用。如果給NSNumber的工廠方法傳入不同類型的數(shù)據(jù),則會(huì)返回不同數(shù)據(jù)所對(duì)應(yīng)的NSNumber的子類。
    • JDK中的Calendar類中的私有的createCalendar(TimeZone zone, Locale aLocale)方法通過(guò)不同的入?yún)?lái)返回不同類型的Calendar子類的實(shí)例。

    二. 工廠方法模式

    定義

    工廠方法模式(Factory Method Pattern)又稱為工廠模式,工廠父類負(fù)責(zé)定義創(chuàng)建產(chǎn)品對(duì)象的公共接口,而工廠子類則負(fù)責(zé)生成具體的產(chǎn)品對(duì)象,即通過(guò)不同的工廠子類來(lái)創(chuàng)建不同的產(chǎn)品對(duì)象。

    適用場(chǎng)景

    工廠方法模式的適用場(chǎng)景與簡(jiǎn)單工廠類似,都是創(chuàng)建數(shù)據(jù)和行為比較類似的對(duì)象。但是和簡(jiǎn)單工廠不同的是:在工廠方法模式中,因?yàn)閯?chuàng)建對(duì)象的責(zé)任移交給了抽象工廠的子類,因此客戶端需要知道其所需產(chǎn)品所對(duì)應(yīng)的工廠子類,而不是簡(jiǎn)單工廠中的參數(shù)。

    下面我們看一下工廠方法模式的成員和類圖。

    成員與類圖

    成員

    工廠方法模式包含四個(gè)成員:

  • 抽象工廠(Abstract Factory):抽象工廠負(fù)責(zé)聲明具體工廠的創(chuàng)建產(chǎn)品的接口。
  • 具體工廠(Concrete Factory):具體工廠負(fù)責(zé)創(chuàng)建產(chǎn)品。
  • 抽象產(chǎn)品(Abstract Product):抽象產(chǎn)品是工廠所創(chuàng)建的所有產(chǎn)品對(duì)象的父類,負(fù)責(zé)聲明所有產(chǎn)品實(shí)例所共有的公共接口。
  • 具體產(chǎn)品(Concrete Product):具體產(chǎn)品是工廠所創(chuàng)建的所有產(chǎn)品對(duì)象類,它以自己的方式來(lái)實(shí)現(xiàn)其共同父類聲明的接口。
  • 下面通過(guò)類圖來(lái)看一下各個(gè)成員之間的關(guān)系:

    模式類圖

    從類圖中我們可以看到:抽象工廠負(fù)責(zé)定義具體工廠必須實(shí)現(xiàn)的接口,而創(chuàng)建產(chǎn)品對(duì)象的任務(wù)則交給具體工廠,由特定的子工廠來(lái)創(chuàng)建其對(duì)應(yīng)的產(chǎn)品。

    這使得工廠方法模式可以允許系統(tǒng)在不修改原有工廠的情況下引進(jìn)新產(chǎn)品:只需要?jiǎng)?chuàng)建新產(chǎn)品類和其所對(duì)應(yīng)的工廠類即可。

    代碼示例

    場(chǎng)景概述

    同樣也是模擬上面的簡(jiǎn)單工廠例子中的場(chǎng)景(手機(jī)商店賣手機(jī)),但是由于這次是由工廠方法模式來(lái)實(shí)現(xiàn)的,因此在代碼設(shè)計(jì)上會(huì)有變化。

    場(chǎng)景分析

    與簡(jiǎn)單工廠模式不同的是:簡(jiǎn)單工廠模式里面只有一個(gè)工廠,而工廠方法模式里面有一個(gè)抽象工廠和繼承于它的具體工廠。

    因此同樣的三個(gè)品牌的手機(jī),我們可以通過(guò)三個(gè)不同的具體工廠:蘋果手機(jī)工廠(IPhoneFactory),小米手機(jī)工廠 (MIPhoneFactory),華為手機(jī)工廠(HWPhoneFactory)來(lái)生產(chǎn)。而這些具體工廠類都會(huì)繼承于抽象手機(jī)工廠類:PhoneFactory,它來(lái)聲明生產(chǎn)手機(jī)的接口。

    下面我們用代碼來(lái)具體來(lái)看一下工廠類(抽象工廠和具體工廠)的設(shè)計(jì):

    代碼實(shí)現(xiàn)

    首先我們聲明一個(gè)抽象工廠類?PhoneFactory:

    //================== PhoneFactory.h ================== #import "Phone.h"@interface PhoneFactory : NSObject+ (Phone *)createPhone;@end//================== PhoneFactory.m ================== @implementation PhoneFactory+ (Phone *)createPhone{//implemented by subclassreturn nil; }@end

    抽象工廠類給具體工廠提供了生產(chǎn)手機(jī)的接口,因此不同的具體工廠可以按照自己的方式來(lái)生產(chǎn)手機(jī)。下面看一下具體工廠:

    蘋果手機(jī)工廠?IPhoneFactory

    //================== IPhoneFactory.h ================== @interface IPhoneFactory : PhoneFactory @end//================== IPhoneFactory.m ================== #import "IPhone.h"@implementation IPhoneFactory+ (Phone *)createPhone{IPhone *iphone = [[IPhone alloc] init];NSLog(@"iPhone has been created");return iphone; }@end

    小米手機(jī)工廠?MIPhoneFactory:

    //================== MIPhoneFactory.h ================== @interface MPhoneFactory : PhoneFactory@end//================== MIPhoneFactory.m ================== #import "MiPhone.h"@implementation MPhoneFactory+ (Phone *)createPhone{MiPhone *miPhone = [[MiPhone alloc] init];NSLog(@"MIPhone has been created");return miPhone; }@end

    華為手機(jī)工廠?HWPhoneFactory:

    //================== HWPhoneFactory.h ================== @interface HWPhoneFactory : PhoneFactory@end//================== HWPhoneFactory.m ================== #import "HWPhone.h"@implementation HWPhoneFactory+ (Phone *)createPhone{HWPhone *hwPhone = [[HWPhone alloc] init];NSLog(@"HWPhone has been created");return hwPhone; }@end

    以上就是聲明的抽象工廠類和具體工廠類。因?yàn)樯a(chǎn)手機(jī)的責(zé)任分配給了各個(gè)具體工廠類,因此客戶端只需要委托所需手機(jī)所對(duì)應(yīng)的工廠就可以獲得其生產(chǎn)的手機(jī)了。

    因?yàn)槌橄螽a(chǎn)品類Phone和三個(gè)具體產(chǎn)品類(IPhone,MIPhone,HWPhone)和簡(jiǎn)單工廠模式中介紹的例子中的一樣,因此這里就不再重復(fù)介紹了。

    下面我們用代碼模擬一下該場(chǎng)景:

    //================== Using by client ==================//A phone store Store *phoneStore = [[Store alloc] init];//phoneStore wants to sell iphone Phone *iphone = [IPhoneFactory createPhone]; [iphone packaging]; [phoneStore sellPhone:iphone];//phoneStore wants to sell MIPhone Phone *miPhone = [MPhoneFactory createPhone]; [miPhone packaging]; [phoneStore sellPhone:miPhone];//phoneStore wants to sell HWPhone Phone *hwPhone = [HWPhoneFactory createPhone]; [hwPhone packaging]; [phoneStore sellPhone:hwPhone];

    由上面的代碼可以看出:客戶端phoneStore只需委托iPhone,MIPhone,HWPhone對(duì)應(yīng)的工廠即可獲得對(duì)應(yīng)的手機(jī)了。

    而且以后如果增加其他牌子的手機(jī),例如魅族手機(jī),就可以聲明一個(gè)魅族手機(jī)類和魅族手機(jī)的工廠類并實(shí)現(xiàn)createPhone這個(gè)方法即可,而不需要改動(dòng)原有已經(jīng)聲明好的各個(gè)手機(jī)類和具體工廠類。

    下面我們看一下該例子對(duì)應(yīng)的 UML類圖,可以更直觀地看一下各個(gè)成員之間的關(guān)系:

    代碼對(duì)應(yīng)的類圖

    優(yōu)點(diǎn)

    • 用戶只需要關(guān)心其所需產(chǎn)品對(duì)應(yīng)的具體工廠是哪一個(gè)即可,不需要關(guān)心產(chǎn)品的創(chuàng)建細(xì)節(jié),也不需要知道具體產(chǎn)品類的類名。
    • 當(dāng)系統(tǒng)中加入新產(chǎn)品時(shí),不需要修改抽象工廠和抽象產(chǎn)品提供的接口,也無(wú)須修改客戶端和其他的具體工廠和具體產(chǎn)品,而只要添加一個(gè)具體工廠和與其對(duì)應(yīng)的具體產(chǎn)品就可以了,符合了開閉原則(這一點(diǎn)與簡(jiǎn)單工廠模式不同)。

    缺點(diǎn)

    • 當(dāng)系統(tǒng)中加入新產(chǎn)品時(shí),除了需要提供新的產(chǎn)品類之外,還要提供與其對(duì)應(yīng)的具體工廠類。因此系統(tǒng)中類的個(gè)數(shù)將成對(duì)增加,增加了系統(tǒng)的復(fù)雜度。

    iOS SDK 和 JDK 中的應(yīng)用

    • 暫未發(fā)現(xiàn)iOS SDK中使用工廠方法的例子,有知道的小伙伴歡迎留言。
    • 在JDK中,Collection接口聲明了iterator()方法,該方法返回結(jié)果的抽象類是Iterator。ArrayList就實(shí)現(xiàn)了這個(gè)接口;,而ArrayList對(duì)應(yīng)的具體產(chǎn)品是Itr。

    三. 抽象工廠模式

    定義

    抽象工廠模式(Abstract Factory Pattern):提供一個(gè)創(chuàng)建一系列相關(guān)或相互依賴對(duì)象的接口,而無(wú)須指定它們具體的類。

    適用場(chǎng)景

    有時(shí)候我們需要一個(gè)工廠可以提供多個(gè)產(chǎn)品對(duì)象,而不是單一的產(chǎn)品對(duì)象。比如系統(tǒng)中有多于一個(gè)的產(chǎn)品族,而每次只使用其中某一產(chǎn)品族,屬于同一個(gè)產(chǎn)品族的產(chǎn)品將在一起使用。

    在這里說(shuō)一下產(chǎn)品族和產(chǎn)品等級(jí)結(jié)構(gòu)的概念:

    • 產(chǎn)品族:同一工廠生產(chǎn)的不同產(chǎn)品
    • 產(chǎn)品等級(jí)結(jié)構(gòu):同一類型產(chǎn)品的不同實(shí)現(xiàn)

    用一張圖來(lái)幫助理解:

    在上圖中:

    • 縱向的,不同形狀,相同色系的圖形屬于同一產(chǎn)品組的產(chǎn)品,而同一產(chǎn)品族的產(chǎn)品對(duì)應(yīng)的是同一個(gè)工廠;
    • 橫向的,同一形狀,不同色系的圖形屬于統(tǒng)一產(chǎn)品等級(jí)結(jié)構(gòu)的產(chǎn)品,而統(tǒng)一產(chǎn)品等級(jí)結(jié)構(gòu)的產(chǎn)品對(duì)應(yīng)的是同一個(gè)工廠方法。

    下面再舉一個(gè)例子幫助大家理解:

    我們將小米,華為,蘋果公司比作抽象工廠方法里的工廠:這三個(gè)工廠都有自己生產(chǎn)的手機(jī),平板和電腦。
    那么小米手機(jī),小米平板,小米電腦就屬于小米這個(gè)工廠的產(chǎn)品族;同樣適用于華為工廠和蘋果工廠。
    而小米手機(jī),華為手機(jī),蘋果手機(jī)則屬于同一產(chǎn)品等級(jí)結(jié)構(gòu):手機(jī)的產(chǎn)品等級(jí)結(jié)構(gòu);平板和電腦也是如此。

    結(jié)合這個(gè)例子對(duì)上面的圖做一個(gè)修改可以更形象地理解抽象工廠方法的設(shè)計(jì):

    成員與類圖

    成員

    抽象工廠模式的成員和工廠方法模式的成員是一樣的,只不過(guò)抽象工廠方法里的工廠是面向產(chǎn)品族的。

  • 抽象工廠(Abstract Factory):抽象工廠負(fù)責(zé)聲明具體工廠的創(chuàng)建產(chǎn)品族內(nèi)的所有產(chǎn)品的接口。
  • 具體工廠(Concrete Factory):具體工廠負(fù)責(zé)創(chuàng)建產(chǎn)品族內(nèi)的產(chǎn)品。
  • 抽象產(chǎn)品(Abstract Product):抽象產(chǎn)品是工廠所創(chuàng)建的所有產(chǎn)品對(duì)象的父類,負(fù)責(zé)聲明所有產(chǎn)品實(shí)例所共有的公共接口。
  • 具體產(chǎn)品(Concrete Product):具體產(chǎn)品是工廠所創(chuàng)建的所有產(chǎn)品對(duì)象類,它以自己的方式來(lái)實(shí)現(xiàn)其共同父類聲明的接口。
  • 下面通過(guò)類圖來(lái)看一下各個(gè)成員之間的關(guān)系:

    模式類圖

    • 抽象工廠模式與工廠方法模式最大的區(qū)別在于,工廠方法模式針對(duì)的是一個(gè)產(chǎn)品等級(jí)結(jié)構(gòu),而抽象工廠模式則需要面對(duì)多個(gè)產(chǎn)品等級(jí)結(jié)構(gòu)
    • 增加新的具體工廠和產(chǎn)品族很方便,無(wú)須修改已有系統(tǒng),符合“開閉原則”。

    代碼示例

    場(chǎng)景概述

    由于抽象工廠方法里的工廠是面向產(chǎn)品族的,所以為了貼合抽象工廠方法的特點(diǎn),我們將上面的場(chǎng)景做一下調(diào)整:在上面兩個(gè)例子中,商店只賣手機(jī)。在這個(gè)例子中我們讓商店也賣電腦:分別是蘋果電腦,小米電腦,華為電腦。

    場(chǎng)景分析

    如果我們還是套用上面介紹過(guò)的工廠方法模式來(lái)實(shí)現(xiàn)該場(chǎng)景的話,則需要?jiǎng)?chuàng)建三個(gè)電腦產(chǎn)品對(duì)應(yīng)的工廠:蘋果電腦工廠,小米電腦工廠,華為電腦工廠。這就導(dǎo)致類的個(gè)數(shù)直線上升,以后如果還增加其他的產(chǎn)品,還需要添加其對(duì)應(yīng)的工廠類,這顯然是不夠優(yōu)雅的。

    仔細(xì)看一下這六個(gè)產(chǎn)品的特點(diǎn),我們可以把這它們劃分在三個(gè)產(chǎn)品族里面:

  • 蘋果產(chǎn)品族:蘋果手機(jī),蘋果電腦
  • 小米產(chǎn)品族:小米手機(jī),小米電腦
  • 華為產(chǎn)品族:華為手機(jī),華為電腦
  • 而抽象方法恰恰是面向產(chǎn)品族設(shè)計(jì)的,因此該場(chǎng)景適合使用的是抽象工廠方法。下面結(jié)合代碼來(lái)看一下該如何設(shè)計(jì)。

    代碼實(shí)現(xiàn)

    首先引入電腦的基類和各個(gè)品牌的電腦類:

    電腦基類:

    //================== Computer.h ================== @interface Computer : NSObject//package to store - (void)packaging;@end//================== Computer.m ================== @implementation Computer- (void)packaging{//implemented by subclass }@end

    蘋果電腦類?MacBookComputer:

    //================== MacBookComputer.h ================== @interface MacBookComputer : Computer@end//================== MacBookComputer.m ================== @implementation MacBookComputer- (void)packaging{NSLog(@"MacBookComputer has been packaged"); }@end

    小米電腦類?MIComputer:

    //================== MIComputer.h ================== @interface MIComputer : Computer@end//================== MIComputer.m ================== @implementation MIComputer- (void)packaging{NSLog(@"MIComputer has been packaged"); }@end

    華為電腦類?MateBookComputer:

    //================== MateBookComputer.h ================== @interface MateBookComputer : Computer@end//================== MateBookComputer.m ================== @implementation MateBookComputer- (void)packaging{NSLog(@"MateBookComputer has been packaged"); }@end

    引入電腦相關(guān)產(chǎn)品類以后,我們需要重新設(shè)計(jì)工廠類。因?yàn)槌橄蠊S方法模式的工廠是面向產(chǎn)品族的,所以抽象工廠方法模式里的工廠所創(chuàng)建的是同一產(chǎn)品族的產(chǎn)品。下面我們看一下抽象工廠方法模式的工廠該如何設(shè)計(jì):

    首先創(chuàng)建所有工廠都需要集成的抽象工廠,它聲明了生產(chǎn)同一產(chǎn)品族的所有產(chǎn)品的接口:

    //================== Factory.h ================== #import "Phone.h" #import "Computer.h"@interface Factory : NSObject+ (Phone *)createPhone;+ (Computer *)createComputer;@end//================== Factory.m ================== @implementation Factory+ (Phone *)createPhone{//implemented by subclassreturn nil; }+ (Computer *)createComputer{//implemented by subclassreturn nil; }@end

    接著,根據(jù)不同的產(chǎn)品族,我們創(chuàng)建不同的具體工廠:

    首先是蘋果產(chǎn)品族工廠?AppleFactory:

    //================== AppleFactory.h ================== @interface AppleFactory : Factory@end//================== AppleFactory.m ================== #import "IPhone.h" #import "MacBookComputer.h"@implementation AppleFactory+ (Phone *)createPhone{IPhone *iPhone = [[IPhone alloc] init];NSLog(@"iPhone has been created");return iPhone; }+ (Computer *)createComputer{MacBookComputer *macbook = [[MacBookComputer alloc] init];NSLog(@"Macbook has been created");return macbook; }@end

    接著是小米產(chǎn)品族工廠?MIFactory:

    //================== MIFactory.h ================== @interface MIFactory : Factory@end//================== MIFactory.m ================== #import "MIPhone.h" #import "MIComputer.h"@implementation MIFactory+ (Phone *)createPhone{MIPhone *miPhone = [[MIPhone alloc] init];NSLog(@"MIPhone has been created");return miPhone; }+ (Computer *)createComputer{MIComputer *miComputer = [[MIComputer alloc] init];NSLog(@"MIComputer has been created");return miComputer; }@end

    最后是華為產(chǎn)品族工廠?HWFactory:

    //================== HWFactory.h ================== @interface HWFactory : Factory@end//================== HWFactory.m ================== #import "HWPhone.h" #import "MateBookComputer.h"@implementation HWFactory+ (Phone *)createPhone{HWPhone *hwPhone = [[HWPhone alloc] init];NSLog(@"HWPhone has been created");return hwPhone; }+ (Computer *)createComputer{MateBookComputer *hwComputer = [[MateBookComputer alloc] init];NSLog(@"HWComputer has been created");return hwComputer; }@end

    以上就是工廠類的設(shè)計(jì)。這樣設(shè)計(jì)好之后,客戶端如果需要哪一產(chǎn)品族的某個(gè)產(chǎn)品的話,只需要找到對(duì)應(yīng)產(chǎn)品族工廠后,調(diào)用生產(chǎn)該產(chǎn)品的接口即可。假如需要蘋果電腦,只需要委托蘋果工廠來(lái)制造蘋果電腦即可;如果需要小米手機(jī),只需要委托小米工廠制造小米手機(jī)即可。

    下面用代碼來(lái)模擬一下這個(gè)場(chǎng)景:

    //================== Using by client ==================Store *store = [[Store alloc] init];//Store wants to sell MacBook Computer *macBook = [AppleFactory createComputer]; [macBook packaging];[store sellComputer:macBook];//Store wants to sell MIPhone Phone *miPhone = [MIFactory createPhone]; [miPhone packaging];[store sellPhone:miPhone];//Store wants to sell MateBook Computer *mateBook = [HWFactory createComputer]; [mateBook packaging];[store sellComputer:mateBook];

    上面的代碼就是模擬了商店售賣蘋果電腦,小米手機(jī),華為電腦的場(chǎng)景。而今后如果該商店引入了新品牌的產(chǎn)品,比如聯(lián)想手機(jī),聯(lián)想電腦,那么我們只需要新增聯(lián)想手機(jī)類,聯(lián)想電腦類,聯(lián)想工廠類即可。

    下面我們看一下該例子對(duì)應(yīng)的 UML類圖,可以更直觀地看一下各個(gè)成員之間的關(guān)系:

    代碼對(duì)應(yīng)的類圖

    由于三個(gè)工廠的產(chǎn)品總數(shù)過(guò)多,因此在這里只體現(xiàn)了蘋果工廠和小米工廠的產(chǎn)品。

    優(yōu)點(diǎn)

    • 具體產(chǎn)品在應(yīng)用層代碼隔離,不需要關(guān)心產(chǎn)品細(xì)節(jié)。只需要知道自己需要的產(chǎn)品是屬于哪個(gè)工廠的即可
      當(dāng)一個(gè)產(chǎn)品族中的多個(gè)對(duì)象被設(shè)計(jì)成一起工作時(shí),它能夠保證客戶端始終只使用同一個(gè)產(chǎn)品族中的對(duì)象。這對(duì)一些需要根據(jù)當(dāng)前環(huán)境來(lái)決定其行為的軟件系統(tǒng)來(lái)說(shuō),是一種非常實(shí)用的設(shè)計(jì)模式。

    缺點(diǎn)

    • 規(guī)定了所有可能被創(chuàng)建的產(chǎn)品集合,產(chǎn)品族中擴(kuò)展新的產(chǎn)品困難,需要修改抽象工廠的接口。
    • 新增產(chǎn)品等級(jí)比較困難
    • 產(chǎn)品等級(jí)固定,而產(chǎn)品族不固定,擴(kuò)展性強(qiáng)的場(chǎng)景。

    iOS SDK 和 JDK 中的應(yīng)用

    • 暫未發(fā)現(xiàn)iOS SDK中使用抽象工廠方法的例子,有知道的小伙伴歡迎留言。
    • JDK中有一個(gè)數(shù)據(jù)庫(kù)連接的接口Connection。在這個(gè)接口里面有createStatement()和prepareStatement(String sql)。這兩個(gè)接口都是獲取的統(tǒng)一產(chǎn)品族的對(duì)象,比如MySql和PostgreSQL產(chǎn)品族,具體返回的是哪個(gè)產(chǎn)品族對(duì)象,取決于所連接的數(shù)據(jù)庫(kù)類型。

    OK,到現(xiàn)在三個(gè)工廠模式已經(jīng)講完了。在繼續(xù)講解下面三個(gè)設(shè)計(jì)模式之前,先簡(jiǎn)單回顧一下上面講解的三個(gè)工廠模式:

    大體上看,簡(jiǎn)單工廠模式,工廠方法模式和抽象工廠模式的復(fù)雜程度是逐漸升高的。

    • 簡(jiǎn)單工廠模式使用不同的入?yún)?lái)讓同一個(gè)工廠生產(chǎn)出不同的產(chǎn)品。
    • 工廠方法模式和抽象工廠模式都需要有特定的工廠類來(lái)生產(chǎn)對(duì)應(yīng)的產(chǎn)品;而工廠方法模式里的工廠是面向同一產(chǎn)品等級(jí)的產(chǎn)品;而抽象工廠方法模式里的工廠是面向同一產(chǎn)品族的產(chǎn)品的。

    在實(shí)際開發(fā)過(guò)程中,我們需要根據(jù)業(yè)務(wù)場(chǎng)景的復(fù)雜程度的不同來(lái)采用最適合的工廠模式。

    四. 單例模式

    定義

    單例模式(Singleton Pattern):單例模式確保某一個(gè)類只有一個(gè)實(shí)例,并提供一個(gè)訪問(wèn)它的全劇訪問(wèn)點(diǎn)。

    適用場(chǎng)景

    系統(tǒng)只需要一個(gè)實(shí)例對(duì)象,客戶調(diào)用類的單個(gè)實(shí)例只允許使用一個(gè)公共訪問(wèn)點(diǎn),除了該公共訪問(wèn)點(diǎn),不能通過(guò)其他途徑訪問(wèn)該實(shí)例。比較典型的例子是音樂(lè)播放器,日志系統(tǒng)類等等。

    成員與類圖

    成員

    單例模式只有一個(gè)成員,就是單例類。因?yàn)橹挥幸粋€(gè)成員,所以該設(shè)計(jì)模式的類圖比較簡(jiǎn)單:

    模式類圖

    一般來(lái)說(shuō)單例類會(huì)給外部提供一個(gè)獲取單例對(duì)象的方法,內(nèi)部會(huì)用靜態(tài)對(duì)象的方式保存這個(gè)對(duì)象。

    代碼示例

    場(chǎng)景概述

    在這里我們創(chuàng)建一個(gè)簡(jiǎn)單的打印日至或上報(bào)日至的日至管理單例。

    場(chǎng)景分析

    在創(chuàng)建單例時(shí),除了要保證提供唯一實(shí)例對(duì)象以外,還需注意多線程的問(wèn)題。下面用代碼來(lái)看一下。

    代碼實(shí)現(xiàn)

    創(chuàng)建單例類?LogManager

    //================== LogManager.h ================== @interface LogManager : NSObject+(instancetype)sharedInstance;- (void)printLog:(NSString *)logMessage;- (void)uploadLog:(NSString *)logMessage;@end//================== LogManager.m ================== @implementation LogManagerstatic LogManager* _sharedInstance = nil;+(instancetype)sharedInstance {static dispatch_once_t onceToken ;dispatch_once(&onceToken, ^{_sharedInstance = [[super allocWithZone:NULL] init] ;}) ;return _sharedInstance ; }+(id)allocWithZone:(struct _NSZone *)zone {return [LogManager sharedInstance] ; }-(id)copyWithZone:(struct _NSZone *)zone {return [LogManager sharedInstance]; }-(id)mutableCopyWithZone:(NSZone *)zone {return [LogManager sharedInstance]; }- (void)printLog:(NSString *)logMessage{//print logMessage }- (void)uploadLog:(NSString *)logMessage{//upload logMessage }@end

    從上面的代碼中可以看到:

    • sharedInstance方法是向外部提供的獲取唯一的實(shí)例對(duì)象的方法,也是該類中的其他可以創(chuàng)建對(duì)象的方法的都調(diào)用的方法。在這個(gè)方法內(nèi)部使用了dispatch_once函數(shù)來(lái)避免多線程訪問(wèn)導(dǎo)致創(chuàng)建多個(gè)實(shí)例的情況。
    • 為了在alloc init出初始化方法可以返回同一個(gè)實(shí)例對(duì)象,在allocWithZone:方法里面仍然調(diào)用了sharedInstance方法。
    • 而且為了在copy和mutableCopy方法也可以返回同一個(gè)實(shí)例對(duì)象,在copyWithZone:與mutableCopyWithZone也是調(diào)用了sharedInstance方法。

    下面分別用這些接口來(lái)驗(yàn)證一下實(shí)例的唯一性:

    //================== Using by client ==================//alloc&init LogManager *manager0 = [[LogManager alloc] init];//sharedInstance LogManager *manager1 = [LogManager sharedInstance];//copy LogManager *manager2 = [manager0 copy];//mutableCopy LogManager *manager3 = [manager1 mutableCopy];NSLog(@"\nalloc&init: %p\nsharedInstance: %p\ncopy: %p\nmutableCopy: %p",manager0,manager1,manager2,manager3);

    我們看一下打印出來(lái)的四個(gè)指針?biāo)赶驅(qū)ο蟮牡刂?#xff1a;

    alloc&init: 0x60000000f7e0 sharedInstance: 0x60000000f7e0 copy: 0x60000000f7e0 mutableCopy: 0x60000000f7e0

    可以看出打印出來(lái)的地址都相同,說(shuō)明都是同一對(duì)象,證明了實(shí)現(xiàn)方法的正確性。

    下面我們看一下該例子對(duì)應(yīng)的 UML類圖,可以更直觀地看一下各個(gè)成員之間的關(guān)系:

    代碼對(duì)應(yīng)的類圖

    優(yōu)點(diǎn)

    • 提供了對(duì)唯一實(shí)例的受控訪問(wèn)。因?yàn)閱卫惙庋b了它的唯一實(shí)例,所以它可以嚴(yán)格控制客戶怎樣以及何時(shí)訪問(wèn)它。
    • 因?yàn)樵擃愒谙到y(tǒng)內(nèi)存中只存在一個(gè)對(duì)象,所以可以節(jié)約系統(tǒng)資源。

    缺點(diǎn)

    • 由于單例模式中沒(méi)有抽象層,因此單例類很難進(jìn)行擴(kuò)展。
    • 對(duì)于有垃圾回收系統(tǒng)的語(yǔ)言(Java,C#)來(lái)說(shuō),如果對(duì)象長(zhǎng)時(shí)間不被利用,則可能會(huì)被回收。那么如果這個(gè)單例持有一些數(shù)據(jù)的話,在回收后重新實(shí)例化時(shí)就不復(fù)存在了。

    iOS SDK 和 JDK 中的應(yīng)用

    • 在Objective-C語(yǔ)言中使用單例模式的類有NSUserDefaults(key-value持久化)和UIApplication類(代表應(yīng)用程序,可以處理一些點(diǎn)擊事件等)。
    • 在JDK中使用的單例模式的類有Runtime類(代表應(yīng)用程序的運(yùn)行環(huán)境,使應(yīng)用程序能夠與其運(yùn)行的環(huán)境相連接);Desktop類(允許 Java 應(yīng)用程序啟動(dòng)已在本機(jī)桌面上注冊(cè)的關(guān)聯(lián)應(yīng)用程序)

    五. 生成器模式

    定義

    生成器模式(Builder Pattern):也叫創(chuàng)建者模式,它將一個(gè)復(fù)雜對(duì)象的構(gòu)建與它的表示分離,使得同樣的構(gòu)建過(guò)程可以創(chuàng)建不同的表示。

    具體點(diǎn)說(shuō)就是:有些對(duì)象的創(chuàng)建流程是一樣的,但是因?yàn)樽陨硖匦缘牟煌?#xff0c;所以在創(chuàng)建他們的時(shí)候需要將創(chuàng)建過(guò)程和特性的定制分離開來(lái)。

    下面我們看一下該設(shè)計(jì)模式的適用場(chǎng)景。

    適用場(chǎng)景

    當(dāng)創(chuàng)建復(fù)雜對(duì)象的算法應(yīng)該獨(dú)立于該對(duì)象的組成部分以及它們的裝配方式時(shí)比較適合使用生成器模式。

    一些復(fù)雜的對(duì)象,它們擁有多個(gè)組成部分(如汽車,它包括車輪、方向盤、發(fā)送機(jī)等各種部件)。而對(duì)于大多數(shù)用戶而言,無(wú)須知道這些部件的裝配細(xì)節(jié),也幾乎不會(huì)使用單獨(dú)某個(gè)部件,而是使用一輛完整的汽車。而且這些部分的創(chuàng)建順序是固定的,或者是需要指定的。

    在這種情況下可以通過(guò)建造者模式對(duì)其進(jìn)行設(shè)計(jì)與描述,生成器模式可以將部件和其組裝過(guò)程分開,一步一步創(chuàng)建一個(gè)復(fù)雜的對(duì)象。

    成員與類圖

    成員

    建造者模式包含4個(gè)成員:

  • 抽象建造者(Builder):定義構(gòu)造產(chǎn)品的幾個(gè)公共方法。
  • 具體建造者(ConcreteBuilder):根據(jù)不同的需求來(lái)實(shí)現(xiàn)抽象建造者定義的公共方法;每一個(gè)具體建造者都包含一個(gè)產(chǎn)品對(duì)象作為它的成員變量。
  • 指揮者(Director):根據(jù)傳入的具體建造者來(lái)返回其所對(duì)應(yīng)的產(chǎn)品對(duì)象。
  • 產(chǎn)品角色(Product):創(chuàng)建的產(chǎn)品。
  • 下面通過(guò)類圖來(lái)看一下各個(gè)成員之間的關(guān)系:

    模式類圖

    需要注意的是:

    • Builder類中的product成員變量的關(guān)鍵字為protected,目的是為了僅讓它和它的子類可以訪問(wèn)該成員變量。
    • Director類中的constructProductWithBuilder(Builder builder)方法是通過(guò)傳入不同的builder來(lái)構(gòu)造產(chǎn)品的。而且它的getProduct()方法同時(shí)也封裝了Concrete Builder類的getProduct()方法,目的是為了讓客戶端直接從Director拿到對(duì)應(yīng)的產(chǎn)品(有些資料里面的Director類沒(méi)有封裝Concrete Builder類的getProduct()方法)。

    代碼示例

    場(chǎng)景概述

    模擬一個(gè)制造手機(jī)的場(chǎng)景:手機(jī)的組裝需要幾個(gè)固定的零件:CPU,RAM,屏幕,攝像頭,而且需要CPU -> RAM ->屏幕 -> 攝像頭的順序來(lái)制造。

    場(chǎng)景分析

    我們使用建造者設(shè)計(jì)模式來(lái)實(shí)現(xiàn)這個(gè)場(chǎng)景:首先不同的手機(jī)要匹配不同的builder;然后在Director類里面來(lái)定義制造順序。

    代碼實(shí)現(xiàn)

    首先我們定義手機(jī)這個(gè)類,它有幾個(gè)屬性:

    //================== Phone.h ================== @interface Phone : NSObject@property (nonatomic, copy) NSString *cpu; @property (nonatomic, copy) NSString *capacity; @property (nonatomic, copy) NSString *display; @property (nonatomic, copy) NSString *camera;@end

    然后我們創(chuàng)建抽象builder類:

    //================== Builder.h ================== #import "Phone.h"@interface Builder : NSObject {@protected Phone *_phone; }- (void)createPhone;- (void)buildCPU; - (void)buildCapacity; - (void)buildDisplay; - (void)buildCamera;- (Phone *)obtainPhone;@end

    抽象builder類聲明了創(chuàng)建手機(jī)各個(gè)組件的接口,也提供了返回手機(jī)實(shí)例的對(duì)象。

    接下來(lái)我們創(chuàng)建對(duì)應(yīng)不同手機(jī)的具體生成者類:

    IPhoneXR手機(jī)的builder:IPhoneXRBuilder:

    //================== IPhoneXRBuilder.h ================== @interface IPhoneXRBuilder : Builder@end//================== IPhoneXRBuilder.m ================== @implementation IPhoneXRBuilder- (void)createPhone{_phone = [[Phone alloc] init]; }- (void)buildCPU{[_phone setCpu:@"A12"]; }- (void)buildCapacity{[_phone setCapacity:@"256"]; }- (void)buildDisplay{[_phone setDisplay:@"6.1"]; }- (void)buildCamera{[_phone setCamera:@"12MP"]; }- (Phone *)obtainPhone{return _phone; }@end

    小米8手機(jī)的builder:MI8Builder:

    //================== MI8Builder.h ================== @interface MI8Builder : Builder@end//================== MI8Builder.m ================== @implementation MI8Builder- (void)createPhone{_phone = [[Phone alloc] init]; }- (void)buildCPU{[_phone setCpu:@"Snapdragon 845"]; }- (void)buildCapacity{[_phone setCapacity:@"128"]; }- (void)buildDisplay{[_phone setDisplay:@"6.21"]; }- (void)buildCamera{[_phone setCamera:@"12MP"]; }- (Phone *)obtainPhone{return _phone; }@end

    從上面兩個(gè)具體builder的代碼可以看出,這兩個(gè)builder都按照其對(duì)應(yīng)的手機(jī)配置來(lái)創(chuàng)建其對(duì)應(yīng)的手機(jī)。

    下面來(lái)看一下Director的用法:

    //================== Director.h ================== #import "Builder.h"@interface Director : NSObject- (void)constructPhoneWithBuilder:(Builder *)builder;- (Phone *)obtainPhone;@end//================== Director.m ================== implementation Director {Builder *_builder; }- (void)constructPhoneWithBuilder:(Builder *)builder{_builder = builder;[_builder buildCPU];[_builder buildCapacity];[_builder buildDisplay];[_builder buildCamera];}- (Phone *)obtainPhone{return [_builder obtainPhone]; }@end

    Director類提供了construct:方法,需要傳入builder的實(shí)例。該方法里面按照既定的順序來(lái)創(chuàng)建手機(jī)。

    最后我們看一下客戶端是如何使用具體的Builder和Director實(shí)例的:

    //================== Using by client ==================//Get iPhoneXR //1\. A director instance Director *director = [[Director alloc] init];//2\. A builder instance IPhoneXRBuilder *iphoneXRBuilder = [[IPhoneXRBuilder alloc] init];//3\. Construct phone by director [director construct:iphoneXRBuilder];//4\. Get phone by builder Phone *iPhoneXR = [iphoneXRBuilder obtainPhone]; NSLog(@"Get new phone iPhoneXR of data: %@",iPhoneXR);//Get MI8 MI8Builder *mi8Builder = [[MI8Builder alloc] init]; [director construct:mi8Builder]; Phone *mi8 = [mi8Builder obtainPhone]; NSLog(@"Get new phone MI8 of data: %@",mi8);

    從上面可以看出客戶端獲取具體產(chǎn)品的過(guò)程:

  • 首先需要實(shí)例化一個(gè)Director的實(shí)例。
  • 然后根據(jù)所需要的產(chǎn)品找出其對(duì)應(yīng)的builder。
  • 將builder傳入director實(shí)例的construct:方法。
  • 從builder的obtainPhone獲取手機(jī)實(shí)例。
  • 下面我們看一下該例子對(duì)應(yīng)的 UML類圖,可以更直觀地看一下各個(gè)成員之間的關(guān)系:

    代碼對(duì)應(yīng)的類圖

    優(yōu)點(diǎn)

    • 客戶端不必知道產(chǎn)品內(nèi)部組成的細(xì)節(jié),將產(chǎn)品本身與產(chǎn)品的創(chuàng)建過(guò)程解耦,使得相同的創(chuàng)建過(guò)程可以創(chuàng)建不同的產(chǎn)品對(duì)象。
    • 每一個(gè)具體建造者都相對(duì)獨(dú)立,而與其他的具體建造者無(wú)關(guān),因此可以很方便地替換具體建造者或增加新的具體建造者, 用戶使用不同的具體建造者即可得到不同的產(chǎn)品對(duì)象 。
    • 增加新的具體建造者無(wú)須修改原有類庫(kù)的代碼,指揮者類針對(duì)抽象建造者類編程,系統(tǒng)擴(kuò)展方便,符合“開閉原則”。
    • 可以更加精細(xì)地控制產(chǎn)品的創(chuàng)建過(guò)程 。將復(fù)雜產(chǎn)品的創(chuàng)建步驟分解在不同的方法中,使得創(chuàng)建過(guò)程更加清晰,也更方便使用程序來(lái)控制創(chuàng)建過(guò)程。

    缺點(diǎn)

    • 建造者模式所創(chuàng)建的產(chǎn)品一般具有較多的共同點(diǎn),其組成部分相似,如果產(chǎn)品之間的差異性很大,則不適合使用建造者模式,因此其使用范圍受到一定的限制。

    • 如果產(chǎn)品的內(nèi)部變化復(fù)雜,可能會(huì)導(dǎo)致需要定義很多具體建造者類來(lái)實(shí)現(xiàn)這種變化,導(dǎo)致系統(tǒng)變得很龐大。

    iOS SDK 和 JDK 中的應(yīng)用

    • 暫未發(fā)現(xiàn)iOS SDK中使用生成器設(shè)計(jì)模式的例子,有知道的小伙伴歡迎留言。
    • JDK中的StringBuilder屬于builder,它向外部提供append(String)方法來(lái)拼接字符串(也可以傳入int等其他類型);而toString()方法來(lái)返回字符串。

    六. 原型模式

    定義

    原型模式(Prototype Pattern): 使用原型實(shí)例指定待創(chuàng)建對(duì)象的類型,并且通過(guò)復(fù)制這個(gè)原型來(lái)創(chuàng)建新的對(duì)象。

    適用場(chǎng)景

    • 對(duì)象層級(jí)嵌套比較多,從零到一創(chuàng)建對(duì)象的過(guò)程比較繁瑣時(shí),可以直接通過(guò)復(fù)制的方式創(chuàng)建新的對(duì)象

    • 當(dāng)一個(gè)類的實(shí)例只能有幾個(gè)不同狀態(tài)組合中的一種時(shí),我們可以利用已有的對(duì)象進(jìn)行復(fù)制來(lái)獲得

    成員與類圖

    成員

    原型模式主要包含如下兩個(gè)角色:

  • 抽象原型類(Prototype):抽象原型類聲明克隆自身的接口。
  • 具體原型類(ConcretePrototype):具體原型類實(shí)現(xiàn)克隆的具體操作(克隆數(shù)據(jù),狀態(tài)等)。
  • 下面通過(guò)類圖來(lái)看一下各個(gè)成員之間的關(guān)系:

    模式類圖

    需要注意的是,這里面的clone()方法返回的是被復(fù)制出來(lái)的實(shí)例對(duì)象。

    代碼示例

    場(chǎng)景概述

    模擬一份校招的簡(jiǎn)歷,簡(jiǎn)歷里面有人名,性別,年齡以及學(xué)歷相關(guān)的信息。這里面學(xué)歷相關(guān)的信息又包含學(xué)校名稱,專業(yè),開始和截止年限的信息。

    場(chǎng)景分析

    這里的學(xué)歷相關(guān)信息可以使用單獨(dú)一個(gè)對(duì)象來(lái)做,因此整體的簡(jiǎn)歷對(duì)象的結(jié)構(gòu)可以是:

    簡(jiǎn)歷對(duì)象:

    • 人名
    • 性別
    • 年齡
    • 學(xué)歷對(duì)象
      • 學(xué)校名稱
      • 專業(yè)
      • 開始年份
      • 結(jié)束年份

    而且因?yàn)閷?duì)于同一學(xué)校同一屆的同一專業(yè)的畢業(yè)生來(lái)說(shuō),學(xué)歷對(duì)象中的信息是相同的,這時(shí)候如果需要大量生成這些畢業(yè)生的簡(jiǎn)歷的話比較適合使用原型模式。

    代碼實(shí)現(xiàn)

    首先定義學(xué)歷對(duì)象:

    //================== UniversityInfo.h ================== @interface UniversityInfo : NSObject<NSCopying>@property (nonatomic, copy) NSString *universityName; @property (nonatomic, copy) NSString *startYear; @property (nonatomic, copy) NSString *endYear; @property (nonatomic, copy) NSString *major;- (id)copyWithZone:(NSZone *)zone;@end//================== UniversityInfo.m ================== @implementation UniversityInfo- (id)copyWithZone:(NSZone *)zone {UniversityInfo *infoCopy = [[[self class] allocWithZone:zone] init];[infoCopy setUniversityName:[_universityName mutableCopy]];[infoCopy setStartYear:[_startYear mutableCopy]];[infoCopy setEndYear:[_endYear mutableCopy]];[infoCopy setMajor:[_major mutableCopy]];return infoCopy; }@end

    因?yàn)閷W(xué)歷對(duì)象是支持復(fù)制的,因此需要遵從<NSCopying>協(xié)議并實(shí)現(xiàn)copyWithZone:方法。而且支持的是深復(fù)制,所以在復(fù)制NSString的過(guò)程中需要使用mutableCopy來(lái)實(shí)現(xiàn)。

    接著我們看一下簡(jiǎn)歷對(duì)象:

    //================== Resume.h ================== #import "UniversityInfo.h"@interface Resume : NSObject<NSCopying>@property (nonatomic, copy) NSString *name; @property (nonatomic, copy) NSString *gender; @property (nonatomic, copy) NSString *age;@property (nonatomic, strong) UniversityInfo *universityInfo;@end//================== Resume.m ================== @implementation Resume- (id)copyWithZone:(NSZone *)zone {Resume *resumeCopy = [[[self class] allocWithZone:zone] init];[resumeCopy setName:[_name mutableCopy]];[resumeCopy setGender:[_gender mutableCopy]];[resumeCopy setAge:[_age mutableCopy]];[resumeCopy setUniversityInfo:[_universityInfo copy]];return resumeCopy; }@end

    同樣地,簡(jiǎn)歷對(duì)象也需要遵從<NSCopying>協(xié)議并實(shí)現(xiàn)copyWithZone:方法。

    最后我們看一下復(fù)制的效果有沒(méi)有達(dá)到我們的預(yù)期(被復(fù)制對(duì)象和復(fù)制對(duì)象的地址和它們所有的屬性對(duì)象的地址都不相同)

    //================== Using by client ==================//resume for LiLei Resume *resume = [[Resume alloc] init]; resume.name = @"LiLei"; resume.gender = @"male"; resume.age = @"24";UniversityInfo *info = [[UniversityInfo alloc] init]; info.universityName = @"X"; info.startYear = @"2014"; info.endYear = @"2018"; info.major = @"CS";resume.universityInfo = info;//resume_copy for HanMeiMei Resume *resume_copy = [resume copy];NSLog(@"\n\n\n======== original resume ======== %@\n\n\n======== copy resume ======== %@",resume,resume_copy);resume_copy.name = @"HanMeiMei"; resume_copy.gender = @"female"; resume_copy.universityInfo.major = @"TeleCommunication";NSLog(@"\n\n\n======== original resume ======== %@\n\n\n======== revised copy resume ======== %@",resume,resume_copy);

    上面的代碼模擬了這樣一個(gè)場(chǎng)景:李雷同學(xué)寫了一份自己的簡(jiǎn)歷,然后韓梅梅復(fù)制了一份并修改了姓名,性別和專業(yè)這三個(gè)和李雷不同的信息。

    這里我們重寫了Resume的description方法來(lái)看一下所有屬性的值及其內(nèi)存地址。最后來(lái)看一下resume對(duì)象和resume_copy對(duì)象打印的結(jié)果:

    //================== Output log ========================== original resume ======== resume object address:0x604000247d10 name:LiLei | 0x10bc0c0b0 gender:male | 0x10bc0c0d0 age:24 | 0x10bc0c0f0 university name:X| 0x10bc0c110 university start year:2014 | 0x10bc0c130 university end year:2018 | 0x10bc0c150 university major:CS | 0x10bc0c170======== copy resume ======== resume object address:0x604000247da0 name:LiLei | 0xa000069654c694c5 gender:male | 0xa000000656c616d4 age:24 | 0xa000000000034322 university name:X| 0xa000000000000581 university start year:2014 | 0xa000000343130324 university end year:2018 | 0xa000000383130324 university major:CS | 0xa000000000053432======== original resume ======== resume object address:0x604000247d10 name:LiLei | 0x10bc0c0b0 gender:male | 0x10bc0c0d0 age:24 | 0x10bc0c0f0 university name:X| 0x10bc0c110 university start year:2014 | 0x10bc0c130 university end year:2018 | 0x10bc0c150 university major:CS | 0x10bc0c170======== revised copy resume ======== resume object address:0x604000247da0 name:HanMeiMei | 0x10bc0c1b0 gender:female | 0x10bc0c1d0 age:24 | 0xa000000000034322 university name:X| 0xa000000000000581 university start year:2014 | 0xa000000343130324 university end year:2018 | 0xa000000383130324 university major:TeleCommunication | 0x10bc0c1f0
    • 上面兩個(gè)是原resume和剛被復(fù)制后的 copy resume的信息,可以看出來(lái)無(wú)論是這兩個(gè)對(duì)象的地址還是它們的值對(duì)應(yīng)的地址都是不同的,說(shuō)明成功地實(shí)現(xiàn)了深復(fù)制。
    • 下面兩個(gè)是原resume和被修改后的 copy_resume的信息,可以看出來(lái)新的copy_resume的值發(fā)生了變化,而且值所對(duì)應(yīng)的地址還是和原resume的不同。

    注:還可以用序列化和反序列化的辦法來(lái)實(shí)現(xiàn)深復(fù)制,因?yàn)榕c代碼設(shè)計(jì)上不是很復(fù)雜,很多語(yǔ)言直接提供了接口,故這里不做介紹。

    下面我們看一下該例子對(duì)應(yīng)的 UML類圖,可以更直觀地看一下各個(gè)成員之間的關(guān)系:

    代碼對(duì)應(yīng)的類圖

    在這里需要注意的是:

    • copy方法是NSObject類提供的復(fù)制本對(duì)象的接口。NSObject類似于Java中的Object類,在Objective-C中幾乎所有的對(duì)象都繼承與它。而且這個(gè)copy方法也類似于Object類的clone()方法。
    • copyWithZone(NSZone zone)方法是接口NSCopying提供的接口。而因?yàn)檫@個(gè)接口存在于實(shí)現(xiàn)文件而不是頭文件,所以它不是對(duì)外公開的;即是說(shuō)外部無(wú)法直接調(diào)用copyWithZone(NSZone zone)方法。copyWithZone(NSZone zone)方法是在上面所說(shuō)的copy方法調(diào)用后再調(diào)用的,作用是將對(duì)象的所有數(shù)據(jù)都進(jìn)行復(fù)制。因此使用者需要在copyWithZone(NSZone zone)方法里做工作,而不是copy方法,這一點(diǎn)和Java的clone方法不同。

    優(yōu)點(diǎn)

    • 可以利用原型模式簡(jiǎn)化對(duì)象的創(chuàng)建過(guò)程,尤其是對(duì)一些創(chuàng)建過(guò)程繁瑣,包含對(duì)象層級(jí)比較多的對(duì)象來(lái)說(shuō),使用原型模式可以節(jié)約系統(tǒng)資源,提高對(duì)象生成的效率。
    • 可以很方便得通過(guò)改變值來(lái)生成新的對(duì)象:有些對(duì)象之間的差別可能只在于某些值的不同;用原型模式可以快速?gòu)?fù)制出新的對(duì)象并手動(dòng)修改值即可。

    缺點(diǎn)

    • 對(duì)象包含的所有對(duì)象都需要配備一個(gè)克隆的方法,這就使得在對(duì)象層級(jí)比較多的情況下,代碼量會(huì)很大,也更加復(fù)雜。

    iOS SDK 和 JDK 中的應(yīng)用

    • Objective-C中可以使用<NSCopying>?協(xié)議,配合- (id)copyWithZone:(NSZone *)zone方法; 或者<NSMutableCopying>協(xié)議,配合?copyWithZone:/mutableCopyWithZone:方法
    • Java中可以讓一個(gè)類實(shí)現(xiàn)Cloneable接口并實(shí)現(xiàn)clone()方法來(lái)復(fù)制該類的實(shí)例。

    到這里設(shè)計(jì)模式中的創(chuàng)建型模式就介紹完了,讀者可以結(jié)合UML類圖和demo的代碼來(lái)理解每個(gè)設(shè)計(jì)模式的特點(diǎn)和相互之間的區(qū)別,希望讀者可以有所收獲。

    另外,本篇博客的代碼和類圖都保存在我的GitHub庫(kù)中:中的Chapter2。

    總結(jié)

    以上是生活随笔為你收集整理的iOS开发 - 面向对象设计的设计模式(一):创建型模式(附 Demo UML类图)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。