淺談程式設計思想

kangpp發表於2018-01-29

一、鏈式程式設計

OC中有一個使用非常廣泛的框架——masonry。在masonry中,我們可以使用“點語法”實現方法呼叫。但是OC 的呼叫方法使用“[Class func]”。相比於OC中反人類的AutoLayout語法,masonry的鏈式語法使用的便利性,被廣大iOS開發者所接受。

masonry語法:↓

  1. [view mas_makeConstraints:^(MASConstraintMaker *make) {
  2.    make.top.equalTo(superview.mas_top).with.offset(10);
  3.    make.left.equalTo(10);
  4.    make.bottom.right.equalTo(-10);
  5. }];

上面的示例程式碼中,鏈式程式設計思想被體現,並且極大的簡化了自動佈局程式碼。

今天我們由簡到繁,逐步實現這樣的鏈式語法設計。

首先建立一個Person類檔案,該類檔案在下面的示例中均被使用。

  1. #import <Foundation/Foundation.h>
  2. @interface Person : NSObject
  3. @end

我們最終想要實現的方法呼叫格式為:

  1.    self.p = [[Person alloc] init];
  2.    self.p.eat(2);

1.block定義為屬性

    block定義為屬性時,常備用於反向傳值(此處不再討論)。現在我們僅僅給出屬性定義語法!

  1. #import <Foundation/Foundation.h>
  2. @interface Person : NSObject
  3. /* block型別屬性:(ARC使用strong,非ARC使用copy) **/
  4. @property (nonatomic, strong) void(^eat)();
  5. @end


2.block作為方法引數使用

    block作為程式碼引數使用,可以向方法傳遞一個可被執行的程式碼塊。這也是很多框架中常用的一種設計方式。例如常用的AFN框架中,對網路請求結果的處理。相比於用協議實現回撥功能,這樣寫可以使程式碼更加緊湊!

例如我們在Person中定義一個eat(吃)的方法:

  1. #import <Foundation/Foundation.h>
  2. @interface Person : NSObject
  3. /** block當引數(block程式碼塊)*/
  4. - (void)eat:(void (^)())block;
  5. @end

此時作為Person的呼叫者,我們可以將 eat 的具體行為通過程式碼塊的形式傳遞給該方法執行:

  1. /** block當做方法的引數使用 */
  2. - (void)demo2 {
  3.    self.p = [[Person alloc] init];
  4.    [self.p eat:^{
  5.        NSLog(@"我是怎樣吃飯的");
  6.    }];
  7. }

到此並沒有結束,我們還需要在Person檔案中將宣告的該方法實現。並呼叫執行引數block。否則我們的block程式碼塊沒有被呼叫當然不會被執行:

  1. #import "Person.h"
  2. @implementation Person
  3. // 實現宣告方法並呼叫block
  4. - (void)eat:(void (^)())block {
  5.    block();
  6. }
  7. @end

然後用例項呼叫該 eat 方法,我們會在控制檯看到結果:


3.block作為方法返回值

    本文開頭提到,使用“點語法”呼叫方法。既然block作為引數使用很廣泛,那我們是不是可以用它作為返回值使用?答案是肯定的!

    我們看masonry的方法:在bolck內點開 make.top.equalTo 方法,我們看它的返回值:


我們看到它的返回值是一個block型別,MASConstraint是返回值型別,我們暫且先不管(以 void 型別替換)。到此我們可以有一點想法了。

我們模仿masonry的該方法定義我們的自己Person類的 eat 方法:

  1. #import <Foundation/Foundation.h>
  2. @interface Person : NSObject
  3. /** block作為方法返回值使用 */
  4. - (void(^)(int))eat;
  5. @end

宣告瞭該方法,當然還要實現它:

  1. #import "Person.h"
  2. @implementation Person
  3. - (void (^)(int))eat {
  4.    return ^(int n){
  5.        NSLog(@"吃了 %d 個饅頭!!!", n);
  6.    };
  7. }
  8. @end

