美文网首页
设计模式在倍牛工程中的应用

设计模式在倍牛工程中的应用

作者: 迷路的安然和无恙 | 来源:发表于2018-04-08 16:14 被阅读25次

前言

脱离了代码谈设计模式,就像是脱离了业务谈架构一样。很多时候,觉得别人代码写的好,是离不开合理的运用设计模式的,本篇就重点用代码讲述,倍牛工程中用到的设计模式,文中关于设计模式的定义来自《大话设计模式》一书。
从最常用的工厂模式说起:

工厂模式

工厂模式是我们最常用的实例化对象模式,是用工厂方法代替new操作的一种模式。

在倍牛工程中,用到的UPHKEmpty类,在该类的.h中如下:

/*
 * 默认没有数据 占位view
 * */
+ (instancetype)defaultEmptyView;

/*
 * 转菊花样式 占位view
 * */
+ (instancetype)progressEmptyView;

/*
 * 网络异常 占位view
 * */
+ (instancetype)netErrorEmptyViewWithHandler:(UPHKEmptyViewHandler)handler;

/**
 *  通用的无网络的"雷达"空白页 点击可从新加载
 */
+ (instancetype)reconnectEmptyViewWithHandler:(UPHKEmptyViewHandler)handler;

/*
 * 自选空白 占位view
 * */
+ (instancetype)optionalEmptyViewWithHandler:(UPHKEmptyViewHandler)handler;

工厂方法也可以是如下这个初始化的方法:

/**
 *  工厂方法
 */
+ (instancetype) modelWithStartPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint;

这样理解起来也并不困难。

根据不同的场景,使用不同的工厂方法,即可。

单一职责原则

先说个人理解,单一职责原则,我一直理解为,在一个类中,要保证该类的功能是单一的,也就是专一,该类不能既承担A功能,又承担B功能,不然会造成功能耦合,从而导致设计脆弱、扩展性差,此原则的核心就是解耦和增强内聚性。
单一职责原则的定义如下:

就一个类而言,应该仅有一个引起它发生变化的原因。

我并不能很好的理解“仅有一个引起它发生变化的原因”这句话。
所以在联想到倍牛工程的时候,我觉得,数据库的创建和读取的设计,类UPHKDBHelperUPHKUserDBManagerUPHKDBHelper是直接操作数据库的类,负责UPHKUserSDK中数据的IO操作,将读取或者需要写入的数据,回调或者写入到数据库中,但是这里的单一职责我就认为它是功能单一,或者唯一,是唯一直接操作数据库的类。
UPHKDBHelper.h中的代码如下,

//  数据表操作相关
- (id)initDBWithName:(NSString *)dbName;

- (id)initWithDBWithPath:(NSString *)dbPath;
...

///************************ 数据库存取相关 *****************************************

- (void)putObject:(id)object withId:(NSString *)objectId intoTable:(NSString *)tableName;

- (id)getObjectById:(NSString *)objectId fromTable:(NSString *)tableName;
....

其实UPHKDBHepler理解为单一职责原则,我并不认为合理,还需探讨。
我认为在倍牛工程中比较符合单一职责设计模式的是UPHKCommDataClient以及与它相似的类。
UPHKCommDataClient.h

//  下载
- (UPHKResponse *)downloadFile:(NSString *)filePath;

/**
 * 上传图片到服务器
 */
- (UPHKResponse *)uploadImageFile:(UIImage *)image;

UPHKCommDataClient负责了整个工程的文件下载和图片的上传,职责单一,也不依赖上层逻辑,做到“仅有一个引起它发生变化的原因”,在倍牛工程共还有许多与之相似的类,如UserSDK中负责线程管理的UserServer、负责组包的UserService、负责底层网络请求的UserClient等等都有单一职责的应用。

开放封闭原则

是说软件实体(类、模块、函数等),应该可以扩展,但是不可修改。
开放封闭原则主要体现在两个方面:对于扩展是开放的,对于更改是封闭的。

在倍牛及股票通工程中使用的UPHybridSDK,是严格开放封闭原则进行设计的。


image.png

在此之前,我从未想过一个WebView要做这么深层次的封装,也才知道,WebView的功能,可以以插件的形式设计,对HybridSDK结构理解还不够深刻,只是拿来UPHybridPlugin这个类,对开放封闭原则这一设计模式做深入理解用。
UPHybridPlugin(插件基类),在实现上只是将事件分发给UPHybridPluginManager。在native需要与web页面交互的时候,execute方法就是web调用终端的onJsRequest时触发的方法,actionargs分别是需要终端处理的事件和终端需要的参数。

-(BOOL)execute:(NSString *)callbackId action:(NSString *)action args:(NSDictionary *)args;

