定义
Many client specific interfaces are better than one general purpose interface.
即:多个特定的客户端接口要好于一个通用性的总接口。
定义解读
- 客户端不应该依赖它不需要实现的接口。
- 不建立庞大臃肿的接口,应尽量细化接口,接口中的方法应该尽量少。
需要注意的是:接口的粒度也不能太小。如果过小,则会造成接口数量过多,使设计复杂化。
优点
避免同一个接口里面包含不同类职责的方法,接口责任划分更加明确,符合高内聚低耦合的思想。
代码讲解
下面通过一个餐厅服务的例子讲解一下接口分离原则。
需求点
现在的餐厅除了提供传统的店内服务,多数也都支持网上下单,网上支付功能。写一些接口方法来涵盖餐厅的所有的下单及支付功能。
不好的设计
//================== RestaurantProtocol.h ==================
@protocol RestaurantProtocol <NSObject>
- (void)placeOnlineOrder; //下订单:online
- (void)placeTelephoneOrder; //下订单:通过电话
- (void)placeWalkInCustomerOrder; //下订单:在店里
- (void)payOnline; //支付订单:online
- (void)payInPerson; //支付订单:在店里支付
@end
在这里声明了一个接口,它包含了下单和支付的几种方式:
-
下单:
- online下单
- 电话下单
- 店里下单(店内服务)
-
支付
- online支付(适用于online下单和电话下单的顾客)
- 店里支付(店内服务)
这里先不讨论电话下单的顾客是用online支付还是店内支付。
对应的,我们有三种下单方式的顾客:
1.online下单,online支付的顾客
//================== OnlineClient.h ==================
#import "RestaurantProtocol.h"
@interface OnlineClient : NSObject<RestaurantProtocol>
@end
//================== OnlineClient.m ==================
@implementation OnlineClient
- (void)placeOnlineOrder{
NSLog(@"place on line order");
}
- (void)placeTelephoneOrder{
//not necessarily
}
- (void)placeWalkInCustomerOrder{
//not necessarily
}
- (void)payOnline{
NSLog(@"pay on line");
}
- (void)payInPerson{
//not necessarily
}
@end
2.电话下单,online支付的顾客
//================== TelephoneClient.h ==================
#import "RestaurantProtocol.h"
@interface TelephoneClient : NSObject<RestaurantProtocol>
@end
//================== TelephoneClient.m ==================
@implementation TelephoneClient
- (void)placeOnlineOrder{
//not necessarily
}
- (void)placeTelephoneOrder{
NSLog(@"place telephone order");
}
- (void)placeWalkInCustomerOrder{
//not necessarily
}
- (void)payOnline{
NSLog(@"pay on line");
}
- (void)payInPerson{
//not necessarily
}
@end
3.在店里下单并支付的顾客:
//================== WalkinClient.h ==================
#import "RestaurantProtocol.h"
@interface WalkinClient : NSObject<RestaurantProtocol>
@end
//================== WalkinClient.m ==================
@implementation WalkinClient
- (void)placeOnlineOrder{
//not necessarily
}
- (void)placeTelephoneOrder{
//not necessarily
}
- (void)placeWalkInCustomerOrder{
NSLog(@"place walk in customer order");
}
- (void)payOnline{
//not necessarily
}
- (void)payInPerson{
NSLog(@"pay in person");
}
@end
我们发现,并不是所有顾客都必须要实现RestaurantProtocol
里面的所有方法。由于接口方法的设计造成了冗余,因此该设计不符合接口隔离原则。
注意,Objective-C中的协议可以通过
@optional
关键字设置不需要必须实现的方法,该特性不与接口分离原则冲突:只要属于同一类责任的接口,都可以放入同一接口中。
那么如何做才符合接口隔离原则呢?我们来看一下较好的设计。
较好的设计
要符合接口隔离原则,只需要将不同类型的接口分离出来即可。我们将原来的RestaurantProtocol
接口拆分成两个接口:下单接口和支付接口。
下单接口:
//================== RestaurantPlaceOrderProtocol.h ==================
@protocol RestaurantPlaceOrderProtocol <NSObject>
- (void)placeOrder;
@end
支付接口:
//================== RestaurantPaymentProtocol.h ==================
@protocol RestaurantPaymentProtocol <NSObject>
- (void)payOrder;
@end
现在有了下单接口和支付接口,我们就可以让不同的客户来以自己的方式实现下单和支付操作了:
首先创建一个所有客户的父类,来遵循这个两个接口:
//================== Client.h ==================
#import "RestaurantPlaceOrderProtocol.h"
#import "RestaurantPaymentProtocol.h"
@interface Client : NSObject<RestaurantPlaceOrderProtocol,RestaurantPaymentProtocol>
@end
接着另online下单,电话下单,店内下单的顾客继承这个父类,分别实现这两个接口的方法:
1.online下单,online支付的顾客
//================== OnlineClient.h ==================
#import "Client.h"
@interface OnlineClient : Client
@end
//================== OnlineClient.m ==================
@implementation OnlineClient
- (void)placeOrder{
NSLog(@"place on line order");
}
- (void)payOrder{
NSLog(@"pay on line");
}
@end
2.电话下单,online支付的顾客
//================== TelephoneClient.h ==================
#import "Client.h"
@interface TelephoneClient : Client
@end
//================== TelephoneClient.m ==================
@implementation TelephoneClient
- (void)placeOrder{
NSLog(@"place telephone order");
}
- (void)payOrder{
NSLog(@"pay on line");
}
@end
3.在店里下单并支付顾客:
//================== WalkinClient.h ==================
#import "Client.h"
@interface WalkinClient : Client
@end
//================== WalkinClient.m ==================
@implementation WalkinClient
- (void)placeOrder{
NSLog(@"place walk in customer order");
}
- (void)payOrder{
NSLog(@"pay in person");
}
@end
因为我们把不同职责的接口拆开,使得接口的责任更加清晰,简洁明了。不同的客户端可以根据自己的需求遵循所需要的接口来以自己的方式实现。
而且今后如果还有和下单或者支付相关的方法,也可以分别加入到各自的接口中,避免了接口的臃肿,同时也提高了程序的内聚性。
下面来看一下这两个设计的UML 类图,可以更形象地看出两种设计上的区别:
UML 类图对比
未实践接口分离原则:
实践了接口分离原则:
通过遵守接口分离原则,接口的设计变得更加简洁,而且各种客户类不需要实现自己不需要实现的接口。
如何实践
在设计接口时,尤其是在向现有的接口添加方法时,我们需要仔细斟酌这些方法是否是处理同一类任务的:如果是则可以放在一起;如果不是则需要做拆分。
做iOS开发的朋友对UITableView
的UITableViewDelegate
和UITableViewDataSource
这两个协议应该会非常熟悉。这两个协议里的方法都是与UITableView
相关的,但iOS SDK的设计者却把这些方法放在不同的两个协议中。原因就是这两个协议所包含的方法所处理的任务是不同的两种:
-
UITableViewDelegate
:含有的方法是UITableView
的实例告知其代理一些点击事件的方法,即事件的传递,方向是从UITableView
的实例到其代理。 -
UITableViewDataSource
:含有的方法是UITableView
的代理传给UITableView
一些必要数据供UITableView
展示出来,即数据的传递,方向是从UITableView
的代理到UITableView
。
很显然,UITableView
协议的设计者很好地实践了接口分离的原则,值得我们大家学习。
网友评论