前言
最近学习 rust 文档的 subtyping-and-variance 时异常艰辛。个人感觉是因为之前一直在写前端,所以对面向对象的设计原则练习不够,最终导致学 rust 时的艰辛。这里简单记录一下学习这个章节时的学习笔记和个人总结。
SOLID 原则
记得大学学习编程时老师曾说,只有跨过面向对象的坎,才能算是入了编程的门。而我倒觉得对范式的理解并不是「跨过门槛」这么短时性的东西。不同编码水平的程序员对范式的理解是不同的,随着编码时长的增长,程序员对范式的理解会逐步增加,最终又会反过来提升编码水平。所以范式的学习应该是个 递进 的过程。编程有很多种范式,其中面向对象(object-oriented programming)是经典中的经典。
在我的理解里,范式本身是对前人编程经验的提炼及汇总。程序员们通过不断的使用面向对象范式,发现了一些有助于设计稳定、可维护和灵活的软件的准则,这就是大名鼎鼎的 SOLID 原则。其实就是几个原则的首字母缩写:
单一职责原则(Single Responsibility Principle,SRP): 一个类应该只有一个引起它变化的原因。这个原则强调每个类应该只负责单一的职责,以减少类的复杂性,增强可维护性。
开闭原则(Open/Closed Principle,OCP): 软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。这个原则鼓励设计能够通过添加新功能来扩展,而不是修改现有代码。
里氏替换原则(Liskov Substitution Principle,LSP): 子类应该能够替代基类,而不会影响程序的正确性。子类在继承基类时应该保持基类的行为和契约,以确保代码的稳定性。
接口隔离原则(Interface Segregation Principle,ISP): 客户端不应该被迫依赖它不使用的接口。这个原则鼓励将大型接口分解为更小的、更专注的接口,以避免不必要的依赖和复杂性。
依赖倒置原则(Dependency Inversion Principle,DIP): 高层模块不应该依赖于低层模块,二者都应该依赖于抽象。这个原则强调使用抽象接口或抽象类来进行解耦,以提高代码的可维护性和灵活性。
里氏替换原则
开头提到的章节学习中,rust 的子类型化和变异性实际上有用到里氏替换原则,所以这里只对其进行进一步阐述,其余原则以后有时间再说。
里氏替换原则由计算机科学家 Barbara Liskov 在 1987 年在一次会议上名为 (《数据的抽象与层次》)[https://dl.acm.org/doi/pdf/10.1145/62139.62141] 的演说中首先提出,并在 1994 年发表论文 (《A Behavioral Notion of Subtyping》)[https://www.cs.cmu.edu/~wing/publications/LiskovWing94.pdf] 确定。该原则强调子类对象应该能够替换其基类(父类)对象,而不会影响程序的正确性。换句话说,子类应该是基类的有效扩展,而不是修改或破坏基类的行为。如果 S 是 T 的子类,那么在不破坏程序的正确性的前提下,任何针对 T 的程序都应该能够透明地使用 S 的对象。在遵循 LSP 的情况下,代码中使用基类对象的地方,在需要时将其替换为子类对象,代码不会出错。
LSP 可以从下面的观点推导出来:
Subtyping(子类型化)关系: 继承是一种子类型化的关系。子类应该可以被当作父类来对待,因为它们共享了父类的行为和特征。这使得基类对象的代码可以在不知道具体子类的情况下正常工作。
Liskov Substitution(里氏替换): 如果子类可以替代基类,那么基类对象的行为应该在不知道子类类型的情况下保持一致。子类不应该引入新的、与基类不一致的行为。
Behavioral Preservation(行为保持): 子类应该保持基类的行为和契约。这意味着子类的方法应该遵循与基类相同的前置条件、后置条件和异常处理。
Expectation(期望): 在使用基类对象的代码中,我们对于对象的行为有一定的期望。子类对象应该能够满足这些期望,以确保代码的正确性和稳定性。
子类型化和变异性
在这里再贴一下原文:subtyping-and-variance
找到了一篇讲的更详细的文档:Subtyping and Variance