六大設(shè)計(jì)原則
單一職責(zé)原則、開閉原則、依賴倒置原則、里氏替換原則、接口隔離原則、迪米特法則
單一職責(zé)原則
一個(gè)類只負(fù)責(zé)一件事
開閉原則
對(duì)修改關(guān)閉、對(duì)擴(kuò)展開放
接口隔離原則
使用多個(gè)專門的協(xié)議、而不是一個(gè)龐大臃腫的協(xié)議
協(xié)議中的方法應(yīng)當(dāng)盡量少
依賴倒置原則
抽象不應(yīng)該依賴于具體實(shí)現(xiàn),具體實(shí)現(xiàn)可以依賴于抽象
里氏替換原則
父類可以被子類無縫替換,且原有功能不受任何影響
(KVO使用了此種原則,用runtime生成了一個(gè)新的子類NSKVONotifying_類名)
迪米特法則
一個(gè)對(duì)象對(duì)其他對(duì)象有盡可能少的了解
高內(nèi)聚、低耦合
一、責(zé)任鏈模式
使用處理請(qǐng)求的模式,它是讓多個(gè)處理器都有機(jī)會(huì)處理該請(qǐng)求,知道其中某個(gè)處理成功為止,責(zé)任鏈模式把多個(gè)處理器串成鏈,然后讓請(qǐng)求在鏈傳遞。如果當(dāng)前的處理器無法處理則交給下一個(gè)處理器處理。
一個(gè)關(guān)于需求變更的問題?
加入剛開始業(yè)務(wù)邏輯是 業(yè)務(wù)A->業(yè)務(wù)B->業(yè)務(wù)C
后因?yàn)樾枨笞兏{(diào)用邏輯變?yōu)? 業(yè)務(wù)C->業(yè)務(wù)B->業(yè)務(wù)A,這里就要用到責(zé)任鏈模式了
責(zé)任鏈模式的類構(gòu)成
Abstract Class --->成員變量(Abstract Class)
業(yè)務(wù)類中有一個(gè)當(dāng)前類型的成員變量,而具體的事件處理交由具體的業(yè)務(wù)對(duì)象去處理的。
iOS中責(zé)任鏈的使用
iOS中有許多情況使用到了責(zé)任鏈模式,最典型的就是響應(yīng)鏈,一層層的往下傳遞事件,尋找可以處理事件的對(duì)象
下面看下抽象基類的代碼實(shí)現(xiàn)
BusinessObject.h頭文件
#import <Foundation/Foundation.h>
@class BusinessObject;
typedef void(^CompletionBlock)(BOOL handled);
typedef void(^ResultBlock)(BusinessObject *handler, BOOL handled);
@interface BusinessObject : NSObject
// 下一個(gè)響應(yīng)者(響應(yīng)鏈構(gòu)成的關(guān)鍵)
@property (nonatomic, strong) BusinessObject *nextBusiness;
// 響應(yīng)者的處理方法
- (void)handle:(ResultBlock)result;
// 各個(gè)業(yè)務(wù)在該方法當(dāng)中做實(shí)際業(yè)務(wù)處理
- (void)handleBusiness:(CompletionBlock)completion;
@end
BusinessObject.m具體實(shí)現(xiàn)
@implementation BusinessObject
// 責(zé)任鏈入口方法
- (void)handle:(ResultBlock)result
{
CompletionBlock completion = ^(BOOL handled){
// 當(dāng)前業(yè)務(wù)處理掉了,上拋結(jié)果
if (handled) {
result(self, handled);
}
else{
// 沿著責(zé)任鏈,指派給下一個(gè)業(yè)務(wù)處理,直到找到可以處理業(yè)務(wù)的對(duì)象為止
if (self.nextBusiness) {
[self.nextBusiness handle:result];
}
else{
// 沒有業(yè)務(wù)處理, 上拋
result(nil, NO);
}
}
};
// 當(dāng)前業(yè)務(wù)進(jìn)行處理
[self handleBusiness:completion];
}
- (void)handleBusiness:(CompletionBlock)completion
{
/*
業(yè)務(wù)邏輯處理
如網(wǎng)絡(luò)請(qǐng)求、本地照片查詢等
*/
}
@end
然后我們只要將BusinessA,BusinessB,BusinessC都繼承自抽象基類創(chuàng)建具體的業(yè)務(wù)對(duì)象,
然后把責(zé)任鏈中nextRespond指向進(jìn)行更改
BusinessA--->(nextRespond)BusinessB--->(nextRespond)BusinessC改為
BusinessC--->(nextRespond)BusinessB--->(nextRespond)BusinessA即可,跟iOS UI事件響應(yīng)者鏈?zhǔn)且粯拥摹?/p>
使用場(chǎng)景
有多個(gè)對(duì)象可以處理同一個(gè)請(qǐng)求,動(dòng)態(tài)確定誰可以處理該請(qǐng)求
不明確指定接受者的情況下,給多個(gè)對(duì)象中的其中一個(gè)對(duì)象發(fā)送請(qǐng)求
動(dòng)態(tài)指定一組對(duì)象實(shí)現(xiàn)請(qǐng)求
雖然說責(zé)任鏈一般是指當(dāng)一個(gè)對(duì)象可以處理時(shí)候停止傳遞,但是在有需求的時(shí)候,就算不停止傳遞也沒有關(guān)系。
優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
責(zé)任鏈模式非常顯著的優(yōu)點(diǎn)是將請(qǐng)求和處理分開。請(qǐng)求者可以不用知道是誰處理的,處理者可以不用知道請(qǐng)求的全貌,兩者解耦,提高系統(tǒng)的靈活性。缺點(diǎn)
責(zé)任鏈模式有兩個(gè)非常顯著的缺點(diǎn):一是性能問題,每個(gè)請(qǐng)求都是從鏈頭遍歷到鏈尾,特別是在鏈比較長(zhǎng)的時(shí)候,性能是一個(gè)非常大的問題。二是調(diào)試不方便,特別是鏈條比較長(zhǎng),環(huán)節(jié)比較多的時(shí)候,由于采用了類似遞歸的方式,調(diào)試的時(shí)候邏輯可能比較復(fù)雜
二、橋接模式
橋接(Bridge)是用于把抽象化與實(shí)現(xiàn)化解耦,使得二者可以獨(dú)立變化。這種模式涉及到一個(gè)作為橋接的接口,使得抽象層次結(jié)構(gòu)從其實(shí)現(xiàn)中分離出來,抽象層定義了供客戶端使用的上層的抽象接口,實(shí)現(xiàn)層則定義了供抽象層使用的底層接口,實(shí)現(xiàn)類的引用被封裝與抽象層的實(shí)例中時(shí),橋接就形成了。這兩種類型的類可被結(jié)構(gòu)化改變而互不影響。

