单一职责和里氏替换

来源:互联网 发布:景区电子数据采集方式 编辑:程序博客网 时间:2024/06/05 06:00

    • 一单一职责原则
      • 1 原则解读
      • 2 例1
      • 3 例2
    • 二里氏替换原则
      • 1 原则解读

一、单一职责原则

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

0 0
原创粉丝点击