单一职责原则(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,也就是面向接口的编程。

浅谈 SOLID 原则的更多相关文章

  1. 浅谈 SOLID 原则的具体使用

    SOLID 是面向对象设计5大重要原则的首字母缩写,当我们设计类和模块时,遵守 SOLID 原则可以让软件更加健壮和稳定.那么,什么是 SOLID 原则呢?本篇文章我将谈谈 SOLID 原则在软件开发 ...

  2. 【译】浅谈SOLID原则

    SOLID原则是一种编码的标准,为了避免不良设计,所有的软件开发人员都应该清楚这些原则.SOLID原则是由Robert C Martin推广并被广泛引用于面向对象编程中.正确使用这些规范将提升你的代码 ...

  3. 在net中json序列化与反序列化 面向对象六大原则 (第一篇) 一步一步带你了解linq to Object 10分钟浅谈泛型协变与逆变

    在net中json序列化与反序列化   准备好饮料,我们一起来玩玩JSON,什么是Json:一种数据表示形式,JSON:JavaScript Object Notation对象表示法 Json语法规则 ...

  4. 浅谈SQL注入风险 - 一个Login拿下Server

    前两天,带着学生们学习了简单的ASP.NET MVC,通过ADO.NET方式连接数据库,实现增删改查. 可能有一部分学生提前预习过,在我写登录SQL的时候,他们鄙视我说:“老师你这SQL有注入,随便都 ...

  5. 浅谈WebService的版本兼容性设计

    在现在大型的项目或者软件开发中,一般都会有很多种终端, PC端比如Winform.WebForm,移动端,比如各种Native客户端(iOS, Android, WP),Html5等,我们要满足以上所 ...

  6. Spring缓存框架原理浅谈

    运维在上线,无聊写博客.最近看了下Spring的缓存框架,这里写一下 1.Spring 缓存框架 原理浅谈 2.Spring 缓存框架 注解使用说明 3.Spring 缓存配置 + Ehcache(默 ...

  7. Android应用安全开发之浅谈加密算法的坑

      <Android应用安全开发之浅谈加密算法的坑> 作者:阿里移动安全@伊樵,@舟海 阿里聚安全,一站式解决应用开发安全问题     Android开发中,难免会遇到需要加解密一些数据内 ...

  8. Android性能优化的浅谈

    一.概要: 本文主要以Android的渲染机制.UI优化.多线程的处理.缓存处理.电量优化以及代码规范等几方面来简述Android的性能优化 二.渲染机制的优化: 大多数用户感知到的卡顿等性能问题的最 ...

  9. 转:浅谈CSS在前端优化中一些值得注意的关键点

    前端优化工作中要考虑的元素多种多样,而合理地使用CSS脚本可以在很大程度上优化页面的加载性能,以下我们就来浅谈CSS在前端优化中一些值得注意的关键点: 当谈到Web的“高性能”时,很多人想到的是页面加 ...

随机推荐

  1. Azure MFA 守护你的账户安全

    一,引言 MFA 又名 "多因素身份认证",指用户在登录的时候提示输入其他形式的标识.如果只使用密码对用户进行身份验证,是特别不安全的,尤其是在密码泄露的情况下.为了提高安全性,启 ...

  2. 控制器view生命周期

    控制器View的生命周期方法:只要是控制器的生命周期方法,都是以view开头. 控制器View加载完成时调用- (void)viewDidLoad { [super viewDidLoad];} 控制 ...

  3. memcache启动多个服务

    windows 7 64bit 环境下安装memcached 1.下载后解压到D:\memcached(下载地址:memcached-win64下载地址) 2.安装到windows服务,打开cmd命令 ...

  4. WebLogic11g- 集群化

    其实网上关于weblogic集群的资料非常多[大部分都是从创建新的domain开始,我这篇先介绍怎么样把原本普通的domain改造为集群环境],如果觉得不够,可以啃weblogic的官方文档.下面给几 ...

  5. Docker创建私有镜像仓库

    Docker官方提供了一个工具docker-registry,可以借助这个工具构建私有镜像仓库: 1.拉取registry镜像 # docker pull registry//可以使用 docker ...

  6. Lesson10——NumPy 迭代数组

    NumPy 教程目录 NumPy 迭代数组 NumPy 迭代器对象  numpy.nditer  提供了一种灵活访问一个或者多个数组元素的方式. 迭代器最基本的任务的可以完成对数组元素的访问. Exa ...

  7. Azure AD Domain Service(二)为域服务中的机器配置 Azure File Share 磁盘共享

    一,引言 Azure File Share 是支持两种认证方式的! 1)Active Directory 2)Storage account key 记得上次分析的 "Azure File ...

  8. sublime与python交互

    点击菜单栏中的工具 -> 编译系统,勾选Python即可 创建hello.py文件,Ctrl+S保存文件,Ctrl+B执行文件,结果如下图   3.sublime运行python文件的交互环境设 ...

  9. Android 连接蓝牙扫码器 无输入框

    Android 的APP 需要集成一个蓝牙扫码器, 特别的是,需要扫码的地方是没有输入框的(EditText),不能通过直觉上理解的通过对EditText输入事件进行监听处理,取得扫码结果.并且设备也 ...

  10. 通俗理解.NET 6 Minimal APIs

    .NET 6,微软称为"最快的.NET",带有了许多令人兴奋的新功能.语言和性能改进.这是自 .NET Core 3.1 以来的第一个 LTS 版本,将支持三年. 本次大版本发布, ...