網(wǎng)站首頁的模塊布局杭州百度快速排名提升
【iOS】設(shè)計(jì)模式的六大原則
文章目錄
- 【iOS】設(shè)計(jì)模式的六大原則
- 前言
- 開閉原則——OCP
- 單一職能原則——SRP
- 里氏替換原則——LSP
- 依賴倒置原則——DLP
- 接口隔離原則——ISP
- 迪米特法則——LoD
- 小結(jié)
前言
筆者這段時(shí)間看了一下有關(guān)于設(shè)計(jì)模式的七大原則,下面代碼示例均為OC。
開閉原則——OCP
這原則要求很簡單:
- 該原則要求軟件實(shí)體(類、模塊、函數(shù)等)應(yīng)該對擴(kuò)展開放,對修改關(guān)閉。
開閉原則的思想是:當(dāng)新的需求出現(xiàn)時(shí),應(yīng)該盡可能地通過增加新的代碼來滿足這些需求,而不是直接修改現(xiàn)有代碼。
正如iOS開發(fā)中的分類的思想一樣。
下面給出一個(gè)例子:
//下面是一個(gè)有關(guān)于Car的例子
#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface Car : NSObject
@property (nonatomic, strong) NSString* brand;
@endNS_ASSUME_NONNULL_END#import "Car.h"@implementation Car
- (void) startEngine { // 一個(gè)汽車啟動(dòng)的業(yè)務(wù)NSLog(@"the car go to ran");
}
@end
根據(jù)我們這個(gè)原則的思想來說的話,我們?nèi)绻胩砑右粋€(gè)新的功能,比方說自動(dòng)泊車,我們應(yīng)該是添加一個(gè)方法,而不是在原先的方法上進(jìn)行一個(gè)修改:
錯(cuò)誤案例:
//下面是一個(gè)有關(guān)于Car的例子
#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface Car : NSObject
@property (nonatomic, strong) NSString* brand;
@endNS_ASSUME_NONNULL_END#import "Car.h"@implementation Car
- (void) startEngine { // 一個(gè)汽車啟動(dòng)的業(yè)務(wù)NSLog(@"the car go to ran");//添加自動(dòng)泊車NSLog(@"the car begin to park automatically");
}
@end
這樣很顯然不符合我們的開閉原則,下面是一個(gè)正確的案例:
#import "Car.h"NS_ASSUME_NONNULL_BEGIN
//這里創(chuàng)建一個(gè)抽象類來實(shí)現(xiàn),這個(gè)抽象類表示所有的一個(gè)汽車裝飾器
@interface CarDecorator : Car
@property (nonatomic, strong) Car* car;
-(instancetype)initWithCar:(Car*)car;
@endNS_ASSUME_NONNULL_END#import "CarDecorator.h"
//這里是這個(gè)抽象類的一個(gè)具體實(shí)現(xiàn)
@implementation CarDecorator
- (instancetype)initWithCar:(Car *)car {if ([super init]) {self.car = car;}return self;
}
- (void)startEngine {[self.car startEngine];
}
@end
//下面是一個(gè)具體某一類別的實(shí)現(xiàn):
#import "CarDecorator.h"NS_ASSUME_NONNULL_BEGIN@interface ElectricCarDecorator : CarDecorator
-(void)autoParking;
@endNS_ASSUME_NONNULL_END
#import "ElectricCarDecorator.h"@implementation ElectricCarDecorator
-(void)autoParking { // 創(chuàng)建一個(gè)新的方法用于實(shí)現(xiàn)我們的一個(gè)自動(dòng)泊車NSLog(@"Auto Parking");
}
- (void)startEngine { //在這里重寫父類的方法,讓這個(gè)方法在實(shí)現(xiàn)我們想要的自動(dòng)泊車功[super startEngine];[self autoParking];
}
@end
這上面的我們創(chuàng)建了一個(gè)新的抽象類來執(zhí)行他新的一個(gè)方法,然后在子類中重寫有關(guān)于父類的一個(gè)方法,既保證了代碼不會(huì)被修改的同時(shí),實(shí)現(xiàn)了一個(gè)新的功能,這就體現(xiàn)出了我們的一個(gè)開閉原則。
單一職能原則——SRP
該原則要求:
- 不要存在多于一個(gè)導(dǎo)致類變更的原因,也就是說每個(gè)類應(yīng)該實(shí)現(xiàn)單一的職責(zé),否則就應(yīng)該把類拆分.
要點(diǎn):
- 一個(gè)類只負(fù)責(zé)一個(gè)職能,類的設(shè)計(jì)應(yīng)該避免包含過多的一個(gè)功能
- 高內(nèi)聚,低耦合
- 避免濫用接口
下面是一個(gè)錯(cuò)誤的示例:
#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface Employee : NSObject // 這是一個(gè)工人的基本信息的內(nèi)容
@property (nonatomic, strong) NSString* name;
@property (nonatomic, assign) NSInteger age;
-(void)calculateSalary:(Employee*)employee; // 計(jì)算了工人的薪資
@endNS_ASSUME_NONNULL_END#import "Employee.h"@implementation Employee
-(void)calculateSalary:(Employee *)employee {NSLog(@"calcurlatSalry: name: %@, age : %ld", employee.name, employee.age);
}
@end
這個(gè)案例我們可以看出我們這里出現(xiàn)了這個(gè)類包含了多個(gè)職能,這很顯然不符合我們的單一職能原則,我們這個(gè)工人的類別就應(yīng)該僅僅包含一個(gè)工人的一個(gè)基本信息,而不應(yīng)該涉及計(jì)算薪資的一個(gè)內(nèi)容。
這里我們給出一個(gè)正確的例子:
#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface Employee : NSObject
@property (nonatomic, strong) NSString* name;
@property (nonatomic, assign) NSInteger age;
@endNS_ASSUME_NONNULL_END#import <Foundation/Foundation.h>
@class Employee;
NS_ASSUME_NONNULL_BEGIN@interface SalaryCaculator : NSObject
-(void)calculateSalary:(Employee*)employee;
@endNS_ASSUME_NONNULL_END
#import "SalaryCaculator.h"
#import "Employee.h"
@implementation SalaryCaculator
-(void)calculateSalary:(Employee *)employee {NSLog(@"calcurlatSalry: name: %@, age : %ld", employee.name, employee.age);
}
@end
在這個(gè)案例中我們把兩個(gè)內(nèi)容分開了,創(chuàng)建了一個(gè)新的類來負(fù)責(zé)他對應(yīng)的一個(gè)工作,從而保證了一個(gè)類只負(fù)責(zé)一個(gè)職能,符合我們的一個(gè)單一職能原則。
遵循單一職責(zé)原則是一個(gè)重要的設(shè)計(jì)原則,可以幫助我們寫出更加模塊化、可維護(hù)和可擴(kuò)展的代碼。
里氏替換原則——LSP
該原則要求:
子類對象可以替換父類對象出現(xiàn)在程序中,而不影響程序的正確性。
這里可能比較難以理解這項(xiàng)原則的一個(gè)作用,這里筆者借用學(xué)長的一段話來看一下:
- 里氏替換原則是實(shí)現(xiàn)開放封閉原則的重要方式之一。
- 它克服了繼承中重寫父類造成的可復(fù)用性變差的缺點(diǎn)。
- 它是動(dòng)作正確性的保證。即類的擴(kuò)展不會(huì)給已有的系統(tǒng)引入新的錯(cuò)誤,降低了代碼出錯(cuò)的可能性?!驹O(shè)計(jì)模式】六大原則詳解,每個(gè)原則提供代碼示例
這里我們可以看出這項(xiàng)原則一個(gè)核心是為來保證類的一個(gè)擴(kuò)展是不會(huì)給已經(jīng)存在的系統(tǒng)引入新的錯(cuò)誤,防止我們采用多態(tài)的時(shí)候出現(xiàn)一個(gè)代碼上的問題。
下面給出一個(gè)經(jīng)典的長方形不是正方形的一個(gè)例子:
#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@protocol Shape <NSObject>
- (NSInteger)calculateArea; //定義一個(gè)協(xié)議方法,也就是一個(gè)公共的接口
@endNS_ASSUME_NONNULL_END#import <Foundation/Foundation.h>
#import "Shape.h"
NS_ASSUME_NONNULL_BEGIN@interface Squre : NSObject<Shape> //實(shí)現(xiàn)一個(gè)正方形類來實(shí)現(xiàn)對應(yīng)的接口的內(nèi)容
@property (nonatomic, assign) NSInteger length;
@endNS_ASSUME_NONNULL_END#import "Squre.h"@implementation Squre
- (NSInteger)calculateArea {return self.length * self.length;
}
@end#import "Shape.h"
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN@interface Rectangle : NSObject<Shape> //實(shí)現(xiàn)長方形類來實(shí)現(xiàn)對應(yīng)接口的內(nèi)容
@property (nonatomic, assign) NSInteger width;
@property (nonatomic, assign) NSInteger height;
@endNS_ASSUME_NONNULL_END#import "Rectangle.h"@implementation Rectangle
- (NSInteger)calculateArea {return self.width * self.height;
}
@end
這里我們把長方形和正方形分成兩個(gè)不同的類別遵循不同的協(xié)議,這樣才不會(huì)出現(xiàn)里氏替換原則中將正方形替換成長方形,然后設(shè)置長寬出現(xiàn)與預(yù)期的結(jié)果實(shí)際不符的一個(gè)情況。
依賴倒置原則——DLP
該原則要求:
- 高層模塊不應(yīng)該依賴低層模塊,二者都應(yīng)該依賴抽象。抽象不應(yīng)該依賴細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴抽象
在OC中我認(rèn)為抽象類是協(xié)議,細(xì)節(jié)是一個(gè)類的實(shí)現(xiàn)方法,高層模塊就是調(diào)用端,底層模塊就是實(shí)現(xiàn)端。
也就是說,模塊間依賴是通過抽象發(fā)生;實(shí)現(xiàn)類之間沒有依賴關(guān)系,所有的依賴關(guān)系通過接口/抽象類產(chǎn)生。【設(shè)計(jì)模式】六大原則詳解,每個(gè)原則提供代碼示例
下面給出我認(rèn)為的依賴倒置原則的實(shí)現(xiàn):
#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@protocol LoggerProtocol <NSObject> // 一個(gè)抽象的接口
-(void) log:(NSString*)message;
@endNS_ASSUME_NONNULL_END#import <Foundation/Foundation.h>
#import "LoggerProtocol.h"
NS_ASSUME_NONNULL_BEGIN@interface Logger : NSObject<LoggerProtocol> //這里是我們的一個(gè)具體的底層實(shí)現(xiàn)端
@endNS_ASSUME_NONNULL_END#import "Logger.h"@implementation Logger
- (void)log:(NSString*)message { // 底層實(shí)現(xiàn)一個(gè)抽象NSLog(@"1234 %@", message);
}
@end#import <Foundation/Foundation.h>
#import "Logger.h"
NS_ASSUME_NONNULL_BEGIN@interface MyClass : NSObject // 這里是我們的高層調(diào)用端
@property (nonatomic, strong) id<LoggerProtocol> logger; //高層調(diào)用這個(gè)接口
@endNS_ASSUME_NONNULL_END//使用的時(shí)候我們通過下面的方法來實(shí)現(xiàn):
// MyClass.logger = [[Logger alloc] init];在這里進(jìn)行一個(gè)依賴注入
// [MyClass.logger log:@"123"];
接口隔離原則——ISP
該原則要求:
- 一個(gè)類不應(yīng)該強(qiáng)迫其它類依賴它們不需要使用的方法,也就是說,一個(gè)類對另一個(gè)類的依賴應(yīng)該建立在最小的接口上
也就說:一個(gè)類應(yīng)該只提供其它類需要使用的方法,而不應(yīng)該強(qiáng)迫其它類依賴于它們不需要使用的方法
這里我們舉一個(gè)例子:假設(shè)我們設(shè)計(jì)一個(gè)多功能設(shè)備,里面可能包含了打印機(jī)和掃描儀,但是一般的設(shè)備可能不具備對應(yīng)的一個(gè)掃描的一個(gè)功能,但是如果這個(gè)類遵循這個(gè)協(xié)議就會(huì)導(dǎo)致對應(yīng)的一個(gè)問題,實(shí)現(xiàn)了一個(gè)他不具備的一個(gè)功能。
下面只介紹正確的案例的樣式,設(shè)置小而專的接口
#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@protocol Printable <NSObject> //設(shè)置打印的接口
- (void) printDoucment;
@endNS_ASSUME_NONNULL_END#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@protocol Scanabel <NSObject> //設(shè)置掃描的接口
-(void) scanDoucment;
@endNS_ASSUME_NONNULL_END#import <Foundation/Foundation.h>
#import "Printable.h"
NS_ASSUME_NONNULL_BEGIN@interface Printer : NSObject <Printable> //普通打印機(jī)的一個(gè)實(shí)現(xiàn),如果是多功能則多遵循一個(gè)協(xié)議就可以了,@endNS_ASSUME_NONNULL_END#import "Printer.h"@implementation Printer
-(void)printDoucment {NSLog(!"common Printer");
}
@end
其核心思想在以下的內(nèi)容:
如果一個(gè)接口過于臃腫,就需要將其拆分成多個(gè)小的接口,使得每個(gè)接口中只包含必要的方法。這樣的好處是:
- 接口更加具有內(nèi)聚性:每個(gè)接口只需要關(guān)注自己的功能,而不需要關(guān)注其他接口中的方法,因此能夠使接口更加專注和具有內(nèi)聚性。
- 接口之間的耦合度更低:每個(gè)接口只依賴于必要的方法,而不依賴于其他不必要的方法,因此能夠使接口之間的耦合度更低。
- 代碼的復(fù)用性更高:每個(gè)接口只包含必要的方法,因此能夠使得代碼的復(fù)用性更高,也能夠提高代碼的可讀性和可維護(hù)性。24種設(shè)計(jì)模式代碼實(shí)例學(xué)習(xí)(一)七大設(shè)計(jì)原則
迪米特法則——LoD
該原則要求:
- 一個(gè)類對自己依賴的類知道的越少越好。無論被依賴的類多么復(fù)雜,都應(yīng)該將邏輯封裝在方法的內(nèi)部,通過
public
方法提供給外部。
最少知道原則的另一個(gè)表達(dá)方式是:只與直接的朋友通信。
類之間只要有耦合關(guān)系,就叫朋友關(guān)系。耦合分為依賴、關(guān)聯(lián)、聚合、組合等。我們稱出現(xiàn)為成員變量、方法參數(shù)、方法返回值中的類為直接朋友。局部變量、臨時(shí)變量則不是直接的朋友。
這里我們以購物車為例子:一個(gè)人去超市買東西主要分成三個(gè)部分,一個(gè)是我們的用戶,一個(gè)是購物車,一個(gè)是商品,這里面我們?nèi)绻线@個(gè)設(shè)計(jì)原則的話,這里的人應(yīng)該和購物車進(jìn)行交互,然后購物車和商品進(jìn)行一個(gè)交互,這樣符合我們的迪米特法則。
下面給出一段代碼示例:
#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface Product : NSObject // 這個(gè)是商品
@property (nonatomic, assign) CGFloat price;
@property (nonatomic, strong) NSString* name;
@endNS_ASSUME_NONNULL_END#import <Foundation/Foundation.h>
@class Product;
NS_ASSUME_NONNULL_BEGIN@interface ShopCart : NSObject // 這個(gè)是購物車
@property (nonatomic, strong) NSMutableArray<Product*>* products;
-(void) addProduct:(Product*)product;
@endNS_ASSUME_NONNULL_END#import "ShopCart.h"@implementation ShopCart //與商品發(fā)生直接關(guān)系
- (void)addProduct:(Product *)product {[self.products addObject:product];
}
@end#import <Foundation/Foundation.h>
@class ShopCart;
@class Product;
NS_ASSUME_NONNULL_BEGIN@interface User : NSObject //用戶
@property (nonatomic, strong) ShopCart* shopCart;
-(void)addToCart:(Product*)product;
@endNS_ASSUME_NONNULL_END#import "User.h"
#import "ShopCart.h"
@implementation User
- (void)addToCart:(Product *)product {[self.shopCart addProduct:product]; // 與購物車發(fā)生直接關(guān)系
}
@end
在上面的代碼示例中,每個(gè)類都只和自己直接的朋友進(jìn)行通信,遵循了最少知道原則。
小結(jié)
這里筆者簡單介紹了一下六大設(shè)計(jì)模式的一個(gè)內(nèi)容,筆者對這部分內(nèi)容也是初次接觸,如有紕漏或者還請不吝賜教,筆者會(huì)及時(shí)改正。
參考博客:
【設(shè)計(jì)模式】六大原則詳解,每個(gè)原則提供代碼示例
24種設(shè)計(jì)模式代碼實(shí)例學(xué)習(xí)(一)七大設(shè)計(jì)原則