設計模式之感悟和實踐1

FlyOceanFish發表於2018-12-16

背景

設計模式可以說是老生常談的一個知識點。工作這麼多年來也是陸陸續續看過幾本書。比如《大話設計模式》、《Head First 設計模式》,這兩本書是前期接觸比較多,而且質量還是不錯的兩本書,不過之前看的感覺有點像豬八戒吃人參果——食而不知其味。

很多時候有的人在寫程式碼的過程中對設計模式並不為然。像本人是做APP開發的,APP開發結束後,迭代和新增業務邏輯不是很多的話,確實以前寫成啥樣其實真的無所謂。直到近來接手了公司一個歷史悠久的主專案,由於業務的發展,業務邏輯越來越多,改動越來越大,每次新增業務都舉步維艱,讓我深刻的體會到了設計模式的優點和威力。

近來正好又重新拾起設計模式在看另外一本書《設計模式之禪》這本書,這本書較之其他兩本書感覺比較接地氣吧。這次看著很慢,每個模式都在細細的琢磨,再加上之前專案的經驗和這次槽糕的老專案,收穫比較多,感悟也比較深。

設計原則

要說設計模式,設計原則是必不可缺少的。如果單單去看這幾個原則,其實一眼就能看完。原則只是原則,是要儘量保持,而不是必須保持的哦!所以不能一言而論,根據業務和情景懂得變通。

單一原則

應該有且僅有一個原因引起類的變更。

這個原則在使用的過程中要做到適度,如果過度使用的話,可以將一個類中所有的方法都對應做成一個類。 其實在使用過程中說白了就是根據業務或某一方面將功能歸類,同一功能的放在一起,宣告一個介面。

設計模式之感悟和實踐1
比如上圖,我們根據使用者的屬性和用的行為,劃分為兩個介面。

根據以上例子單一原則的有點也是顯而易見:類的複雜性降低,可維護性高。

里氏替換原則

只要父類出現的地方子類就可以出現,而且替換為子類也不會產生任何錯誤或異常,使用這可能根本就不需要知道是父類還是子類。但是,反過來就不行了。

這個原則用到的其實就是類的繼承,在一些情況下繼承的優點不言而喻,不過在專案中不要隨便使用繼承。繼承是可以用其他設計模式替換的,比如裝飾模式等。 繼承具有以下缺點:

  • 繼承是侵入性的。只要繼承,就必須擁有父類的所有屬性和方法。這個危害性還是挺大的!
  • 降低了程式碼耦合性。子類必須擁有父類的屬性和方法,讓子類自由的世界中多了些約束
  • 增加了耦合性。當父類的常量、變數和方法被修改時,需要考慮子類的修改,而且在缺乏規範的環境下,這種修改時毀滅性的。

如果子類不能完整地實現父類的方法,或者父類的某些方法在子類中已經發生"畸形",則建議斷開父子繼承關係,採用依賴、聚集、組合等關係代替繼承

依賴倒置原則

高層模組不應該依賴底層模組,兩者都應該依賴其抽象(介面或實現類)。一句大白話就是面向介面程式設計

也就是APP開發過程中,只要有介面文件,我們就可以實現APP啦,不需要後臺人員api的實現。也可以這麼理解。

介面隔離原則

建立單一介面,不要建立臃腫龐大的介面。

單一職責要求的是類和介面職責單一,注重的是職責,這是業務邏輯上的劃分,而介面隔離原則要求介面的方法儘量少。

例如一個介面的職責可能包含10個方法,這10個方法都放在一個介面中,並且提供給多個模組訪問,各個模組按照規定的許可權來訪問,在系統外通過文件約束“不使用的方法不要訪問”,按照單一職位原則是允許的,按照介面隔離原則是不允許的,因為它要求“儘量使用多個專門的介面”。就是指提供給每個模組的都應該是單一介面,提供給幾個模組就應該有幾個介面,而不是建議一個龐大的臃腫的介面。 我們在使用元件化的過程中,由於模組間的呼叫,每個模組都對外宣告一個公共介面,這時候其實就違背了介面隔離原則。比如我們可以按照同一層級呼叫宣告一個介面,不同層級的呼叫宣告一個介面。

迪米特法則

一個物件應該對其他物件有最少的瞭解

  • 只和朋友交流 朋友:出現在成員變數、方法的輸入輸出引數中的類稱為成員朋友類,而出現在方法體內的類不屬於朋友類 比如我們在方法中使用了一個區域性物件變數,這就違背了這個原則
  • 是自己的就是自己的 如果一個方法放在本類中,既不增加類間關係,也對本類不產生負面影響,那就放置在本類中。

