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,也就是面向接口的编程。

小结###

本篇博客为大家介绍了面向对象设计的 SOLID 原则,并以具体的案例辅助讲解。你可以看到,继承和多态在SOLID 原则中扮演了非常重要的角色。我们的应用程序不能过度设计,当然也不能随意设计。了解基本的 SOLID 原则能让你的应用程序变得健壮。你可以在Github 上查看具体的示例代码:https://github.com/MEyes/SOLID.Principles

浅谈 SOLID 原则的具体使用的更多相关文章

  1. 【译】浅谈SOLID原则

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

  2. 浅谈 SOLID 原则

    单一职责原则(SRP) 单一职责原则(SRP)表明一个类有且只有一个职责.一个类就像容器一样,它能添加任意数量的属性.方法等.然而,如果你试图让一个类实现太多,很快这个类就会变得笨重.任意小的改变都将 ...

  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. 需要了解的 Linux 网络和监控命令

    列出来的10个基础的每个linux用户都应该知道的网络和监控命令.网络和监控命令类似于这些: hostname, ping, ifconfig, iwconfig, netstat, nslookup ...

  2. mysql在ubuntu下的安装

    如果是调用的apt-get 那么应该是sudo apt-get install mysql-server-core-5.6  mysql-server-5.6 mysql-common-5.6 mys ...

  3. Mac 下安装ruby,以及CocoaPods安装以及使用网摘

    ruby安装网址:https://ruby-china.org/wiki/install_ruby_guide CocoaPods安装和使用教程地址:http://code4app.com/artic ...

  4. Kiwi iOS驱动测试开发

    Kiwi是iOS开发一个行为驱动开发库.目标是提供一个BDD库,精美的简单安装和使用. 安装 使用CocoaPods安装: pod 'Kiwi'如果使用Xcode5则使用: pod 'Kiwi/XCT ...

  5. javascript code snippet -- Forwarding Mouse Events Through Layers

    Anyone who has worked with web apps has likely created a masking element at some point, and the grea ...

  6. spring4+hibernate4+maven环境搭建

    本文主要介绍利用maven搭建spring4+hibernate4开发环境. 首先我们创建一个maven项目,具体步骤就不详细介绍了,看看我们pom.xml文件 <project xmlns=& ...

  7. linux性能检测工具

  8. Websocket Component

    As of Camel 2.10, the Websocket component supports SSL/TLS configuration through the Camel JSSE Conf ...

  9. Python中的random模块,来自于Capricorn的实验室

    Python中的random模块用于生成随机数.下面介绍一下random模块中最常用的几个函数. random.random random.random()用于生成一个0到1的随机符点数: 0 < ...

  10. 关于MFC文本框输入内容的获取 与 设置文本框的内容

    八月要开始做界面了<( ̄︶ ̄)/,然而目前只会用MFC╮(╯▽╰)╭ 好吧,言归正传,设置好文本框后,要获取用户输入的内容,可以用: GetDlgItemText() ; 这个函数有两个参数,第 ...