Abstraction是一個(gè)抽象基類或者叫控制管理類, 是供開發(fā)者直接調(diào)用的上層抽象接口的父接口(方法)。它有一個(gè)對(duì) Implementor 實(shí)例的引用,Implementor 定義了實(shí)現(xiàn)類的接口。這個(gè)接口不必跟 Abstraction 的接口一致。Implementor 的接口提供基本操作,而 Abstraction 的內(nèi)部實(shí)際操作通過實(shí)現(xiàn)類Implementor的調(diào)用實(shí)現(xiàn)的。當(dāng)客戶端向 Abstraction 的實(shí)例發(fā)送 operation 消息時(shí),這個(gè)方法向 imp 發(fā)送 operationImp 消息。實(shí)際的 ConcreteImplementator 將作出響應(yīng)并接收任務(wù)
橋接模式的實(shí)例應(yīng)用
比如有一家電視機(jī)制造商,他們生產(chǎn)的每臺(tái)電視機(jī)都帶有一個(gè)遙控器,用戶可以用遙控器進(jìn)行切換頻道之類的操作。這里遙控器就是電視機(jī)的接口。如果每個(gè)電視機(jī)型號(hào)需要一個(gè)專用的遙控器,那么單是遙控器就會(huì)導(dǎo)致設(shè)計(jì)激增。我們可以把遙控器邏輯同實(shí)際的電視機(jī)型號(hào)分離開來,這樣電視機(jī)型號(hào)的改變就不會(huì)對(duì)遙控器的設(shè)計(jì)有任何影響。遙控器的同一個(gè)設(shè)計(jì)可以被復(fù)用和擴(kuò)展,而不會(huì)影響其他電視機(jī)型號(hào)。 在面向?qū)ο蟮能浖O(shè)計(jì)中也有類似的情形,從針對(duì)不同的實(shí)現(xiàn)中,分離出其可被復(fù)用的抽象稱為橋接模式
實(shí)現(xiàn)橋接模式有兩種方式比較常見,主要是實(shí)現(xiàn)類的具體操作不一樣,一種是通過創(chuàng)建實(shí)現(xiàn)類的抽象類,然后遵守一個(gè)協(xié)議,然后通過創(chuàng)建實(shí)現(xiàn)類的子類來分別實(shí)現(xiàn)不同的業(yè)務(wù),另一種就是直接在抽象類中通過方法實(shí)現(xiàn)。(其實(shí)第一種是把實(shí)現(xiàn)的方法列表抽成一個(gè)協(xié)議的另一種實(shí)現(xiàn)方式)
下面兩圖是對(duì)第二種方式的介紹,控制抽象基類A有一個(gè)實(shí)現(xiàn)抽象基類B,然后分別創(chuàng)建控制類和實(shí)現(xiàn)類的子類,來具體實(shí)現(xiàn)業(yè)務(wù)邏輯。


