写在前面
在程序设计领域, SOLID(单一功能、开闭原则、里氏替换、接口隔离以及依赖反转)是由罗伯特·C·马丁在21世纪早期引入,指代了面向对象编程和面向对象设计的五个基本原则。当这些原则被一起应用时,它们使得一个程序员开发一个容易进行软件维护和扩展的系统变得更加可能
1 单一职责原则(Single Responsibility Principle,SRP)
一个类应该只有一个职责。通过将职责抽象成不同的类或方法,我们可以更好地组织和管理代码。
所谓职责是指类变化的原因。如果一个类有多于一个的动机被改变,那么这个类就具有多于一个的职责。而单一职责原则就是指一个类或者模块应该有且只有一个改变的原因。
很多人认为这个原则是针对一个类进行描述,在我看来这里使用模块这个更加抽象的名词更为合适。在设计中,小到一个方法,大到一个模块我们都应该尽量去遵循单一职责原则。
相信在我们日常开发中,每个人都遇到过改了一个问题经常会引起另一个问题。其原因大多都是因为在设计上违背了单一职责原则。
如果一个类承担的职责过多,就等于把这些职责耦合在一起了。一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当发生变化时,设计会遭受到意想不到的破坏。而如果想要避免这种现象的发生,就要尽可能地遵守单一职责原则。
此原则的核心就是解耦和增强内聚性,是为了实现代码高内聚、低耦合,提高代码的复用性、可读性、可维护性。
2 开闭原则(Open-Closed Principle,OCP)
当我们在软件开发中添加新功能或修改现有功能时,我们希望这些更改不会破坏现有的代码。这就是开闭原则(Open-Closed Principle,OCP)的核心思想:软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。
换句话说,我们应该通过添加新的代码来扩展软件的功能,而不是修改现有的代码。这样做的好处是,我们可以保证现有的代码仍然能够正常工作,同时也可以避免引入新的错误
遵循开闭原则的好处是,我们可以编写具有可扩展性和可维护性的代码,从而提高软件开发的效率和质量。如果我们的代码不具备这些特性,那么在软件开发过程中就会遇到很多问题,例如:
- 当我们需要添加新的功能时,我们可能需要修改现有的代码,这可能会破坏现有的功能或引入新的错误。
- 当我们需要修改现有的代码时,我们可能会影响到其他部分的代码,这可能会导致其他功能出现问题。
- 当我们需要维护代码时,我们可能会发现代码难以理解或难以修改,这可能会导致维护成本增加。
遵循开闭原则是非常重要的,它可以帮助我们编写具有可扩展性和可维护性的代码,从而提高软件开发的效率和质量。
3 里氏替换原则(Liskov Substitution Principle,LSP)
里氏替换原则(Liskov Substitution Principle,LSP)是 SOLID 原则中的第三个原则,它强调了子类应该能够替换其父类并且不会影响程序的正确性。
里氏替换原则强调的是设计和实现要依赖于抽象而非具体;子类只能去扩展基类,而不是隐藏或者覆盖基类,它包含以下4层含义
子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法,父类中凡是已经实现好的方法(相对于抽象方法而言),实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些契约,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏
4 接口隔离原则(Interface Segregation Principle,ISP)
接口隔离原则也叫Interface Segregation Principle、ISP,要求程序员尽量将臃肿庞大的接口拆分成更小的和更具体的接口,让接口中只包含客户感兴趣的方法。2002 年罗伯特·C·马丁给“接口隔离原则”的定义是:客户端不应该被迫依赖于它不使用的方法(Clients should not be forced to depend on methods they do not use)。
该原则还有另外一个定义:一个类对另一个类的依赖应该建立在最小的接口上(The dependency of one class to another one should depend on the smallest possible interface)。
以上两个定义的含义是:要为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。
感觉可能与单一职责原则很像,接口隔离原则和单一职责都是为了提高类的内聚性、降低它们之间的耦合性,体现了封装的思想,但两者是不同的:
- 单一职责原则注重的是职责,而接口隔离原则注重的是对接口依赖的隔离。单一职责原则主要是约束类,它针对的是程序中的实现和细节;
- 接口隔离原则主要约束接口,主要针对抽象和程序整体框架的构建。
简单来说接口隔离原则与单一职责的定义的规则是不相同的,单一职责要求的是类和接口职责单一,注重的是职责,没有要求接口的方法减少,例如一个职责可能包含 10个方法,这 10个方法都放在一个接口中,并且提供给多个模块访问,各个模块按照规定的权限来访问,在系统外通过文档约束不使用的方法不要访问,按照单一职责原则是允许的,按照接口隔离原则是不允许的。
5 依赖倒置原则(Dependency Inversion Principle,DIP)
依赖倒置原则(Dependency Inversion Principle,简称DIP)是面向对象设计原则之一,它强调高层模块不应该依赖于低层模块,而是应该依赖于抽象。换句话说,依赖关系应该建立在抽象层次上,而不是具体实现层次上。这样可以降低模块间的耦合度,提高系统的可扩展性和可维护性。
依赖倒置原则主要包含以下两个方面:
- 高层模块不应该依赖于低层模块,两者都应该依赖于抽象。
- 抽象不应该依赖于具体实现,具体实现应该依赖于抽象。
为了更好地理解依赖倒置原则,我们可以通过以下几个步骤来应用它:
识别模块间的依赖关系:在设计系统时,我们需要识别出各个模块之间的依赖关系,以便确定哪些模块需要遵循依赖倒置原则。
- 定义抽象接口:为了实现依赖倒置原则,我们需要定义一个抽象接口,让高层模块和低层模块都依赖于这个接口。这样,高层模块就不再直接依赖于低层模块的具体实现,而是依赖于抽象接口。实现抽象接口:低层模块需要实现抽象接口,这样高层模块就可以通过抽象接口与低层模块进行交互。这种方式可以确保高层模块与低层模块之间的解耦,提高系统的可扩展性和可维护性。
依赖倒置原则是一种有效的设计原则,可以帮助我们构建更加灵活、可扩展和可维护的系统。通过将依赖关系建立在抽象层次上,我们可以降低模块间的耦合度,从而提高系统的整体质量。
23种设计模式简介
设计模式是一种常见的代码设计思想,它可以帮助我们解决常见的代码设计问题。通过使用抽象思维,我们可以更好地理解和应用设计模式,以便更好地组织和管理代码。
抽象思维在代码设计中非常重要。通过使用不同的代码设计思想和准则,我们可以将具体的事物抽象成一般性的概念或模型,以便更好地组织和管理代码。一堆抽象度很低的代码,很容易就会成为我们项目当中人人唾弃的对象(简称代码“屎山”)。
这里总结了23种常见的设计模式,在做系统设计时,可以根据需求,使用对应的设计模式
类别 | 设计模式 | 简要介绍 |
---|---|---|
创建型模式 | 单例模式 | 确保一个类只有一个实例,并提供一个全局访问点。 |
工厂方法模式 | 定义一个创建对象的接口,让子类决定实例化哪一个类。 | |
抽象工厂模式 | 提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。 | |
建造者模式 | 将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。 | |
原型模式 | 通过复制现有的实例来创建新的实例。 | |
结构型模式 | 适配器模式 | 将一个类的接口转换成客户期望的另一个接口。 |
桥接模式 | 将抽象部分与实现部分分离,使它们可以独立地变化。 | |
组合模式 | 将对象组合成树形结构以表示“部分-整体”的层次结构。 | |
装饰器模式 | 动态地给一个对象添加一些额外的职责。 | |
外观模式 | 为子系统中的一组接口提供一个统一的接口。 | |
享元模式 | 使用共享技术有效地支持大量细粒度的对象。 | |
代理模式 | 为其他对象提供一个代理以控制对这个对象的访问。 | |
行为型模式 | 责任链模式 | 为请求创建一个接收者对象的链。 |
命令模式 | 将一个请求封装为一个对象,从而使您可以用不同的请求对客户进行参数化。 | |
解释器模式 | 给定一个语言,定义它的文法的一种表示,并定义一个解释器。 | |
迭代器模式 | 提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。 | |
中介者模式 | 用一个中介对象来封装一系列的对象交互。 | |
备忘录模式 | 在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。 | |
观察者模式 | 当一个对象的状态发生改变时,其相关依赖对象会被自动更新。 | |
状态模式 | 允许一个对象在其内部状态改变时改变它的行为。 | |
策略模式 | 定义一系列算法,把它们一个个封装起来,并且使它们可互相替换。 | |
模板方法模式 | 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。 | |
访问者模式 | 在不改变数据结构的前提下,定义作用于某种数据结构中的各元素的新操作。 |
总结
原则 | 含义 |
---|---|
单一职责原则(Single Responsibility Principle,SRP) | 一个类应该只有一个职责。通过将职责抽象成不同的类或方法,我们可以更好地组织和管理代码。 |
开闭原则(Open-Closed Principle,OCP) | 一个类应该对扩展开放,对修改关闭。通过使用抽象类或接口来定义通用的方法或属性,我们可以更好地实现代码的扩展性。 |
里氏替换原则(Liskov Substitution Principle,LSP) | 子类应该能够替换父类。通过使用抽象类或接口来定义通用的方法或属性,我们可以更好地实现代码的可扩展性和可维护性。 |
接口隔离原则(Interface Segregation Principle,ISP) | 一个类不应该依赖于它不需要的接口。通过将接口抽象成不同的类或方法,我们可以更好地组织和管理代码。 |
依赖倒置原则(Dependency Inversion Principle,DIP) | 高层模块不应该依赖于低层模块,它们应该依赖于抽象。通过使用抽象类或接口来定义通用的方法或属性,我们可以更好地实现代码的可扩展性和可维护性。 |
参考
[1]鹅厂程序员的9个生存法则