接口隔离原则:定义较小且职责单一的接口,提高复用性和针对性

2024-11-21
来源:网络整理

接口隔离原则(ISP):使用多个专用接口而不是单个总体接口,即客户端不应该依赖自己不需要的接口。

从接口隔离原理的定义可以看出,它似乎与SRP有很多相似之处。是的,事实上,ISP和SRP都强调职责的统一。接口隔离原则告诉我们,定义接口时,应该根据职责定义“较小”的接口,不要定义“高端”的接口。也就是说,接口应该尽可能单一职责,这样更容易复用,暴露给客户端的方法也更“有针对性”。比如定义一个接口包括一堆访问数据库的方法,有的还包括一堆访问网络的方法。还包括一些权限认证的方法。将这么一堆不相关的方法封装到一个接口中显然是不合适的。如果客户端程序只想使用一些数据访问的功能,但在调用接口时,需要对网络访问方式和权限进行认证。将方法暴露给客户端,让客户端程序感觉“混乱”,那么这个接口就不是ISP的,显然构成了接口污染。

注:这里所说的接口是广义的接口。它是为程序交互提供的一组契约和一组约定。它不是由各种语言关键字定义的方法的集合。但这里所说的接口可以使用各种语言的关键字来定义,当然也可以使用抽象类、类等来定义。

假设客户提出了一个软件系统的需求:

1、用户可以使用第三方QQ、微信、微博登录系统。

2、系统包括人事管理和人事管理。

3. 访问第三方API获取一些数据。

好,得到这个需求后,我们首先进行分析,简单的原型设计,数据库设计,然后开始写代码。通常第一步是定义接口。很快接口定义如下:

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); }

这看起来不错。接口已定义。编写一个具体的类来继承这个接口并实现所有方法。现在就可以实现业务并编写接口了。等了几天,客户说帮我添加支付宝登录。然后添加支付宝登录接口。代码现在看起来像这样:

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); }

只需在实现类中实现该方法即可。

随着时间的推移,有一天客户要求我再添加一个百度登录。好吧,我已经有了例程,只需添加一个即可。有什么大不了的?时间还剩。 。 。客户说要添加登录。 。 。添加一个。 。 。 ,无穷无尽,现在界面变成了这样:

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); }

有一天我什至不想看这个界面。方法实在是太多了,更何况实现类中的代码足有七八千行。

所以我决定重构。现在回想起来,这个界面早就应该被重构了。它甚至应该在第一次定义时被分割。我不知道如何命名接口(通常在编写代码时,类、接口和方法的名称是(当你不知道如何命名时,就该重构了)它有一个如此奇怪的名称。这个设计显然很糟糕,它几乎违反了我们谈到的所有设计原则。

来吧,让我们重构一下。经过分析,第一步将界面按照功能分为三个“小”界面:

1.将数据库操作相关信息提取到接口()中。

2、将第三方API调用相关的方法提取到接口中(呃)。

3、第三方登录相关方法被抽取到一个接口()中。

代码现在看起来像这样:

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); }

现在看起来好多了,但是代码仍然很多而且非常丑陋。有什么办法可以进一步重构吗?答案是肯定的。第二步,我们可以将第三方登录接口中的方法提到一个单独的接口中,其他特定站点的接口可以继承这个接口。代码如下:

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、接口的职责更加单一,调用目标更加清晰,每个接口专门做一件事。遵守建议零售价。

2、操作数据库时,界面中不会调用其他第三方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,系统的灵活性会降低,难以维护和扩展。

相关阅读:

【面向对象设计原则】【面向对象设计原则】单一职责原则(SRP)【面向对象设计原则】开闭原则(OCP)【面向对象设计原则】里氏替换原则( LSP)【面向对象设计原则】依赖倒置原则(DIP)

分享