#import <Foundation/Foundation.h>
#import "TVProtocol.h"
NS_ASSUME_NONNULL_BEGIN
/// 遙控器抽象類
@interface AbstractRemoteControl : NSObject
@property (nonatomic, weak) id<TVProtocol> tvProtocol;
- (void)detectTVFunction;
@end
NS_ASSUME_NONNULL_END
#import "AbstractRemoteControl.h"
@implementation AbstractRemoteControl
- (void)detectTVFunction {
NSLog(@"檢測(cè)電視機(jī)具備的功能,由子類來進(jìn)行實(shí)現(xiàn)");
}
@end
要實(shí)現(xiàn)的通用功能定義為一個(gè)協(xié)議列表TVProtocol
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@protocol TVProtocol <NSObject>
@required
// 切換頻道
- (void)switchChannel;
// 調(diào)節(jié)音量
- (void)adjustVolume;
// 電源開關(guān)
- (void)powerSwitch;
@end
NS_ASSUME_NONNULL_END
然后再定義一個(gè)電視機(jī)實(shí)現(xiàn)的抽象基類,讓其遵守TVProtocol協(xié)議
#import <Foundation/Foundation.h>
#import "TVProtocol.h"
NS_ASSUME_NONNULL_BEGIN
/// 電視機(jī)抽象類
@interface AbstractTV : NSObject<TVProtocol>
@end
#import "AbstractTV.h"
@implementation AbstractTV
- (void)switchChannel {
NSLog(@"切換頻道,由具體的子類來實(shí)現(xiàn)");
}
- (void)adjustVolume {
NSLog(@"調(diào)節(jié)音量,由具體的子類來實(shí)現(xiàn)");
}
- (void)powerSwitch {
NSLog(@"電源開關(guān),由具體的子類來實(shí)現(xiàn)");
}
然后再創(chuàng)建遙控器抽象類AbstractRemoteControl的子類ConcreteRemoteControl
#import "AbstractRemoteControl.h"
NS_ASSUME_NONNULL_BEGIN
/// 遙控器實(shí)例類
@interface ConcreteRemoteControl : AbstractRemoteControl
@end
NS_ASSUME_NONNULL_END
#import "ConcreteRemoteControl.h"
@implementation ConcreteRemoteControl
- (void)detectTVFunction {
[self.tvProtocol switchChannel];
[self.tvProtocol adjustVolume];
[self.tvProtocol powerSwitch];
}
@end
同樣的分別創(chuàng)建電視機(jī)抽象類AbstractTV的兩個(gè)子類TVA、TVB(電視型號(hào)A和B)
#import "AbstractTV.h"
NS_ASSUME_NONNULL_BEGIN
@interface TVA : AbstractTV
// 重寫這三個(gè)方法
//- (void)switchChannel;
//- (void)adjustVolume;
//- (void)powerSwitch;
@end
// TVA.m
// DesignPatten
//
// Created by 根哥 on 2023/2/14.
// Copyright ? 2023 yangyang. All rights reserved.
//
#import "TVA.h"
@implementation TVA
- (void)switchChannel {
NSLog(@"電視機(jī)A 具備了切換頻道的功能");
}
- (void)adjustVolume {
NSLog(@"電視機(jī)A 具備了調(diào)節(jié)音量的功能");
}
- (void)powerSwitch {
NSLog(@"電視機(jī)A 具備了電源開關(guān)的功能");
}
@end
TVB
#import "AbstractTV.h"
NS_ASSUME_NONNULL_BEGIN
@interface TVB : AbstractTV
//// 重寫這三個(gè)方法
//- (void)switchChannel;
//- (void)adjustVolume;
//- (void)powerSwitch;
@end
#import "TVB.h"
@implementation TVB
- (void)switchChannel {
NSLog(@"電視機(jī)B 具備了切換頻道的功能");
}
- (void)adjustVolume {
NSLog(@"電視機(jī)B 具備了調(diào)節(jié)音量的功能");
}
- (void)powerSwitch {
NSLog(@"電視機(jī)B 具備了電源開關(guān)的功能");
}
@end
最后在項(xiàng)目中使用它
- (void)bridgeTVControl{
AbstractRemoteControl *remoteControl = [[ConcreteRemoteControl alloc] init];
AbstractTV *tvProtocol = [[TVA alloc] init];
remoteControl.tvProtocol = tvProtocol;
[remoteControl detectTVFunction];
NSLog(@"http:///////////////////////////////");
tvProtocol = [[TVB alloc] init];
remoteControl.tvProtocol = tvProtocol;
[remoteControl detectTVFunction];
/**
* 橋接模式:將抽象部分與它的實(shí)現(xiàn)部分分離,使它們都可以獨(dú)立地變化。
* 在本例中,AbstractRemoteControl是抽象部分,TVProtocol是其實(shí)現(xiàn)部分。
*/
// Do any additional setup after loading the view, typically from a nib.
}
控制臺(tái)打印
2023-02-15 11:28:20.714604+0800 DesignPatten[19266:518630] 電視機(jī)A 具備了切換頻道的功能
2023-02-15 11:28:20.715329+0800 DesignPatten[19266:518630] 電視機(jī)A 具備了調(diào)節(jié)音量的功能
2023-02-15 11:28:20.715958+0800 DesignPatten[19266:518630] 電視機(jī)A 具備了電源開關(guān)的功能
2023-02-15 11:28:20.716165+0800 DesignPatten[19266:518630] ///////////////////////////////
2023-02-15 11:28:20.716803+0800 DesignPatten[19266:518630] 電視機(jī)B 具備了切換頻道的功能
2023-02-15 11:28:20.717203+0800 DesignPatten[19266:518630] 電視機(jī)B 具備了調(diào)節(jié)音量的功能
2023-02-15 11:28:20.717706+0800 DesignPatten[19266:518630] 電視機(jī)B 具備了電源開關(guān)的功能
通過橋接模式的應(yīng)用,我們可以把抽象部分與實(shí)現(xiàn)部分分離,使它們都可以獨(dú)立的變化。比如在本例中,對(duì)AbstractRemoteControl的修改,不會(huì)影響到TVProtocol。同樣對(duì)TVProtocol的修改,也不會(huì)影響AbstractRemoteControl。這正是橋接模式帶給我們的便利性。
小結(jié)
總的來說,橋接模式的本質(zhì)在于“分離抽象和實(shí)現(xiàn)”。也可以對(duì)業(yè)務(wù)進(jìn)行解耦。
橋接模式的優(yōu)點(diǎn):
橋接模式使用聚合關(guān)系,解耦了抽象和實(shí)現(xiàn)之間固有的綁定關(guān)系,使得抽象和實(shí)現(xiàn)可以沿著各自的維度來變化。
提高了系統(tǒng)的可擴(kuò)展性,可以獨(dú)立地對(duì)抽象部分和實(shí)現(xiàn)部分進(jìn)行擴(kuò)展。
可減少子類的個(gè)數(shù),這個(gè)在前面講電視機(jī)示例的時(shí)候進(jìn)行分析了。
橋接模式的缺點(diǎn):
橋接模式的引入會(huì)增加系統(tǒng)的理解與設(shè)計(jì)難度,由于聚合關(guān)系建立在抽象層,要求開發(fā)者針對(duì)抽象進(jìn)行設(shè)計(jì)與編程。
橋接模式要求正確識(shí)別出系統(tǒng)中兩個(gè)獨(dú)立變化的維度,因此其使用范圍具有一定的局限性。
適用場(chǎng)景
通過優(yōu)缺點(diǎn)的分析,我們可以在如下的情形下使用橋接模式:
不想在抽象與其實(shí)現(xiàn)之間形成固定的綁定關(guān)系;
抽象及其實(shí)現(xiàn)都應(yīng)可以通過子類化獨(dú)立進(jìn)行擴(kuò)展;
對(duì)抽象的實(shí)現(xiàn)進(jìn)行修改不應(yīng)影響客戶端代碼;
如果每個(gè)實(shí)現(xiàn)需要額外的子類以細(xì)化抽象,則說明有必要把它們分成兩個(gè)部分;
想在帶有不同抽象接口的多個(gè)對(duì)象之間共享一個(gè)實(shí)現(xiàn)。
三、適配器模式
一,適配器的定義
定義: 將一個(gè)類的接口轉(zhuǎn)換成客戶希望的另外一個(gè)接口。適配器模式使得原本由于接口不兼容而不能一起工作的那些類可以一起工作
需求場(chǎng)景: 需要使用以前開發(fā)的“一些現(xiàn)存的對(duì)象”,但是新環(huán)境中要求的接口是這些現(xiàn)存對(duì)象所不滿足的
四、單例模式
#import "Mooc.h"
@implementation Mooc
+ (id)sharedInstance
{
// 靜態(tài)局部變量
static Mooc *instance = nil;
// 通過dispatch_once方式 確保instance在多線程環(huán)境下只被創(chuàng)建一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 創(chuàng)建實(shí)例
instance = [[super allocWithZone:NULL] init];
// instance = [[Mooc alloc]init];
});
return instance;
}
// 重寫方法【必不可少】 ②、防止 [[A alloc] init] 和 new 引起的錯(cuò)誤。因?yàn)?[[A alloc] init] 和 new 實(shí)際是一樣的工作原理,都是執(zhí)行了下面方法
+ (id)allocWithZone:(struct _NSZone *)zone{
return [self sharedInstance];
// return [super allocWithZone:zone];
}
// 重寫方法【必不可少】
- (id)copyWithZone:(nullable NSZone *)zone{
return self;
}
@end