单一职责和里氏替换

前端之家收集整理的这篇文章主要介绍了单一职责和里氏替换前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

一、单一职责原则

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 继承 不会飞行的动物那个

猜你在找的设计模式相关文章