iOS-設計模式在開發中的應用

設計模式.png

一、六大設計原則

  • 單一職責原則:一個類只負責一件事

  • 依賴倒置原則:抽象不該依賴于具體實現,具體實現可以依賴抽象

  • 開閉原則:對修改關閉,對擴展開放

  • 里氏替換原則:父類可以被子類無縫替換,且原有功能不受影響(例如:KVO)

  • 接口隔離原則:使用多個專門的協議、而不是一個龐大臃腫的協議(例如:UITableViewDelegate,UITableViewDataSource)

  • 迪米特法則:一個對象應當對其他對象盡可能少的了解(高內聚、高耦合)

關于設計原則可以看這篇文章面向對象設計的六大設計原則(附 Demo 及 UML類圖)

二、責任鏈模式

主要思想:對象引用了同一類型的另一個對象,形成一條鏈。鏈中的每個對象實現了相同的方法,處理對鏈中第一個對象發起的同一請求,如果一個對象不知道如何處理,就把請求傳給下一個響應器。

代碼示例:

@class BusinessObject;
typedef void(^CompletionBlock)(BOOL handled);
typedef void(^ResultBlock)(BusinessObject *handler, BOOL handled);
?
@interface BusinessObject : NSObject
?
// 下一個響應者(響應鏈構成的關鍵)
@property (nonatomic, strong) BusinessObject *nextBusiness;
// 響應者的處理方法
- (void)handle:(ResultBlock)result;
?
// 各個業務在該方法當中做實際業務處理
- (void)handleBusiness:(CompletionBlock)completion;
@end
@implementation BusinessObject
?
// 責任鏈入口方法
- (void)handle:(ResultBlock)result
{
  CompletionBlock completion = ^(BOOL handled){
      // 當前業務處理掉了,上拋結果
      if (handled) {
          result(self, handled);
      }
      else{
          // 沿著責任鏈,指派給下一個業務處理
          if (self.nextBusiness) {
              [self.nextBusiness handle:result];
          }
          else{
              // 沒有業務處理, 上拋
              result(nil, NO);
          }
      }
  };

  // 當前業務進行處理
  [self handleBusiness:completion];
}
?
- (void)handleBusiness:(CompletionBlock)completion
{
  /*
    業務邏輯處理
    如網絡請求、本地照片查詢等
    */
}
?
@end

三、橋接模式

橋接模式的目的是把抽象層次結構從其實現中分離出來,使其能夠獨立變更。

1363078-98ccc9b19a331319.png

Class A 和ClassB都是抽象類。ClassA中一個成員變量是ClassB的對象。ClassB中作為抽象類,只提供了默認的接口,并沒有實現。B1、B2、B3是ClassB的三個子類,重寫父類的接口方法,提供不同的實現,此時對于ClassB使用方來說,是感知到不使用了哪個實現。ClassA中有一個handle處理方法,默認調用成員變量ClassB對象中的接口方法。A1、A2、A3三個是ClassA的子類,對于子類來說可以覆寫父類的handle方法,做一些自定義的操作。

代碼示例: ClassA

#import <Foundation/Foundation.h>
#import "BaseObjectB.h"
@interface BaseObjectA : NSObject
?
// 橋接模式的核心實現
@property (nonatomic, strong) BaseObjectB *objB;
?
// 獲取數據
- (void)handle;
@end
#import "BaseObjectA.h"
?
@implementation BaseObjectA
?
/*
  組合方式:
  A1 --> B1、B2、B3         3種
  A2 --> B1、B2、B3         3種
  A3 --> B1、B2、B3         3種
*/
- (void)handle
{
  // override to subclass
  // 處理objB中的方法。
  [self.objB fetchData];
}
?
@end

ClassA的子類A1、A2、A3重寫父類中handle方法。

#import "ObjectA1.h"
?
@implementation ObjectA1
?
- (void)handle
{
  // before 業務邏輯操作

  [super handle];

  // after 業務邏輯操作
}
@end

ClassB 實現

#import <Foundation/Foundation.h>
?
@interface BaseObjectB : NSObject
?
- (void)fetchData;
?
@end
#import "BaseObjectB.h"
?
@implementation BaseObjectB
// 默認邏輯實現
- (void)fetchData
{
  // override to subclass
}
@end

