接口隔离原则(Interface Segregation Principle, ISP):使用多个专门的接口,而不使用单一的总接口,即客户端不该该依赖那些它不须要的接口。html
从接口隔离原则的定义能够看出,他彷佛跟SRP有许多类似之处。 是的其实ISP和SRP都是强调职责的单一性, 接口隔离原则告诉咱们在定义接口的时候要根据职责定义“较小”的接口,不要定义“高大全”的接口。也就是说接口要尽量的职责单一,这样更容易复用,暴露给客户端的方法更具备“针对性”, 好比定义一个接口包括一堆访问数据库的方法, 有包括一堆访问网络的方法,还包括一些权限认证的方法。 把这么一摊子风牛马不相及的方法封装到一个接口里面,显然是不合适的, 若是客户程序只想用到数据访问的一些功能,可是调用接口的时候你把访问网络的方法和权限认证的方法暴露给客户,这使得客户程序感到“疑惑”,那么这个接口就不ISP,它很显然的构成了接口污染。sql
注意: 这里所说的接口是广义上的接口,他是一组契约, 是提供给程序交互的一组约定,并不是各类语言interface 关键字定义的一组方法的结集合。可是这里所说的接口能够用各类语言的关键字interface 来定义,固然也能够用抽象类,类等等来定义。数据库
假设有个客户提出了软件系统的需求:微信
1. 用户可使用第三方QQ,微信,微博登陆到系统。网络
2.系统中包括人员管理人员管理。数据库设计
3.访问第三方的API获取一些数据。ide
好了拿到这个需求后首先通过分析,简单的原型设计,数据库设计以后开始编写代码了。 一般第一步定义接口。很快接口就定义出来了以下:url
public interface IObject { void Connection(string connectionString); SqlDataReader ExcuteSql(string sql); string LoginWithQQ(string token); string LoginWithWeibo(string token); string LoginWithWeiXin(string token); string GetDataFromAPI(string url, string token); }
这个看起来还不错,接口已经定义了,写个具体类继承一下这个接口并实现全部的方法,如今就能够实现业务,写界面了。 等过了几天客户说 在给我加上支付宝登陆。那好再加一个支付宝登陆接口,代码如今长这样子:spa
public interface IObject { void Connection(string connectionString); SqlDataReader ExcuteSql(string sql); string LoginWithQQ(string token); string LoginWithWeibo(string token); string LoginWithWeiXin(string token); string GetDataFromAPI(string url, string token); string LoginWithAlipay(string token); }
再在实现类中实现一下LoginWithAlipay方法 就行了。设计
时间在推移,一天客户说再给我加个百度登陆,好吧套路有了加一个就是了,有啥了不得。 时间依旧。。。 客户说加个 facebook 登陆, 。。。加个 Linkedin。。。, 尼玛 没完没了了, 如今接口已经变成这样子了:
public interface IObject { void Connection(string connectionString); SqlDataReader ExcuteSql(string sql); string LoginWithQQ(string token); string LoginWithWeibo(string token); string LoginWithWeiXin(string token); string GetDataFromAPI(string url, string token); string LoginWithAlipay(string token); string LoginWithTwitter(string token); string LoginWithFaceBook(string token); string LoginWithRenRen(string token); string LoginWithBaidu(string token); string LoginWithDropbox(string token); string LoginWithGithub(string token); //这里省略10000字 stringLoginWithLinkedin(string token); }
有一天这个接口本身都不想看了,太多方法了,更况且实现类中的代码都七八千行了。
因而决定重构, 如今回头看看这个接口早就应该重构了,甚至一开始定义的时候就应该拆分,接口的名字都不知道怎么命名(通常在写代码的时候类,接口,方法的名字不知道怎么命名的时候就是该重构的时候了)居然起了IObject这么奇葩的名字,这个设计显然是烂到家了, 他几乎违背了咱们讲过的全部设计原则, 必须到了要重构的时候了。
来吧,重构吧,通过分析第一步先根据功能来划分将IObject接口拆分红三个“小”接口:
1.数据库操做相关的抽取到一个接口中(IDatabaseProvider)。
2.第三方API调用相关的方法抽取到一个接口中(IThirdpartyAPIProvider)。
3.第三方登录相关的方法抽取到一个接口中(IThirdpartyAuthenticationProvider)。
如今代码变成这个样子:
public interface IDatabaseProvider { SqlDataReader ExcuteSql(string sql); string LoginWithQQ(string token); } public interface IThirdpartyAPIProvider { string Get(string url, string token); } public interface IThirdpartyAuthenticationProvider { string LoginWithQQ(string token); string LoginWithWeibo(string token); string LoginWithWeiXin(string token); string LoginWithAlipay(string token); string LoginWithTwitter(string token); string LoginWithFaceBook(string token); string LoginWithRenRen(string token); string LoginWithBaidu(string token); string LoginWithDropbox(string token); string LoginWithGithub(string token); //这里省略10000字 string LoginWithLinkedin(string token); }
这下看起来好多了, 可是IThirdpartyAuthenticationProvider 代码还不少,还很丑陋,有没有办法再进一步重构呢? 答案是确定。 第二步 咱们能够将第三方登陆的接口中的LogigWithxxx方法提到一个单独的接口中,其余具体站点的接口再继承这个接口,代码以下:
public interface IThirdpartyAuthenticationProvider { string Login(string token); } public interface IQQAuthenticationProvider:IThirdpartyAuthenticationProvider{} public interface IWeiboAuthenticationProvider:IThirdpartyAuthenticationProvider{} public interface IWeiXinAuthenticationProvider:IThirdpartyAuthenticationProvider{} public interface IAlipayAuthenticationProvider:IThirdpartyAuthenticationProvider{} public interface ITwitterAuthenticationProvider:IThirdpartyAuthenticationProvider{} public interface IFaceBookAuthenticationProvider:IThirdpartyAuthenticationProvider{} public interface IRenRenAuthenticationProvider:IThirdpartyAuthenticationProvider{} public interface IBaiduAuthenticationProvider:IThirdpartyAuthenticationProvider{} public interface IDropboxAuthenticationProvider : IThirdpartyAuthenticationProvider { } public interface IGitHubAuthenticationProvider:IThirdpartyAuthenticationProvider{} //这里省略10000字 public interface ILinkedinAuthenticationProvider : IThirdpartyAuthenticationProvider { }
这这下就好多了。 咱们分析一下重构后的代码有什么好处:
1. 接口的职责更单一了,调用目标更清晰了,每个接口就专门作一件事情。符合SRP了。
2. 在操做数据库的时候不会在IDatabase接口中调到其它的第三方API调用和第三方登陆认证相关的方法,每个几口更专一了。符合ISP了。
3.在添加新的第三方登陆的时候不须要在修改原来的实现 类了,核心业务逻辑只须要加一个接口和接口的实现类就能够了。符合OCP了。
4. 提高了代码的稳定性,可维护性和可扩展性。
固然任何事情都具备两面性,若是将一件好事作到极端有可能就会走向反面, 比方说定义一个User实体的接口:
public interface IIdProperty { int Id { get; set; } } public interface IFirstNameProperty { string FirstName { get; set; } } public interface ILastNameProperty { string LastName { get; set; } } public interface IAgeProperty { int Age { get; set; } } public interface IBirthdayProperty { DateTime Birthday { get; set; } } public class User:IIdProperty,IFirstNameProperty,ILastNameProperty,IAgeProperty,IBirthdayProperty { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public int Age { get; set; } public DateTime Birthday { get; set; } }
把每一个属性都定义成一个接口那就不可取了,也是没有意义的,反而给维护或扩展带来没必要要的麻烦,这就是使用接口时要注意的地方:
在使用接口时要注意控制接口的粒度,接口定义的粒度不能太细,也不能太粗。 接口粒度太细,系统中就会出现接口泛滥,接口和实现类急剧膨胀,反而不易维护;接口粒度太粗,就会违背ISP,系统的灵活性就会下降,不易维护和扩展。
关联阅读: