設計模式:原型模式

RiverLi發表於2018-07-30

概述

我們知道設計模式方面的知識是一個初中級工程師進階高階工程師過程中一道無法跨越的屏障,學好它並將它應用到自己的專案中是一件充滿樂趣和成就感的事情。本文將講述設計模式中的原型模式,通過閱讀本文你將收穫如下內容:

  • 什麼是原型模式以及它的作用。
  • 什麼時間使用原型模式。
  • Objective-C中深拷貝和淺拷貝
  • 原型模式的具體實踐。

下面我們分條講述。

原型模式及其作用

原型模式

如上圖所示,原型模式是指,一個抽象類 Prototype 具有一個clone 方法,其實現類ConcretePrototype1ConcretePrototype2 實現各自的clone方法,在使用的時候,呼叫Prototype的clone方法可以clone任意實現類。其作用就是快速建立一個新的物件。請看如下程式碼:

Prototype *type = [[ConcretePrototype1 alloc] init];
//(1)對type所持有的變數進行賦值。
type.a = 
type.b = 
//(2)儲存type的現有狀態
[array addObject:type];

//(3)繼續變更type的資訊。
type.a = 
type.b =
複製程式碼

在上訴程式碼中,在步驟(2)中我們需要暫存一下當前type的狀態,以便後續做比較或者其他用途。按照上面的程式碼是無法實現我們的需求的,因為把type加到陣列之後,之後type的值依然再改變。為了不然type的值變化,我們可能這樣做:

Prototype *type = [[ConcretePrototype1 alloc] init];
//(1)對type所持有的變數進行賦值。
type.a = 
type.b = 

//(2)儲存type的現有狀態
Prototype *tempType = [[ConcretePrototype1 alloc] init];
tempType.a = type.a
tempType.b = type.b
[array addObject:tempType];

//(3)繼續變更type的資訊。
type.a = 
type.b =
複製程式碼

想一想,你是否寫過這樣的程式碼?這樣的程式碼有什麼不好呢?

  1. 程式碼冗餘,如果需要多次儲存狀態,可能需要寫多個這樣的賦值邏輯,當然,你可以把它抽出來作為一個單獨的函式。
  2. 如果type物件的屬性中包含了多個其他物件,那麼簡單的賦值操作並不能儲存這些物件的狀態,還需要去建立這些物件,並拷貝其內部屬性,這是相當繁瑣的工程。

看到這裡你可能會想,我根本不會這麼做,我會使用NSObject提供的copy方法,實現NSCopying 協議進行復制。你的想法非常贊,其實NSCopying就是Cocoa框架提供的一種原型模式,在講解之前,我們先說一下,Objective-C的淺拷貝和深拷貝。

何時使用原型模式

  • 需要建立的物件應獨立於其型別與建立方式。
  • 要例項化的類是在執行時決定的。
  • 不想要與產品層次相對應的工廠層次。
  • 不同類的例項間的差異僅是狀態的若干組合。因此複製相應數量的原型比手工例項化更加方便。
  • 類不容易建立,比如每個元件可把其他元件作為子節點的組合物件。複製已有的組合物件並對副本進行修改會更加容易。
  • 從功能的角度來講,不管什麼物件,只要複製自身比手工例項化要好,都可以是原型物件。

在以下兩種特別常見的情形,我們會考慮使用此模式:

  • 有很多相關的類,其行為略有不同,而且主要差異在於內部屬性,如名稱,影像等。
  • 需要使用組合(樹型)物件作為其他東西的基礎。例如,使用組合物件作為元件來構建另一個組合物件。

淺拷貝和深拷貝

我們知道,OC中的變數引用有值引用和指標引用。對於值引用而言,沒有深拷貝和淺拷貝的區分,區別在於指標引用。我們先看淺拷貝模型。

淺拷貝

深拷貝

深拷貝

由上面兩個模型可以看出深拷貝是將記憶體中的資源也進行了一份拷貝,而淺拷貝只是記憶體資源指標的拷貝。

原型模式的實踐

填寫表單使我們在我們生活中經常遇到,我們要填很多表單,比如說上學要填報名表,上班要填職位表等等,有時候我們填了一般忘記了關鍵資訊或者有其他重要的事要先處理,我們需要先把現有表單資訊儲存下來,等有時間了再拿出來繼續填寫。

首先我們需要一個表單協議

@protocol Form <NSObject, NSCopying>
@property (copy, nonatomic) NSString *name;
@property (copy, nonatomic) NSString *address;
@property (strong, nonatomic) NSMutableArray *relatedForm;

- (void)printSelf;
- (void)addForm:(id)form;
- (id)copy;

@end
複製程式碼

我們還有一個學校的表單:

@interface SchoolForm : NSObject <Form>
@end


@implementation SchoolForm
@synthesize address;
@synthesize name;
@synthesize relatedForm;

- (instancetype)init:(NSString *)name address:(NSString *)address
{
    self = [super init];
    if (self) {
        self.name = name;
        self.address = address;
        self.relatedForm = [[NSMutableArray alloc] init];
    }
    return self;
}

- (void)printSelf {
    NSLog(@"name=%@,address=%@", self.name, self.address);
}

- (void)addForm:(id)form {
    [self.relatedForm addObject:form];
}


- (nonnull id)copyWithZone:(nullable NSZone *)zone {
    SchoolForm *form = [[self.class allocWithZone:zone] init:self.name address:self.address];
    
    for (id<Form> tform in self.relatedForm) {
        [form.relatedForm addObject:[tform copy]];
    }
    return form;
}
@end
複製程式碼

使用

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    SchoolForm *schoolForm1 = [[SchoolForm alloc] init:@"清華大學" address:@"北京"];
    SchoolForm *schoolForm2 = [[SchoolForm alloc] init:@"北京大學" address:@"北京"];
    [schoolForm1 addForm:schoolForm2];
    
    SchoolForm *copySchool = [schoolForm1 copy];
    
    NSLog(@"copySchool");
}

@end
複製程式碼

這裡有一個非常有趣的問題,為什麼要在Form協議裡面新增一個copy方法,如果不寫成copy,寫為clone方法會怎麼樣?

答案是NSObject類裡面含有copy方法,當呼叫copy方法的時候回自動呼叫copyWithZone方法,我們在SchoolForm的copyWithZone方法裡遍歷了所用相關的form, 二這些form的型別實在執行時才能確定的,可能是schoolForm也可能是JobForm等等。但無論是什麼型別的Form,它都繼承自NSObject。所以這是一個偷樑換柱的操作,看起來像是呼叫Form協議裡的copy方法,其實是呼叫NSObject的copy方法。換成clone的話就不能使用NSCopying協議了。

參考

本文參考《Objective-C程式設計之道:iOS設計模式解析》 你可以在公眾號裡回覆"資源"得到本書的電子版,當然建議購買正版。

其他

本文首發於RiverLi的個人公眾號,如需轉載請與我本人聯絡, 微信掃碼關注本公眾號。

RiverLi的公眾號

相關文章