ClassB的子類進行具體的邏輯實現。

#import "ObjectB1.h"
?
@implementation ObjectB1
?
- (void)fetchData{
  // 具體的邏輯處理
}
@end

使用方代碼實現

@interface BridgeDemo()
@property (nonatomic, strong) BaseObjectA *objA;
@end
?
@implementation BridgeDemo
?
/*
根據實際業務判斷使用那套具體數據
A1 --> B1、B2、B3         3種
A2 --> B1、B2、B3         3種
A3 --> B1、B2、B3         3種
*/
- (void)fetch
{
  // 創建一個具體的ClassA
  _objA = [[ObjectA1 alloc] init];

  // 創建一個具體的ClassB
  BaseObjectB *b1 = [[ObjectB1 alloc] init];
  // 將一個具體的ClassB1 指定給抽象的ClassB
  _objA.objB = b1;

  // 獲取數據
  [_objA handle];
}
@end

使用方中定義了ClassA對象,可以使用A1、A2、A3來創建不同的對象,獲取不同的實現組合。BaseObjectB也可以有不同的實現組合。通過橋接模式不同的組合可以實現對象之間的解耦。

橋接模式的優點:

  • 分離抽象接口及其實現部分。

  • 橋接模式有時類似于多繼承方案,但是多繼承方案違背了類的單一職責原則(即一個類只有一個變化的原因),復用性比較差,而且多繼承結構中類的個數非常龐大,橋接模式是比多繼承方案更好的解決方法。

  • 橋接模式提高了系統的可擴充性,在兩個變化維度中任意擴展一個維度,都不需要修改原有系統。

  • 實現細節對客戶透明,可以對用戶隱藏實現細節。

四、適配器

適配器模式(Adapter Pattern):將一個接口轉換成客戶希望的另一個接口,適配器模式使接口不兼容的那些類可以一起工作,其別名為包裝器(Wrapper)。適配器模式既可以作為類結構型模式,也可以作為對象結構型模式。

本節主要學習對象適配器模式,簡單的類結構如下。

1363078-6c858d3c2443e4d1.png

適配對象中一個成員變量指向被適配對象。

示例代碼:類Target是被適配對象,CoolTarget為適配對象。

Target類

#import <Foundation/Foundation.h>
?
@interface Target : NSObject
?
- (void)operation;
?
@end
#import "Target.h"
?
@implementation Target
?
- (void)operation
{
  // 原有的具體業務邏輯
}
?
@end

CoolTarget類:

#import "Target.h"
?
// 適配對象
@interface CoolTarget : NSObject
?
// 被適配對象
@property (nonatomic, strong) Target *target;
?
// 對原有方法包裝
- (void)request;
?
@end
#import "CoolTarget.h"
?
@implementation CoolTarget
?
- (void)request
{
  // 額外處理

  [self.target operation];

  // 額外處理
}
?
@end

適配器優點:

  • 將目標類和適配者類解耦,通過引入一個適配器類來重用現有的適配者類,而無須修改原有代碼。

  • 增加了類的透明性和復用性,將具體的實現封裝在適配者類中,對于客戶端類來說是透明的,而且提高了適配者的復用性。

  • 靈活性和擴展性都非常好,通過使用配置文件,可以很方便地更換適配器,也可以在不修改原有代碼的基礎上增加新的適配器類,完全符合“開閉原則”。

三、單例

單例模式(SingletonPattern):單例模式確保某一個類只有一個實例,而且自行實例化并向整個系統提供這個實例,這個類稱為單例類,它提供全局訪問的方法。

單例模式的要點有三個:一是某個類只能有一個實例;二是它必須自行創建這個實例;三是它必須自行向整個系統提供這個實例。單例模式是一種對象創建型模式。單例模式又名單件模式或單態模式。

示例代碼:

@implementation Mooc
?
+ (id)sharedInstance
{
  // 靜態局部變量
  static Mooc *instance = nil;

  // 通過dispatch_once方式 確保instance在多線程環境下只被創建一次
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
      // 創建實例
      instance = [[super allocWithZone:NULL] init];
  });
  return instance;
}
?
// 重寫方法【必不可少】
+ (id)allocWithZone:(struct _NSZone *)zone{
  return [self sharedInstance];
}
?
// 重寫方法【必不可少】
- (id)copyWithZone:(nullable NSZone *)zone{
  return self;
}
?
@end

