繁多职责准则(SRP)
凋谢关闭准则(OCP)
里氏替换准则(LSP)
接口隔离准则(ISP)
依赖倒置准则(DIP)
SOLID 是面向对象设计 5 大重要准则的首字母缩写,当咱们设计类和模块时,恪守 SOLID 准则能够让软件更加强壮和稳固。那么,什么是 SOLID 准则呢?本篇文章我将谈谈 SOLID 准则在软件开发中的具体应用。
繁多职责准则(SRP)
繁多职责准则(SRP)表明一个类有且只有一个职责。一个类就像容器一样,它能增加任意数量的属性、办法等。然而,如果你试图让一个类实现太多,很快这个类就会变得轻便。任意小的扭转都将导致这个单一类的变动。当你改了这个类,你将须要从新测试一遍。如果你恪守 SRP,你的类将变得简洁和灵便。每一个类将负责繁多的问题、工作或者它关注的点,这种形式你只须要扭转相应的类,只有这个类须要再次测试。SRP 外围是把整个问题分为小局部,并且每个小局部都将通过一个独自的类负责。
假如你在构建一个应用程序,其中有个模块是依据条件搜寻顾客并以 Excel 模式导出。随着业务的倒退,搜寻条件会一直减少,导出数据的分类也会一直减少。如果此时将搜寻与数据导出性能放在同一个类中,势必会变的轻便起来,即便是渺小的改变,也可能影响其余性能。所以依据繁多职责准则,一个类只有一个职责,故创立两个独自的类,别离解决搜寻以及导出数据。
凋谢关闭准则(OCP)
凋谢关闭准则(OCP)指出,一个类应该对扩大凋谢,对批改敞开。这象征一旦你创立了一个类并且应用程序的其余局部开始应用它,你不应该批改它。为什么呢?因为如果你扭转它,很可能你的扭转会引发零碎的解体。如果你须要一些额定性能,你应该扩大这个类而不是批改它。应用这种形式,现有零碎不会看到任何新变动的影响。同时,你只须要测试新创建的类。
假如你当初正在开发一个 Web 应用程序,包含一个在线征税计算器。用户能够拜访 Web 页面, 指定他们的支出和费用的细节, 并应用一些数学公式来计算应纳税额。思考到这一点,你创立了如下类:
public class TaxCalculator
{
public decimal Calculate(decimal income, decimal deduction, string country)
{
decimal taxAmount = 0;
decimal taxableIncome = income - deduction;
switch (country)
{
case "India":
//Todo calculation
break;
case "USA":
//Todo calculation
break;
case "UK":
//Todocalculation
break;
}
return taxAmount;
}
}
这个办法非常简单,通过指定支出和收入,能够动静切换不同的国家计算不同的征税额。但这里隐含了一个问题,它只思考了 3 个国家。当这个 Web 利用变得越来越风行时,越来越多的国家将被加进来,你不得不去批改 Calculate 办法。这违反了凋谢关闭准则,有可能你的批改会导致系统其余模块的解体。
让咱们对这个性能进行重构,以合乎对扩大是凋谢,对批改是关闭的。
依据类图,能够看到通过继承实现横向的扩大,并且不会引发对其余不相干类的批改。这时 TaxCalculator 类中的 Calculate 办法会异样简略:
public decimal Calculate(CountryTaxCalculator obj)
{
decimal taxAmount = 0;
taxAmount = obj.CalculateTaxAmount();
return taxAmount;
}
里氏替换准则(LSP)
里氏替换准则指出,派生的子类应该是可替换基类的,也就是说任何基类能够呈现的中央,子类肯定能够呈现。值得注意的是,当你通过继承实现多态行为时,如果派生类没有恪守 LSP,可能会让零碎引发异样。所以请审慎应用继承,只有确定是“is-a”的关系时才应用继承。
假如你在开发一个大的门户网站,并提供很多定制的性能给终端用户,依据用户的级别,零碎提供了不同级别的设定。思考到这个需要,设计如下类图:
能够看到,ISettings 接口有 GlobalSettings、SectionSettings 以及 UserSettings 三个不同的实现。GlobalSettings 设置会影响整个应用程序, 例如题目、主题等。SectionSettings 实用于门户的各个局部, 如新闻、天气、体育等设置。UserSettings 为特定登录用户设置, 如电子邮件和告诉偏好。
这样的设计没问题,但如果有另一个需要,零碎须要反对游客拜访,惟一区别是游客不支持系统的设定,为了满足这个需要,你可能会如下设计:
public class GuestSettings : ISettings
{
public void GetSettings()
{
//get settings from database
//include guest name、ip address...
}
public void SetSettings()
{
//guests are not allowed set settings
throw new NotImplementedException();}
}
这样没问题吗?精确来说,零碎存在隐患。当独自应用 GuestSettings 时,因为咱们理解游客不能设置,所以咱们潜意识并不会被动调用 SetSettings 办法。然而因为多态,ISettings 接口的实现能够被替换为 GuestSettings 对象,当调用 SetSettings 办法时,可能会引发零碎异样。
重构这个性能,拆分为两个不同的接口:IReadableSettings 和 IWritableSettings。子类依据需要实现所需的接口。
接口隔离准则(ISP)
接口隔离准则(ISP)表明类不应该被迫依赖他们不应用的办法,也就是说一个接口应该领有尽可能少的行为,它是精简的,也是繁多的。
假如你正在开发一个电子商务的网站, 须要有一个购物车和关联订单解决机制。你设计一个接口 IOrderProcessor, 它用蕴含一个验证信用卡是否无效的办法(ValidateCardInfo)以及收件人地址是否无效的办法(ValidateShippingAddress)。与此同时,创立一个 OnlineOrderProcessor 的类示意在线领取。
这十分好,你的网站也能失常工作。当初让咱们来思考另一种情景,假如在线信用卡领取不再无效,公司决定承受货到付款领取。
乍一看, 这个解决方案听起来很简略, 你能够创立一个 CashOnDeliveryProcessor 并实现 IOrderProcessor 接口。货到付款的购买形式不会波及任何信贷卡验证, 所以,CashOnDeliveryOrderProcessor 类外部的 ValidateCardInfo 办法抛出 NotImplementedException。
这样的设计在将来可能会呈现的潜在问题。假如因为某种原因在线信用用卡付款须要额定的验证步骤。天然,IOrderProcessor 将被批改,它将包含那些额定的办法, 于此同时 OnlineOrderProcessor 将实现这些额定的办法。然而,CashOnDeliveryOrderProcessor 只管不须要任何的附加性能, 但你必须实现这些附加的性能。显然,这违反了接口隔离准则。
你须要将这个性能重构:
新的设计分成两个接口。IOrderProcessor 接口只蕴含两个办法:ValidateShippingAddress 和 ProcessOrder,而 ValidateCardInfo 形象到到一个独自的接口:IOnlineOrderProcessor。当初, 在线信用卡领取的任何扭转只局限于 IOnlineOrderProcessor 和它的子类实现,而 CashOnDeliveryOrderProcessor 是不会被影响。因而, 新设计合乎接口隔离准则。
依赖倒置准则(DIP)
依赖倒置准则(DIP)表明高层模块不应该依赖低层模块,相同,他们应该依赖抽象类或者接口。这意味着你不应该在高层模块中应用具体的低层模块。因为这样的话,高层模块变得紧耦合低层模块。如果今天,你扭转了低层模块,那么高层模块也会被批改。依据 DIP 准则,高层模块应该依赖形象(以抽象类或者接口的模式),低层模块也是如此。通过面向接口(抽象类)编程,紧耦合被移除。
那么什么是高层模块,什么是低层模块呢?通常状况下,咱们会在一个类(高层模块)的外部实例化它依赖的对象(低层模块),这样势必造成两者的紧耦合,任何依赖对象的扭转都将引起类的扭转。
依赖倒置准则表明高层模块、低层模块都依赖于形象,举个例子,你当初正在开发一个告诉零碎,当用户扭转明码时,邮件告诉用户。
public class UserManager
{
public void ChangePassword(string username,string oldpwd,string newpwd)
{EmailNotifier notifier = new EmailNotifier();
//add some logic and change password
//Notify the user
notifier.Notify("Password was changed on"+DateTime.Now);
}
}
这样的实现在性能上没有问题,但试想一下,新的需要心愿通过 SNS 模式告诉用户,那么咱们只能手动将 EmaiNorifier 替换为 SNSNotifier。在这儿,UserManager 就是高层模块,而 EmailNotifier 就是低层模块,他们彼此耦合。咱们心愿解耦,依赖于形象 INotifier,也就是面向接口的编程。