单一职责原则(Single Responsibility Principle,SRP)
SRP认为:一个类应该仅有一个被修改的理由。换句话说,每个类都应该只承担一种职责。(函数的设计也应该符合单一职责原则)
违反SRP的坏处:
修改一个类的某一个职责,可能导致这个类的另一个职责被破坏。
单个类承担的职责越多,就意味着这个类越复杂,越难维护。
违反SRP的类也很难被复用。
开放-关闭原则(Open-Closed Principle,OCP)
OCP原则认为:类应该对扩展开放,对修改封闭。换句话说,你可以在不修改某个类的前提下,扩展它的行为。
例子:Python里的sorted函数,通过传入lambda表达式作为key参数来修改默认的排序策略。以达到不修改函数(对修改关闭),但能扩展函数功能的目标(对扩展开放)。
实现OCP的途径:
继承:在需求发生变化时,用一种新增子类而不是修改原有类的方式来扩展程序的行为。而要做到有效地扩展,关键点在于先找到父类中不稳定、会变动的内容。只有将这部分变化封装成方法(或属性),子类才能通过继承重写这部分行为。
组合(基于组合思想的依赖注入):在创建对象时,将业务逻辑中易变的部分(常被称为“算法”)通过初始化参数注入对象里,最终利用多态特性达到“不改代码来扩展类”的效果。
数据驱动:将经常变动的部分以数据的方式抽离出来,当需求变化时,只改动数据,代码逻辑可以保持不动。听上去数据驱动和依赖注入有点儿像,它们都是把变化的东西抽离到类外部。二者的不同点在于:依赖注入抽离的通常是类,而数据驱动抽离的是纯粹的数据。
里式替换原则(Liskov Substitution Principle,LSP)
LSP原则认为:给定一个属于类型T的对象x,假如q(x)成立,那么对于T的子类型S来说,S类型的任意对象y也都能让q(y)成立。即:所有子类(派生类)对象应该可以任意替代父类(基类)对象使用,且不会破坏程序原本的功能。
常见的违反LSP原则的例子:
方法抛出异常违反LSP:子类抛出了父类所不认识的异常类型
方法返回值违反LSP:子类的方法返回值类型与父类不同,并且该类型不是父类返回值类型的子类。LSP要求子类方法的返回值类型与父类完全一致,或者返回父类结果类型的子类对象。
方法参数违反LSP:子类的方法参数与父类不同,并且参数要求没有变得更宽松(可选参数)、同名参数没有更抽象。LSP要求,类方法的参数必须与父类完全保持一致,或者,子类方法所接收的参数应该比父类更为抽象,要求更为宽松。
依赖倒置原则(Dependency Inversion Principle,DIP)
DIP原则认为:高层模块不应该依赖低层模块,二者都应该依赖抽象(抽象即为Go里面的接口,Python里面的抽象类)。
增加一层抽象的好处显而易见:它解耦了模块间的依赖关系,让代码变得更灵活。但抽象同时也带来了额外的编码与理解成本。所以,了解何时不抽象与何时抽象同样重要。只有对代码中那些容易变化的东西进行抽象,才能获得最大的收益。
接口隔离原则(Interface Segregation Principle,ISP)
ISP对如何使用接口提出了要求:客户(client)不应该依赖任何它不使用的方法。(客户即接口的使用方,也就是依赖接口的高层模块)。ISP要求一个接口所提供的方法应该刚好满足使用方的需求,一个不多,一个不少。在设计接口时应该让客户(调用方)来驱动协议设计。
合成复用原则(Composite Reuse Principle,CRP)
CRP原则认为:应尽量将已有的对象组合起来,通过组合来达到复用代码的目的,而不是通过继承已有的对象来实现复用。
例子:collections模块中的deque类,deque类是Python的双端队列实现,它使用了对象组合来实现代码复用。deque类内部实现了一个双向链表,每个节点都是一个独立的对象,并且可以自由地添加、删除和替换。
迪米特法则(Law of Demeter,LoD)
LoD法则认为:应尽量降低类与类之间的耦合度,一个对象应该对其他对象有尽可能少的了解,不和“陌生人”说话。