在创建功能插件时,必须继承UPHybridPlugin,并重载execute方法。在UPHybirdSDK的结构设计完成后,父类UPHybridPluginexecute方法就不再更改,但是无论模块多么的封闭,都会存在一些无法对之封闭的变化,我理解的变化,针对UPHybridPlugin来说,就是不断增加的功能,就需要各式各样的功能插件,功能是随需求不断变化的,既然不可能完全封闭,就必须对设计的模块应该对那种变化封闭做出选择,然后构造抽象来隔离这些变化,execute其实就是这样抽象出来的方法。
对UPHybridSDK的可以逐步的理解如下:最初写UPHybird时,假设只有分享功能,就不存在当前插件形式的结构,但是随着拍照功能、页面定制功能的增加,这时候,应该对程序中呈现频繁变化的那部分做出抽象,变化时什么?可以理解为具体的功能代码的实现,但是不变的是什么?是底层Web调用native的结构,这时候需要抽象的部分就形成了,面对新功能,对程序的改动是通过增加新插件(新代码)的形式进行的(可扩展),而不是更改现有的代码(封闭)。

里氏代换原则

子类型必须能够替换掉他们的父类型。

一个软件实体如果使用的是一个父类的话,那么一定适用于其子类,而且它察觉不出父类对象和子类对象的区别。也就是说,在软件里面,把父类都替换成它的子类,程序的行为没有变化。
我理解的里氏代换原则,是开放封闭原则的根本,也就是说,正是由于子类型的可替换性才使得使用父类类型的模块在无需修改的情况下就可以扩展。

依赖倒转原则

高层模块不应该依赖于低层模块。两个都应该依赖抽象。抽象不应该依赖细节,细节应该依赖抽象。

按照UserSDK和User模块的关系,可以理解User是业务逻辑相关的,属于高层模块,UserSDK是低层模块,这样的依赖关系是符合工程模块化设计原理的。


image.png

但是我认为把这种设计理解成依赖倒转的原则,有一点不妥。按照依赖倒转原则,高层模块不应该依赖低层模块,两个都应该依赖抽象(有些拗口)。
在高层模块中,多处调用了低层模块提供的接口,在要做新项目时,如果需求是不更改App的UI布局,换另一个UserSDK直接提供数据,也就意味着,高层模块的业务逻辑完全可以复用,但高层模块多处调用的低层接口是不是要多处修改?

所以按照依赖倒转的原则应该是:


image.png

但实际开发考虑到实际情况,很少这样去设计,也是为了便捷开发,适合自己的才是最好的。

代理模式

######为其他对象提供一种代理以控制对这个对象的访问。
在iOS代理模式最常见的实现方式是协议(Protocol),协议定义了接口,无需关心代理是谁,只要遵循并实现协议,就可以访问到这个对象,在倍牛和股票通中的Router是按照代理模式设计的。
Router跳转个股详情为例
RouterProtocol中定义协议代码如下:

@protocol UPHKMarketRouterDelegate <NSObject>
/*
 进入股票行情页面
 
 @param code 股票代码
 @param setCode 市场代码
 @param category 市场分类
 */
- (void)goMarketHQWithCode:(NSString *)code setCode:(NSInteger)setCode category:(UPMarketStockCategory)category;
@end

在个股详情页遵循并实现该协议

- (void)goMarketHQWithCode:(NSString *)code setCode:(NSInteger)setCode category:(UPMarketStockCategory)category
{
  ...
}

那么遵循协议者就是对代理者开放了一种对当前对象的访问权限。

迪米特法则

如果两个类不需要彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。

先说我的理解,在学习迪米特法则这个设计模式之前,我是经常听到大家这么说的,但是并不知道这其实是一种设计模式。按照之前的理解,每一个类在结构设计上,都应当尽量降低成员的访问权限,也就是说,一个类包装好了自己的private状态,不需要让别的类知道的字段或行为就不要公开,它强调类之前的松耦合,这就是该设计模式的根本思想。

迪米特法则在倍牛中有广泛应用,如UserSDK中的UserServer,在.m将用户数据UserData私有,对外只提供对应的方法,满足条件的情况下会在内部对该属性进行管理,而非将该属性暴露出去,我也认为这一个类,同时满足了几种设计模式。

职责链模式

使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。 image.png
  • 职责链的好处

①当提交一个请求时,请求沿链传递直至有一个Handler对象负责处理它。
②接受者和发送者都没有对方的明确信息,且链中的对象自己也不知道链的结构。结果是职责链可简化对象的相互连接,他们仅需保持一个指向其后继者的引用,而不需要保持它所有候选接受者的引用。降低了耦合度。
③随时随地增加或修改一个请求的结构,增强了给对象指派职责的灵活性。

  • 职责链的应用

在倍牛工程中,并没有非常符合职责链设计模式的代码示例,但是我依然觉的这个设计模式非常有意思。
在大话设计模式一书中,作者是用员工、经理、总监、总经理的角色分工来举例说明的。我举一个iOS中的例子,我认为iOS中的响应链是按照职责链模式进行设计的。
当用户触摸应用中的一个Button时,触摸生成的Event会沿响应链进行传递,并找到最适合处理事件的对象。
UIApplication ~> UIWindow ~> UIViewController~~>UIButton
在查找适合处理事件的对象时,系统会在不同的处理者自动调用pointInside方法,如下

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    [super pointInside:point withEvent:event];
}