開閉原則

一個軟體實體如類、模組和函式應該對外擴充套件開放,對修改關閉。即軟體實體應該對擴充套件開放,對修改關閉,其含義是說一個軟體實體應該通過擴充套件來實現變化,而不是通過修改已有的程式碼來實現變化。


上邊囉嗦一下設計模式的原則,其實我們在專案實踐中也就是因為程式碼違背了其中的原則,然後進行改進,進而演化出設計模式。

所以設計模式都是基於以上原則產生的。

場景使用

這次我們介紹的是責任鏈模式

責任鏈模式的重點是在“鏈”上,由一條鏈去處理相似的請求在鏈中決定誰來處理這個請求,並返回相應的結果。

通過這個定義不知道大家有沒有能想到應用的場景呢?

像在我們工程中由於業務場景的複雜性,就存在大量的if...else判斷。這樣的邏輯導致業務交叉在一起,導致每個業務不清晰,擴充套件起來不是很方便,並且在iOS中會導致UIViewController臃腫。

這個時候我們可以引入責任鏈模式,呼叫方不用關心真正的業務處理,只要關心業務分類就行,真正的業務交給一個實體類來處理。

首先我們看一下責任鏈模式通用類圖

設計模式之感悟和實踐1

這個看著可能有點不知道所以然,所以我們來個例項講解一下,相信看了之後就會恍然大悟。

這是demo所有的類圖

設計模式之感悟和實踐1

ActionClickProtocol

@protocol ActionClickProtocol <NSObject>
- (void)handleClick;
- (void)setNext:(id<ActionClickProtocol>)actionClickHandle;
@end

typedef NS_ENUM(NSUInteger, HandleType) {
    CLICK1,
    CLICK2,
};
複製程式碼

ActionClickHandle

@interface ActionClickHandle : NSObject<ActionClickProtocol>
@property (nonatomic,assign)HandleType type;
@property (nonatomic,strong)ActionClickHandle *nextHandle;
@end


@implementation ActionClickHandle
- (void)handleClick {
    NSLog(@"共有的處理方法");
}

- (void)setNext:(nonnull id<ActionClickProtocol>)actionClickHandle {
    self.nextHandle = actionClickHandle;
}
@end
複製程式碼

ActionClickEvent1

@interface ActionClickEvent1 : ActionClickHandle
@end

@implementation ActionClickEvent1
-(void)handleClick{
    NSLog(@"事件1的處理");
}
@end
複製程式碼

ActionClickEvent2

@interface ActionClickEvent2 : ActionClickHandle
@end

@implementation ActionClickEvent2
-(void)handleClick{
   NSLog(@"事件2的處理");
}
@end
複製程式碼

MyHandle 這個類是核心對外使用的類

@interface MyHandle : NSObject
- (instancetype)initWithType:(HandleType)type;
- (void)handleClick;
@end

@interface MyHandle()
@property (nonatomic,assign)HandleType type;
@property (nonatomic,strong)ActionClickHandle *nextHandle;
@property (nonatomic,strong)ActionClickEvent1 *event1;
@property (nonatomic,strong)ActionClickEvent2 *event2;
@end
@implementation MyHandle
- (instancetype)initWithType:(HandleType)type{
    self = [super init];
    if (self) {
        _type = type;
        _event1 = [[ActionClickEvent1 alloc] init];
        _event1.type = CLICK1;
        _event2 = [[ActionClickEvent2 alloc] init];
        _event2.type = CLICK2;
        [_event1 setNextHandle:_event2];
        self.nextHandle = _event1;
    }
    return self;
}
- (void)handleClick{
    if (self.nextHandle.type==self.type) {
        [self.nextHandle handleClick];
    }else{
        while (self.nextHandle.type!=self.type) {
            self.nextHandle = self.nextHandle.nextHandle;
        }
        [self.nextHandle handleClick];
    }
}

@end
複製程式碼

使用範例:

MyHandle *myHandle = [[MyHandle alloc] initWithType:CLICK2];
[myHandle handleClick];
複製程式碼

我們在使用的過程中可以面向model開發,將model傳入 handleClick方法中。type則是mode中根據介面資料的返回對應的不同型別。

這種使用方法完全可以避免if...else的使用,並且業務邏輯很清晰。

總結

其實專案使用過程中,關鍵一點還是要去發現那塊程式碼會一直變,將經常變換的程式碼進行設計模式的封裝,則以後的程式碼擴充套件是非常的方便

我的部落格

我的部落格

相關文章