到這我們貌似搞定了,接下來試試到底行不行:

  1. - (void)demo3 {
  2.    // block當返回值
  3.    self.p = [[Person alloc] init];
  4.    self.p.eat(2);
  5. }

執行,控制檯列印出來了我們期待的結果:


寫到這裡,我們已經搞清楚了鏈式語法結構的實現原理。我們寫了一個很簡單的小栗子循序漸進,貌似很簡單有沒有。


4.鏈式語法結構的實現

    但是我們知道masonry中我們可以用點語法連續呼叫,比如:make.bottom.right.equalTo(-10);  可以連續的用點語法呼叫。我們不妨設計一個簡單的demo ,使其實現類似masonry的語法。具體如何實現,可以學習masonry的設計。(最終我們想要的結果是可以連續呼叫的語法結構 add(value).add(value).add(value) 

開啟masonry的約束方法:



mas_makeConstraints 方法被設計在 UIView的分類中,分析我們要實現的demo的需求,應該將計算方法宣告在NSObject的分類中!引數 MASConstraintMaker 為方法呼叫者,藉此分析我們建立NSObject的分類以及計算管理者Manager,並宣告計算方法:

  1. #import <Foundation/Foundation.h>
  2. #import "Manager.h"
  3. @interface NSObject (Tools)
  4. /** 計算方法 */
  5. + (int)KY_makeCalculate:(void(^)(Manager *mgr))block;
  6. @end

引數Manager就是我們的計算方法管理者,我們在該類中宣告加法運算,並根據之前點語法的實現原理,以block作為返回值:

  1. #import <Foundation/Foundation.h>
  2. @interface Manager : NSObject
  3. /** 加法計算 */
  4. - (void(^)(int))add;
  5. @end

接下來我們貌似就可以呼叫了:

  1. - (void)demo4 {
  2.    // 鏈式實現
  3.    int result = [NSObject KY_makeSum:^(Manager *mgr) {
  4.        mgr.add(5);
  5.    }];
  6.    NSLog(@"運算結果----> %d", result);
  7. }

但是,對於連續的 add(value) 方法呼叫,貌似我們並沒有實現!其實只要將返回值void型別改為管理者本身就可以了,另外我們再宣告一個屬性將計算結果臨時儲存一下:

  1. #import <Foundation/Foundation.h>
  2. @interface Manager : NSObject
  3. /** 計算結果 */
  4. @property (nonatomic, assign) int result;
  5. - (Manager *(^)(int))add;
  6. @end

.m 檔案中實現我們的 add 方法:

  1. #import "Manager.h"
  2. @implementation Manager
  3. - (Manager *(^)(int))add {
  4.    return ^(int value) {
  5.        _result += value;
  6.        return self;
  7.    };
  8. }
  9. @end

接下來我們再呼叫一下:

  1. // 鏈式實現
  2. - (void)demo4 {
  3.    // 鏈式實現
  4.    int result = [NSObject KY_makeSum:^(Manager *mgr) {
  5.        mgr.add(1).add(2).add(3).add(4).add(5);
  6.    }];
  7.    NSLog(@"運算結果----> %d", result);
  8. }

然後我們執行測試,看下結果:


真的可以了,哈哈哈哈哈????!!!!

Swift 本身就是鏈式的語法結構,所以寫起來就相對簡單的多了,在此不再過多的複數了。


二、響應式程式設計

    響應式程式設計概念性的定義,很難懂!找到的定義是:響應式程式設計是一種面向資料流和變化傳播的程式設計正規化。這意味著可以在程式語言中很方便地表達靜態或動態的資料流,而相關的計算模型會自動將變化的值通過資料流進行傳播。

僅僅看定義的話真的很難理解,在網上找到一個感覺可以加深一下理解。例如,在指令式程式設計環境中,a=b+c表示將表示式的結果賦給a,而之後改變b或c的值不會影響a。但在響應式程式設計中,a的值會隨著b或c的更新而更新。

    響應式程式設計, 開發中我們常見和常用的 Target、Delegate、KVO、通知、時鐘、網路非同步回撥等,都屬於響應式程式設計。(有事件觸發時響應相關事件)。這些我們使用過的方法或許可以幫助我們理解其概念。

OC中有一個響應式程式設計框架 ReactiveCocoa(RAC)

其它的資料暫時還未找到。。。~~~~(>_<)~~~~


三、面向協議程式設計

    我們常聽到的應該是OOP的概念,即物件導向的程式設計。OOP的核心思想是使用封裝和繼承,將一系列相關的東西放到一起。舉個小例子幫助理解一下,看一段小程式碼:

  1. /// 動物類
  2. class Animal {
  3.    func eat() {
  4.        print("吃東西")
  5.    }
  6.    func run() {
  7.        print(" 2 條腿跑")
  8.    }
  9. }
  10. /// 繼承自動物類的人
  11. class Dog: Animal {
  12.    override func run() {
  13.        print(" 4 條腿跑")
  14.    }
  15.    func bite() {
  16.        print("咬人")
  17.    }
  18. }
  19. let dog = Dog()
  20. person.eat()
  21. person.run()

我們定義了一個動物類Animal,然後抽象出它的leg(腿)屬性,以及eat(吃)、run(跑)的方法。子類Dog繼承Animal,並根據自己的特性重寫了run(跑)的方法。而對於eat(吃)已經滿足需求,不必重寫。

Dog和Animal共享了一部分程式碼eat(吃),這部分程式碼被封裝在父類中,所有繼承自Animal的子類都可以使用。這也就是我們剛才提到的OOP的核心思想——使用封裝和繼承,將一系列相關的東西放到一起。

再看,Dog類中我們還寫了一個方法bite(咬人),狗會咬人。然後我們還要寫一個類Wolf(狼)繼承Animal(動物),同樣狼也會咬人。

  1. /// 繼承自動物類的人
  2. class Wolf: Animal {
  3.    override func run() {
  4.        print("四條腿跑")
  5.    }
  6.    func bite() {
  7.        print("咬人")
  8.    }
  9. }

但是我們不得不重新寫bite方法,因為我們無法使用Dog(狗)的bite方法。這時就會看到程式碼的重複性。同樣,我們要在Dog(狗)中新增bark(叫)方法,而狼也會叫,此時就需要在Wolf中再次新增bark(叫)方法。如此下來,程式碼會顯得越來越臃腫,並且擴充套件性和維護性會大大降低。而OC和Swift程式碼中不能夠多繼承!

此時我們就用到了協議的另一個功能,定義統一的方法bark(叫),然後由Dog和Wolf分別遵守該協議:

  1. protocol BarkProtocol {
  2.    func bark()
  3. }

這樣我們就在協議中統一規定了Dog和Wolf共有的Bark(叫)方法,但是這樣貌似也需要在子類中分別實現該方法,但是,這樣寫的好處無疑會比之前的方法好很多。至少它可以在需要的是否對定義的方法進行統一的修改,規範性提高不少。同樣我們還可以這樣給協議介面一個預設實現,這樣就不用在子類中去分別實現了!

  1. import UIKit
  2. protocol BarkProtocol {
  3.    func bark()
  4. }
  5. extension BarkProtocol {
  6.    func bark() {
  7.        print("狼和狗的叫聲")
  8.    }
  9. }

以上所述是一種面向協議的程式設計思想。通過面向協議的思想,我們的實現程式碼得到優化。

  • 協議定義

    • 提供實現的入口

    • 遵循協議的型別需要對其進行實現

  • 協議擴充套件

    • 為入口提供預設實現

    • 根據入口提供額外實現


之前寫過的隔離地圖的設計Demo也是一種面向協議的程式設計思想。

我們的小demo描述的都很簡單,但在實際開發中,我們遇到的問題會比這複雜的多。這就需要對實際問題的具體分析,然後將這樣的程式設計思想應用其中,提高我們的程式碼質量!開發中要培養良好的程式碼習慣,將這些我們看似簡單的東西應用到專案開發中。不要一味地為了實現功能而寫程式碼,更多的融入一些設計思想,會使我們的程式碼更舒適!


相關文章