简介
缩写 | 全称 | 中文 |
---|---|---|
S | The Single Responsibility Principle | 单一责任原则 |
O | The Open Closed Principle | 开放封闭原则 |
L | Liskov Substitution Principle | 里氏替换原则 |
I | The Interface Segregation Principle | 接口分离原则 |
D | The Dependency Inversion Principle | 依赖倒置原则 |
单一职责原则
一个类只应承担一种责任。换句话说,让一个类只做一件事。如果需要承担更多的工作,那么分解这个类。
举例
订单和账单上都有流水号、业务时间等字段。如果只用一个类表达,赋予其双重职责,后果:
- 特有属性和共有属性相互掺杂,难以理解;
- 修改一个场景可能会影响另一个场景。
正确的做法是拆成两个独立的类。
开放封闭原则
实体应该对扩展是开放的,对修改是封闭的。即,可扩展(extension),不可修改(modification)。
举例
一个商户接入了多个付款方式,支付宝和微信支付,如果将调用支付API的类写成:
@implementation ZKPayHandler
- (void)pay:(ZKPayParam *)param {
if ([param.type isEqualToString:kPayTypeALiPay]) {
//支付宝付款调用
}else if ([param.type isEqualToString:kPayTypeWeChatPay]) {
//微信支付调用
}
}
@end
那么每次新加一种支付方式,或者修改原有的其中一种支付方式,都要修改PayHandler这个类,可能会影响现有代码。
比较好的做法是将不同的行为(支付方式)抽象,如下:
@protocol PayProcessorProtocol
- (void)handle:(ZKPayParam *)param;
@end
@interface ZKPayHandler ()
@property (nonatomic, strong) NSDictionary> *processorDict;
@end
@implementation ZKPayHandler
- (void)registProcessorDict:(NSDictionary> *)processorDict {
self.processorDict = processorDict;
}
- (void)pay:(ZKPayParam *)param {
id payProcessor = [self.processorDict objectForKey:param.type];
[payProcessor handle:param];
}
@end
@interface AlipayProcessor : NSObject
...
@end
@interface WeChatPayProcessor : NSObject
...
@end
这样,新增支付方式只需要新增类,注册对应的key。修改已有的支付方式只需要修改对应的类。最大化地避免了对已有实体的修改。
里式替换原则
一个对象在其出现的任何地方,都可以用子类实例做替换,并且不会导致程序的错误。换句话说,当子类可以在任意地方替换基类且软件功能不受影响时,这种继承关系的建模才是合理的。
举例
经典的例子: 正方形不是长方形的子类。原因是正方形多了一个属性“长 == 宽”。这时,对正方形类设置不同的长和宽,计算面积的结果是最后设置那项的平方,而不是长*宽,从而发生了与长方形不一致的行为。如果程序依赖了长方形的面积计算方式,并使用正方形替换了长方形,实际表现与预期不符。
扩展
不能用继承关系(is-a),但可以用委派关系(has-a)表达。上例中,可以使用正方形类包装一个长方形类。或者,将正方形和长方形作进一步抽象,使用共有的抽象类。
接口分离原则
客户(client)不应被强迫依赖它不使用的方法。即,一个类实现的接口中,包含了它不需要的方法。将接口拆分成更小和更具体的接口,有助于解耦,从而更容易重构、更改。
举例
仍以商家接入移动支付API的场景举例,支付宝支持收费和退费;微信接口只支持收费。
@protocol PayChannel
- (void)charge;
- (void)refund;
@end
@interface AlipayChannel : NSObject
@end
@implementation AlipayChannel
- (void)charge {
...
};
- (void)refund {
...
};
@end
@interface WeChatChannel : NSObject
@end
@implementation WeChatChannel
- (void)charge {
...
};
- (void)refund {
//没有任何代码
};
@end
第二种支付渠道,根本没有退款的功能,但是由于实现了PayChannel
,又不得不将refund()
实现成了空方法。那么,在调用中,这个方法是可以调用的,实际上什么都没有做!
改进
将PayChannel拆成各包含一个方法的两个接口PayableChannel
和RefundableChannel
。
依赖倒置原则
- 高层次的模块不应依赖低层次的模块,他们都应该依赖于抽象。
- 抽象不应依赖于具体实现,具体实现应依赖抽象。
实际上,依赖倒置是实现开闭原则的方法。
举例
开闭原则的场景仍然可以说明这个问题。以下换一种表现形式。
@implementation ZKPayHandler
- (void)pay:(ZKPayParam *)param {
if ([param.type isEqualToString:kPayTypeALiPay]) {
//支付宝付款调用
AlipayProcessor *processor = [[AlipayProcessor alloc] init];
[processor hander:param];
...
}else if ([param.type isEqualToString:kPayTypeWeChatPay]) {
//微信支付调用
WeChatPayProcessor *processor = [[WeChatPayProcessor alloc] init];
[processor hander:param];
...
}
}
@end
@interface AlipayProcessor : NSObject
...
@end
@interface WeChatPayProcessor : NSObject
...
@end
这种实现方式,PayHandler
的功能(高层次模块)依赖了两个支付Processor
(低层次模块)的实现。随着时间的推移,越来越多的支付方式加入到ZKPayHandler
中,这个ZKPayHandler
模块将会被if/else
语句弄得很乱,而且依赖于很多的低层模块,只要低层模块发生变动,ZKPayHandler
就必须跟着变动,
扩展:IOC和DI
控制反转(IOC)和依赖注入(DI)是Spring中最重要的核心概念之一,而两者实际上是一体两面的。
- 依赖注入
- 一个类依赖另一个类的功能,那么就通过注入,如构造器、setter方法等,将这个类的实例引入。
- 侧重于实现。
- 控制反转
- 创建实例的控制权由一个实例的代码剥离到IOC容器控制,如xml配置中。
- 侧重于原理。
- 反转了什么:原先是由类本身去创建另一个类,控制反转后变成了被动等待这个类的注入。