设计模式是在软件设计中用于解决常见问题的经过验证的解决方案。设计模式并不是代码或库,而是一种解决问题的思考方式。在使用设计模式时,需要考虑一些基本的设计原则,这些原则有助于构建灵活、可维护和可扩展的软件系统。以下是一些常见的设计原则:
单一职责原则(Single Responsibility Principle - SRP):
它指导我们确保一个类只有一个责任。类的责任应该是单一的,即一个类应该只有一个引起它变化的原因。这有助于提高类的内聚性,使得类更加容易理解、修改和维护。
在上述例子中,第一个示例中的 Report 类违反了单一职责原则,因为它负责生成报告和保存报告到文件两个不同的责任。在第二个示例中,将这两个责任分别放在 Report 类和 FileSaver 类中,遵循了单一职责原则,使得每个类都更加简单和可维护。这样的设计有助于将系统的不同部分解耦,提高代码的灵活性和可扩展性。
一个类应该只有一个引起变化的原因。换句话说,一个类应该只有一个责任。
开放/封闭原则(Open/Closed Principle - OCP):
由勃兰特·梅耶(Bertrand Meyer)提出。该原则表明一个软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。简而言之,当需要添加新功能时,应该通过扩展而不是修改现有代码来实现。
具体来说,开放/封闭原则的核心思想是:
开放(Open):
软件实体应该可以在不修改它的源代码的情况下进行扩展。
新功能应该通过添加新代码来实现,而不是通过修改已有代码。
封闭(Closed):
已有的软件实体不应该被修改,因为修改可能引入新的错误或影响现有功能的稳定性。
这样的设计使得系统更加稳定,因为不需要修改现有代码,只需要添加新的代码。这也有助于降低代码的耦合性,提高代码的可维护性和可扩展性。
在遵循开放/封闭原则的例子中,通过引入一个 Shape 接口和不同的形状类(例如 Rectangle 和 Circle),可以轻松地添加新的形状而无需修改 AreaCalculator 类。这样,系统的扩展性得到了提高,同时保持了对现有代码的封闭性。
软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。这意味着在不修改现有代码的情况下,可以通过添加新的代码来扩展系统的功能。
里氏替换原则(Liskov Substitution Principle - LSP):
由计算机科学家巴巴拉·利斯科夫(Barbara Liskov)提出。该原则指导着子类型(派生类或子类)如何与基类型(基类或父类)进行替换,以确保程序的正确性和一致性。
如果对每一个类型为 S 的对象 o1,都有类型为 T 的对象 o2,使得以 T 定义的所有程序 P 在所有的对象 o1 都替换成 o2 时,程序 P 的行为没有发生变化,那么类型 S 是类型 T 的子类型。
换句话说,如果子类型可以替换父类型而不影响程序的正确性,那么这个子类型是符合里氏替换原则的。
在遵循里氏替换原则的例子中,Square类不再继承自Rectangle类,而是继承自一个通用的Shape类,确保子类型可以被替换而不引起意外的行为变化。通过这种方式,程序可以更灵活地使用不同的形状类型,而不必担心替换时可能引发的问题。
子类型必须能够替换其基类型而不改变程序的正确性。如果一个类是某个抽象类的子类,那么它应该能够替代该抽象类的任何地方,并且程序的行为不会改变。
依赖倒置原则(Dependency Inversion Principle - DIP):
由罗伯特·马丁(Robert C. Martin)提出。该原则主要有两个核心观点:
- 高层模块不应该依赖于低层模块,二者都应该依赖于抽象。
- 抽象不应该依赖于细节,细节应该依赖于抽象。
在设计系统时,应该通过依赖于抽象而不是具体实现来减少模块之间的耦合。高层模块和低层模块都应该依赖于通用的抽象,而不是彼此直接依赖。这有助于提高系统的灵活性、可维护性和可扩展性。
在遵循依赖倒置原则的例子中,Switch 类不再直接依赖于 LightBulb 类,而是依赖于通用的 Switchable 接口。这样,如果有其他类实现了 Switchable 接口,可以轻松地替换 LightBulb 类,而不影响 Switch 类的实现。这提高了系统的灵活性和可维护性。
高层模块不应该依赖于低层模块,二者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。这促使使用接口或抽象类来减少模块之间的耦合。
接口隔离原则(Interface Segregation Principle - ISP):
接口隔离原则(Interface Segregation Principle - ISP)是面向对象设计中的一个原则,它强调一个类不应该被强迫依赖于它不使用的接口。该原则的目标是防止一个类因为实现了不需要的接口而变得庞大臃肿,降低类的内聚性。
接口隔离原则可以通过将大接口拆分成更小的、更具体的接口来实现。具体来说,一个类只应该知道它需要使用的方法,而不需要了解其他不相关的方法。
在遵循接口隔离原则的例子中,将大接口拆分成 Workable 和 Eatable 两个小接口。这样,Robot 类只需实现 Workable 接口,而 Human 类则同时实现了 Workable 和 Eatable 接口。这避免了类实现不需要的方法,提高了系统的灵活性和可维护性。
不应该强迫客户端依赖于它们不使用的接口。一个类不应该被迫实现它用不到的接口。
合成/聚合复用原则(Composition/Aggregation Reuse Principle - CARP):
合成/聚合复用原则(Composition/Aggregation Reuse Principle - CARP)是面向对象设计中的一个原则,它强调在复用时优先使用组合(Composition)和聚合(Aggregation),而不是继承。该原则的核心思想是通过将现有的类组合在一起来创建新的类,而不是通过继承现有类。
合成/聚合复用原则的主要原则有两个:
优先使用合成(Composition):
通过将对象组合在一起来创建新的对象,而不是通过继承现有类。这样可以更灵活地构建对象的行为,而不会产生继承链的问题。
优先使用聚合(Aggregation):
聚合是一种特殊的合成关系,表示一种“整体-部分”的关系,但整体和部分之间的生命周期可以独立存在。这允许部分对象在没有整体对象的情况下存在。与合成一样,聚合也提供了更灵活的复用方式。
在遵循合成/聚合复用原则的例子中,Car 类通过组合引入了 Engine 类,而不是通过继承。这降低了类之间的耦合性,使得系统更加灵活,更容易进行复用和维护。使用合成和聚合的方式可以避免继承链的问题,并提高系统的灵活性。
首选使用合成/聚合,而不是继承。通过将现有类的实例组合到新的类中,而不是通过继承现有类来实现代码复用。
迪米特法则(Law of Demeter - LoD):
迪米特法则(Law of Demeter - LoD),也被称为最少知识原则,是面向对象设计中的一项原则。迪米特法则强调一个对象应该对其他对象有最少的了解,即一个类不应该直接与其他类过多地发生相互作用。
一个对象应该对其他对象保持最少的了解。只与你的直接朋友通信,而避免和陌生人通信。
这意味着一个类应该尽量减少对其他类的引用,尽量减少依赖关系,以降低类之间的耦合度。通过保持对象之间的关系简单,可以提高系统的灵活性和可维护性。
在遵循迪米特法则的例子中,Teacher 类只与 StudentProxy 类发生交互,而不直接与 Student 类发生交互。这样,Teacher 类不需要了解 Student 类的内部实现,通过 StudentProxy 类进行间接的交互。这降低了类之间的耦合度,符合迪米特法则的要求。
一个软件实体应当尽可能少地与其他实体发生相互作用。也被称为最少知识原则。
在面向对象设计中,设计原则是指导我们创建灵活、可维护、可扩展软件系统的重要指导方针。每个设计原则都强调特定的方面,例如单一职责原则、开放/封闭原则、里氏替换原则、依赖倒置原则、接口隔离原则和合成/聚合复用原则。这些原则共同构建了一个强大的设计基础,有助于在面对不断变化的需求时更好地应对挑战。
在实际开发中,理解并应用这些设计原则是至关重要的。它们提供了一组指导原则,帮助自己构建出更加健壮和灵活的软件系统。通过不断学习和实践,可以更好地运用这些原则来创建高质量的面向对象设计。