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]];
总结

工厂模式在实际软件开发中使用的场景是很多的,如果在可预见的未来软件会很有可能发生扩展变化,那么引入工厂方法或者抽象工厂设计出良好扩展的模块还是很有必要的,如果这个模块相对固定不容易改变,那么使用工厂方法也没什么问题,毕竟简单高效才是王道,引入模式反而把问题复杂化了,维护起来工作量反而大了,一点个人不成熟的想法,以上。

相关链接

本文Demo源码传送门>>>

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值