1. 程式人生 > >IOS 工廠模式的面向協議程式設計思想

IOS 工廠模式的面向協議程式設計思想

前言

OOP開發有個原則是針對抽象程式設計而不是針對具體程式設計,實際的軟體開發中,因為時間和專案進度等客觀不可抵抗和主觀的因素,我們偏向使用最簡單的的方式去實現功能,而沒有考慮到未來可能會有的擴充套件問題,導致未來發生擴充套件的時候出現了維護性的災難,軟體模組不好擴充套件,需求變動就得修改模組,這就違反了開閉原則,所以,很有必要在設計的時候去考慮未來可能會引入的變化,使用合適的模式去應對未來的這種變化。

簡單工廠

簡單工廠作為工廠模式的最簡單的一種,與其說是一種模式,不如說是一種程式設計習慣,軟體開發中,我們會無意識或者有意思的把經常用到的那部分內容抽取到一個模組中統一建立,而不是在多個使用者單獨的建立,這也遵循軟體開發中的don't repeat yourself

原則,一般的我們會把建立的方法定義為靜態的方法,如果內容有差異,一般滴我們會使用型別進行區分,比如下面程式碼,我們建立不同型別的mapview物件,這是一個典型的簡單工廠的例子。

//
//  SimpleMapFactory.h
//  DesignPatternProject
//
//  Created by aron on 2017/5/18.
//  Copyright © 2017年 aron. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

typedef NS_ENUM(NSUInteger, MapType) {
    MapTypeBaidu,
    MapTypeGaode,
    MapTypeTencent,
};

@interface SimpleMapFactory : NSObject

+ (UIView*)mapViewWithFrame:(CGRect)frame type:(MapType)mapType;

@end


//
//  SimpleMapFactory.m
//  DesignPatternProject
//
//  Created by aron on 2017/5/18.
//  Copyright © 2017年 aron. All rights reserved.
//

#import "SimpleMapFactory.h"
#import <MAMapKit/MAMapKit.h>
#import <AMapFoundationKit/AMapFoundationKit.h>
#import <BaiduMapAPI_Map/BMKMapView.h>


@implementation SimpleMapFactory

+ (UIView*)mapViewWithFrame:(CGRect)frame type:(MapType)mapType {
    if (mapType == MapTypeGaode) {
        MAMapView *maMapView = [[MAMapView alloc] initWithFrame:frame];
        return maMapView;
    } else if (mapType == MapTypeBaidu) {
        BMKMapView* mapView = [[BMKMapView alloc]initWithFrame:frame];
        return mapView;
    }
    
    return nil;
}

@end

簡單工廠的侷限

定義一個靜態方法+ (UIView*)mapViewWithFrame:(CGRect)frame type:(MapType)mapType,實現中使用分支語句建立不同的例項,如果後面有其他型別的例項,那麼這個方法就得進行相應的修改,如果型別變得多了,建立的過程複雜了,這個模組就得經常的修改,這還沒什麼,簡單工廠返回的是一個UIView的通用型別,使用者需要強轉為對應的型別,才能充分的使用到這個UIView物件,在這個場景中MAMapViewBMKMapView這兩個類的介面是完全不同的,這意味著,添加了一個新型別,修改的不僅僅是工廠靜態方法,呼叫者的使用方式也必須進行相應的修改,違法了開閉原則

,讓維護和擴充套件變得困難起來了,這不是我們想要的。

工廠方法

工廠方法是對簡單工廠的抽象,讓工廠具有了良好的擴充套件性,使得容易擴充套件和維護,工廠方法抽象了兩個方面,首先對產品進行了抽象,在上面的案例中就是對mapview進行了抽象,在抽象了mapview中定義公共的介面提供給呼叫者使用;其次對工廠進行了抽象,工廠返回的不是一個具體的mapview,而是抽象之後的mapview,呼叫者可以使用抽象的mapview種定義的公共的介面和具體的mapview物件互動。
工廠方法的UML描述如下:
工廠方法類圖

工廠方法程式碼實現

  • 對產品的抽象
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@protocol MapView <NSObject>

- (instancetype)initWithFrame:(CGRect)frame;

- (UIView*)getView;

@end
  • 對工廠的抽象
#import <Foundation/Foundation.h>
#import "MapView.h"

@protocol MapFactory <NSObject>

+ (id<MapView>)mapViewWithFrame:(CGRect)frame;

@end
  • 具體的產品,以百度地圖為例
#import "BaiduMapView.h"
#import <BaiduMapAPI_Map/BMKMapView.h>

@interface BaiduMapView () {
    BMKMapView* _mapView;
}

@end

