深入理解LSP:里氏替换原则

2023-12-12 17:33:36 浏览数 (1)

solid之 单一职责原则

单一职责在《敏捷软件开发》中的定义是:一个模块应该有且仅有一个变化的原因。

程序最稳定的状态就是不改变或很少发生改变。模块如果经常发生变化,意味着 这个模块没有很好的分离关注点,做了很多不是自己的事情;单一职责让模块仅有一个变化的原因 也就是只让他负责他关注的事情,不是他关注的事情不负责,

这样模块发生变化的原因就只有 一个了:关注负责的事情发生了改变

solid之 开闭原则

对扩展开放,对修改关闭。说人话就是 新增功能的时候 不会修改旧的代码,只会新增 新的代码。面向对象软件构造里面甚至提出了更严格的软件设计原则:不修改代码。

为什么不能修改呢?因为修改旧代码就意味着引入新问题,在原有的基础上修改代码会对程序造成破坏。相反扩展添加新的代码不会对原有的程序造成破坏。

扩展点的设计就在于分离关注点并用面向接口将 核心的流程抽取出来,以后新增新的功能时只需要新增一个接口的实体实现不同的逻辑即可。

如何使用:在代码中找到经常发生改变的文件,可能是违反了单一原则负责了不关心的东西,也可能是 违反了开闭原则,没有很好的找出程序中共性的行为,导致出现很多无用的重复代码。应该去 抽取找到程序中不变的部分。尽可能不修改而是通过扩展来增加新功能。面向接口抽取共通的可以复用的接口。

推荐书籍《unix编程的艺术》

solid之lsp:里氏替换原则

在设计继承关系的时候应该保证子类可以完全替换父类,忽略类型做特殊处理。如果不能完全替换意味着行为不一样也就代表着程序会出错(本来需要关心类型做不同处理但是代码中没有区分导致程序出错的情况。因此不能根据类型来做不同的处理)

  • 替换原则的定义

如果声明类型是父类a,但是不同的子类(b,c)的行为不一样,也就是说不同的子类替换父类后,程序会表现出不同的结果。(用b替换a后结果是e,但是用c替换a后结果确是d)。

  • 懂了好像又没懂?

最典型的关于lsp的设计题是 长方形和正方形这两个类的设计,凭借经验 正方形是特殊的长方形,因此正方形可以继承长方形。但是长方形和正方形计算面积的方式是不一样的,如果正方形修改长宽后变得不一致那么结果就会出错。正方形的面积是平方,而长方形是长和高的乘积也就是说 计算面积的行为不一样导致了正方形不能替代长方形。

在设计继承时要 时刻提醒自己 用父类的角度去设计子类,保证子类可以完全替换父类忽视类型,一般常见的情况是代码中出现了运行时 识别类型(instance of)而针对不同类型调用不同的方法。如果你需要在编写程序的时候需要知道他的具体类型才能继续编程那么就要注意是否违反了里式替换原则,不能完全替代父类。

  • 必须要关心类型该怎么设计?

而有些时候程序中必须要根据类型来做不同处理,但是如果你确定你的设计已经很好的分离了关注点且可以保证子类可以替代父类 然后还是需要判断类型采取不同的处理方式的话。可以采取另外一个思路:

  1. 里式替换强调的是行为可以替换,如果不同类型需要调用不同的行为,那么就会出现问题(比如handler代表处理,notifaction处理逻辑调用的是sendnotifyction;push处理逻辑调用的是push方法,这两个类型不一样采取的行为也不一样,push不能够完全替换notifaction因为他们调用的行为不同)
  2. 首先就是要抽取出共同对部分来,保证子类可以替换父类的前提就是 子类都有相同的行为因此抽取到父类中
  3. 在handler的例子里面 应该把oush和sendnotifaction抽成一个hanlde方法放在hanlder中然后不同的子类实现这个hanlde方法决定怎样实现。
  4. 把 模式匹配 转换成 map的好处是 代码里面去除了类型判断的工作,而类型判断代表着需要关心类型 从而 引发其他程序员想要在判断类型的时候做一些特殊的处理,而这特殊的处理就会破坏替换原则。因此为了不给自己留下这种后患,同时也让代码表现力更强,因此模式匹配的代码全部换成map去做。
  5. 代码中如何规避违反替换原则?

碰到类型判断模式匹配的情况下,将类型判断放到map中,避免做之后的类型特殊处理

声明类型使用父类进行声明

确保父类的行为子类都有

我正在参与2023腾讯技术创作特训营第四期有奖征文,快来和我瓜分大奖!

0 人点赞