pointInside方法返回YES的时候,会将该视图加入到UIApplication的响应者栈。
在iOS中响应者都继承自UIResponder,查找当前响应者的下一个响应者可以通过[self nextResponder]获取,如果当前响应者不处理事件,可以将事件继续传递给父类,父类再进行分发。
在响应链中,满足了一个请求可以被多个对象处理,并且每个对象仅需保持一个指向其后继者的引用的条件,所以我认为响应链就是按照职责链设计模式进行设计的。

适配器模式

将一个类的接口转换成需求需要的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
  • 应用场景

当数据和行为都正确,但接口不符时,我们应该考虑使用适配器,目的是使控制范围之外的一个原有对象和某个接口匹配。适配模式主要应用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况。

  • 理解

在看安卓代码的时候,会发现有各式各样的Adapter,如下:

public class MarketAmountListAdapter extends MarketBaseRecyclerAdapter {

    private final Context mContext;
    private final ArrayList<UPMarketData> mDataList;

    public MarketAmountListAdapter(Context context) {
        mContext = context;
        mDataList = new ArrayList<>();
    }

但是在iOS中,很少看到专门的Adapter,但并不代表没有,多数时候数据的适配或处理是在数据模型中完成的,所以并不需要创建单独的Adapter
网络请求回来的数据模型如UPHqStockHq和UI控件需要显示的数据往往有些差别,如类型不匹配、字体颜色未知等,这时候就需要对改模型进行适配.h

@interface UPHKMarketHSGTAHStockModel : NSObject
@property (nonatomic, strong) UPMarketAHStockData *ahStock;

@property (nonatomic, strong) UIImage *marketFlagImage;
...
@property (nonatomic, copy) NSString *aChangeRadio;

//  Color
...
@property (nonatomic, strong) UIColor *aNowPriceColor;
@property (nonatomic, strong) UIColor *aChangeRadioColor;
@property (nonatomic, strong) UIColor *premiumRateColor;
@end

.m

@implementation UPHKMarketHSGTAHStockModel

- (UIImage *)marketFlagImage {
    return UPHKImage(@"up_hk_market_hk");
}
...
- (UIColor *)hChangeRadioColor {
    return [UPCompareTool compareWithData:self.ahStock.hItem.changeRatio baseData:0 precise:3];
}
- (UIColor *)premiumRateColor {
    return [UPCompareTool compareWithData:self.ahStock.premiumRate baseData:0 precise:3];
}
@end
使用一个已经存在的类,但如果它的接口,也就是它的方法和你的要求不相同时,就应该考虑用适配器模式。
  • 讨论

jayma(马杰) 4-4 上午 9:25
我觉得对一个类或是一个模块的设计,肯定不是一成不变的,随着功能的增加,需求的变化,架构和模式是一个持续修改和优化的过程。
就现在来看倍牛里面还是有很多需要优化的地方。比如UserSDK里面的模块划分,分层结构,各个类的职责和功能,用户状态机的管理等等,现在感觉实现有点冗余,逻辑有点混乱,有很大的优化空间。
还有TradeSDK对UserSDK的依赖调用,是否有更加合理的方式等等

jayma(马杰) 4-4 上午 9:27
可以从代码优化的方式入手,对着各种设计模式,多思考一下,这样搞过几轮后,理解应该会更加深入 [强]

....(单例模式、迭代器模式等待补充)

相关文章

  • 设计模式在倍牛工程中的应用

    前言 脱离了代码谈设计模式,就像是脱离了业务谈架构一样。很多时候,觉得别人代码写的好,是离不开合理的运用设计模式的...

  • 7、结构型模式-桥接设计模式

    1、简介:学设计模式的疑惑,桥接设计模式的介绍 打个鸡血 牛逼的工程师,在日常开发,明明用了某种设计模式了,但他还...

  • 设计模式在源码中的应用??

    设计模式在源码中的应用

  • 单利设计模式

    单利模式的介绍 单利模式是应用最广的模式之一,也可能是很多初级工程师唯一会使用的设计模式。在应用这个模式时,单利对...

  • Java 设计模式笔记一

    1.Java的设计模式及原则 (1)什么是设计模式? 在软件工程中,设计模式是对软件设计中普遍存在的各种问题,所提...

  • iOS学习之设计模式

    设计模式的定义(引用维基百科) 在软件工程中,设计模式(design pattern)是对软件设计中普遍存在(反复...

  • 设计模式读书笔记一 单例模式

    1、单例模式介绍单例模式是应用最广泛的模式之一,也是可以说是初级工程师唯一会用的设计模式。在应用这一模式的时候,单...

  • 设计模式 - 概述

    在软件工程中,设计模式(design pattern)是对软件设计中普遍存在的各种问题,所提出的解决方案。设计模式...

  • 设计模式之单例模式

    单例模式 介绍 单例模式是应用最为广泛的模式之一,也可能是很多入门或初级工程师唯一会使用的设计模式之吧,在应用这个...

  • Retrofit源码设计模式解析(下)

    本文将接着《Retrofit源码设计模式解析(上)》,继续分享以下设计模式在Retrofit中的应用: 适配器模式...

网友评论

      本文标题:设计模式在倍牛工程中的应用

      本文链接:https://www.haomeiwen.com/subject/kibnqftx.html