@implementation BaiduMapView

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super init];
    if (self) {
        BMKMapView* mapView = [[BMKMapView alloc]initWithFrame:frame];
        _mapView = mapView;
    }
    return self;
}

- (UIView *)getView {
    return _mapView;
}

@end
  • 具體的工廠(百度地圖建立工廠)
#import "BaiduMapView.h"
#import <BaiduMapAPI_Map/BMKMapView.h>

@interface BaiduMapView () {
    BMKMapView* _mapView;
}

@end

@implementation BaiduMapView

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super init];
    if (self) {
        BMKMapView* mapView = [[BMKMapView alloc]initWithFrame:frame];
        _mapView = mapView;
    }
    return self;
}

- (UIView *)getView {
    return _mapView;
}

@end

上面的程式碼片段就是一個簡單的工廠方法的例子,抽象的產品只提供了一個介面,真是的場景使用到的不止一個公有介面,因需求而定。

  • 呼叫者的呼叫方式
    id<MapView> mapView = [BaiduMapFactory mapViewWithFrame:CGRectMake(0, 0, 320, 200)];
    [self.view addSubview:[mapView getView]];
    
    id<MapView> maMapView = [GaodeMapFactory mapViewWithFrame:CGRectMake(0, 200, 320, 200)];
    [self.view addSubview:[maMapView getView]];

當需求有變化需要替換底層元件,呼叫者只要修改工廠就行了,需要新增相應的具體產品和具體的工廠就行了,不會依賴於具體的實現,擴充套件起來相當的方便,當然,因為抽象級別的提高,程式碼量也會相應的變多,不過這是必要的犧牲,魚和熊掌不可兼得。

抽象工廠

工廠方法返回的是多個同種型別的物件,未來的擴充套件我們可能會遇到返回的是一組同種型別的物件,比如在我們的軟體場景中,我麼未來可能擴充套件我們的工廠返回定位物件,這種場景,需要定義一個定位物件的協議,工廠協議需要新增一個公共介面返回一個定位物件,這樣工廠方法轉換為了抽象工廠,可以這麼說抽象工廠是對工廠方法的再次抽象和擴充套件。
抽象工廠的UML描述如下:
抽象工廠類圖

抽象工廠程式碼實現

抽象工廠在工廠方法的基礎上進行了擴充套件,添加了兩部分:1、添加了一個Location抽象介面和Location對應的實現;2、工廠的介面添加了一個返回Location物件的公有方法。下面程式碼只展示了新增加的部分,完整的程式碼可以檢視文章底部的連結。

  • Location抽象介面
#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>

@protocol AbsLocation <NSObject>

- (void)startLocateWithResult:(void(^)(CLLocation* location))complete;

@end
  • 工廠的介面添加了一個返回Location物件的公有方法
@protocol AbsMapFactory <NSObject>

+ (id<AbsMapView>)mapViewWithFrame:(CGRect)frame;

+ (id<AbsLocation>)location;

@end
  • Location抽象介面的實現,例子只是模擬,真正的以實際的定位物件為準

//.h

#import <Foundation/Foundation.h>
#import "AbsLocation.h"

@interface AbsBaiduLocation : NSObject <AbsLocation>

@end

.m

#import "AbsBaiduLocation.h"

@implementation AbsBaiduLocation

- (void)startLocateWithResult:(void(^)(CLLocation* location))complete {
    NSLog(@"BaiduLocation started");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 模擬返回
        !complete ?: complete([[CLLocation alloc] initWithLatitude:123 longitude:123]);
    });
}
@end

使用方法:

 // abstract factory usuage
    id<AbsMapView> absBaiduMapView = [AbsBaiduMapFactory mapViewWithFrame:CGRectMake(0, 0, 320, 200)];
    [self.view addSubview:[absBaiduMapView getView]];
    id<AbsLocation> baiduMapLocation = [AbsBaiduMapFactory location];
    [baiduMapLocation startLocateWithResult:^(CLLocation *location) {
        NSLog(@"location result");
    }];
    
    
    id<AbsMapView> absGaodeMapView = [AbsGaodeMapFactory mapViewWithFrame:CGRectMake(0, 200, 320, 200)];
    [self.view addSubview:[absGaodeMapView getView]];

總結

工廠模式在實際軟體開發中使用的場景是很多的,如果在可預見的未來軟體會很有可能發生擴充套件變化,那麼引入工廠方法或者抽象工廠設計出良好擴充套件的模組還是很有必要的,如果這個模組相對固定不容易改變,那麼使用工廠方法也沒什麼問題,畢竟簡單高效才是王道,引入模式反而把問題複雜化了,維護起來工作量反而大了,一點個人不成熟的想法,以上。

相關連結