注意點:為了防止使用者創建對象,需要從重寫兩個方法allocWithZonecopyWithZone:。另外instance = [[super allocWithZone:NULL] init];需要使用super方法調用防止在第一創建時循環調用。

四、命令模式

命令模式(CommandPattern):將一個請求封裝為一個對象,從而使我們可用不同的請求對客戶進行參數化;對請求排隊或者記錄請求日志,以及支持可撤銷的操作。命令模式是一種對象行為型模式,其別名為動作(Action)模式或事務(Transaction)模式。

代碼實例:一個命令對象和一個命令管理者。

Command

@class Command;
typedef void(^CommandCompletionCallBack)(Command* cmd);
?
@interface Command : NSObject
@property (nonatomic, copy) CommandCompletionCallBack completion; // 執行回調
?
- (void)execute; // 執行
- (void)cancel; // 取消
?
- (void)done; // 完成
?
@end
#import "Command.h"
#import "CommandManager.h"
@implementation Command
?
- (void)execute{

  //override to subclass;

  [self done];
}
?
- (void)cancel{

  self.completion = nil;
}
?
- (void)done
{
  dispatch_async(dispatch_get_main_queue(), ^{

      if (_completion) {
          _completion(self);
      }

      //釋放
      self.completion = nil;
      // 在數組中移除
      [[CommandManager sharedInstance].arrayCommands removeObject:self];
  });
}
?
@end

CommandManager
可以用CommandManager保證任務的順序執行,使用一個正在執行任務數組和一個等待執行任務數組,可以參考SDWebImage圖片下載思路

#import <Foundation/Foundation.h>
#import "Command.h"
@interface CommandManager : NSObject
// 命令管理容器
@property (nonatomic, strong) NSMutableArray <Command*> *arrayCommands;
?
// 命令管理者以單例方式呈現
+ (instancetype)sharedInstance;
?
// 執行命令
+ (void)executeCommand:(Command *)cmd completion:(CommandCompletionCallBack)completion;
?
// 取消命令
+ (void)cancelCommand:(Command *)cmd;
?
@end
#import "CommandManager.h"
?
@implementation CommandManager
?
// 命令管理者以單例方式呈現
+ (instancetype)sharedInstance
{
  static CommandManager *instance = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
      instance = [[super allocWithZone:NULL] init];
  });
  return instance;
}
?
// 【必不可少】
+ (id)allocWithZone:(struct _NSZone *)zone{
  return [self sharedInstance];
}
?
// 【必不可少】
- (id)copyWithZone:(nullable NSZone *)zone{
  return self;
}
?
// 初始化方法
- (id)init
{
  self = [super init];
  if (self) {
      // 初始化命令容器
      _arrayCommands = [NSMutableArray array];
  }
  return self;
}
?
+ (void)executeCommand:(Command *)cmd completion:(CommandCompletionCallBack)completion
{
  if (cmd) {
      // 如果命令正在執行不做處理,否則添加并執行命令
      if (![self _isExecutingCommand:cmd]) {
          // 添加到命令容器當中
          [[[self sharedInstance] arrayCommands] addObject:cmd];
          // 設置命令執行完成的回調
          cmd.completion = completion;
          //執行命令
          [cmd execute];
      }
  }
}
?
// 取消命令
+ (void)cancelCommand:(Command *)cmd
{
  if (cmd) {
      // 從命令容器當中移除
      [[[self sharedInstance] arrayCommands] removeObject:cmd];
      // 取消命令執行
      [cmd cancel];
  }
}
?
// 判斷當前命令是否正在執行
+ (BOOL)_isExecutingCommand:(Command *)cmd
{
  if (cmd) {
      NSArray *cmds = [[self sharedInstance] arrayCommands];
      for (Command *aCmd in cmds) {
          // 當前命令正在執行
          if (cmd == aCmd) {
              return YES;
          }
      }
  }
  return NO;
}
@end

命令模式的優點

  • 降低系統的耦合度。

  • 新的命令可以很容易地加入到系統中。

  • 可以比較容易地設計一個命令隊列和宏命令(組合命令)。

  • 可以方便地實現對請求的Undo和Redo。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容