单一职责和里氏替换

一、单一职责原则

1.1 原则解读

原则定义:应该有且仅有一个原因引起类的变更,也可以说成是一个类只负责一件事情

该原则要求类的职责明确清晰,这样符合该原则的设计有如下好处:

  • 由于单个类只负责一件事情,职责清晰明确,类的复杂性降低
  • 单个类的复杂性降低,整体可读性提高
  • 可读性好,可维护性提高,由于类的职责明确清晰,没有产生不必要的耦合,每个类可以独立变化
  • 变更引起的风险降低,因为单一职责每个类负责一个职责,单个职责或接口变化不会引起其它职责类的变化

原则上,我们只要将不同职责打包到不同的类中去,即可满足单一职责原则。然而,满足该原则的难处在于如何划分职责?,如何划分职责只能在具体场景中进行具体划分,不同的需求下,划分可能是不同的。

1.2 例1

假设现在我们需要实现用户管理的功能,包括了用户的信息更改,增加机构,增加角色等等。为了维护用户的诸多信息,我们将这些写到一个接口当中,作为一个用户管理类。我们来看看类图:

这个接口设计的问题在于:用户的属性和用户的行为没有分开。违反了单一职责的原则。我们可以吧用户信息抽出来成为一个业务对象BO,把用户的行为抽取出来成为一个业务逻辑Biz。其中BO对象的职责就是收集和反馈用户的属性信息,而Biz对象的职责是负责用户的行为。此时类图如下所示:

这样把一个接口拆分成两个有什么好处呢?

  • 类的职责单一,结构简单
  • 容易复用
    假设有这么一种情况,现在要实现一个登录器,需要对用户输入的用户名和密码进行比对。这时可以直接复用IUserBO接口。

1.3 例2

单一职责的好处是不言而喻的,概念的定义也十分清晰明确。但是实现单一职责的首要前提是要会划分职责,而划分职责不是一项容易的工作。我们来看一个例子:

这是一个电话类。在电话通话的过程中,应该包含以下几个过程:

  • 拨号
  • 通话
  • 回应
  • 挂机

代码如下所示:

class IPhone{
public:
    void dial(String phoneNumber) = 0;
    void chat(Object o) = 0;
    void hangup() = 0;
}

这个类在直观上看非常合理,但是实际上已经违反了单一职责模式,因为该类负责了不止一个职责。它包含了两个职责:

  • 协议管理
  • 数据传送

协议管理包括的是dialhangup两个方法,而数据传送包含了chat方法。对于联通或者移动或者电信,在不同的协议下,拨号和挂断的协议都不同,而数据传送方式对于拨号和挂断没有什么关系,因为只要拨通电话号码之后,数据传输只和底层的数据传输协议有关系。
为什么我们说这是两个不同的职责呢?我们是如何划分的呢?首先我们考虑两个问题:

  • 这两个职责会引起类的变化
  • 这两个职责可以独立变化而不互相影响

既然变化互不影响,也就是说这两组接口是相互独立的,所以我们可以考虑拆分成两个不同的类。现在拆分后的类图如下:

二、里氏替换原则

2.1 原则解读

原则定义:所有引用父类的地方,必须能透明地使用其子类对象

就是子类必须能够完全替代 父类,否则就是不合理的继承关系。
换就话说,就是父类的方法 是子类全部需要的,如果不是全部需要的,你的继承关系就存在问题。例如,父类(Anaimal)里有两个方法 Fly 和 Run. 但是 子类 Dog 只需要Run方法,而不需要Fly这个方法。所以这个父类就有问题。因为这里的子类不能完全替代父类,正在引用Anaimal->Fly的地方不能使用Dog->Fly去替代,考虑以下代码

void doSth(Anaimal* A){
   ...
   A->Fly;
   ...
}

int main(){
    Dog *dog = new Dog;
    doSth(dog);//错误,dog没有实现Fly
    return 0;
}

这时候需要进一步优化,脱离继承关系,变成 飞行类动物和不会飞行的动物两个类,这两个类都继承动物类。并将 原动物类里的 方法 Fly 移动飞行类动物里,Run方法 还留在动物类里,而Dog 继承 不会飞行的动物那个

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结