面向对象的设计原则旨在帮助开发者构建健壮、灵活、可扩展的软件系统。常见的面向对象设计原则主要包括SOLID原则以及其他一些辅助原则。
单一职责原则(SRP - Single Responsibility Principle)
定义:一个类应该只有一个引起它变化的原因,即一个类只负责一个职责。
作用:降低类的复杂度,增强类的可维护性和可读性。
示例:一个类如果既负责数据库操作,又负责处理业务逻辑,那么它违反了单一职责原则。应将这些功能拆分为不同的类。
开闭原则(OCP - Open/Closed Principle)
定义:软件实体应对扩展开放,对修改关闭。
作用:通过扩展系统功能而不修改现有代码,保持系统的稳定性。
示例:使用接口和抽象类来定义扩展点,添加新功能时通过继承或实现来扩展,而不修改现有类的实现。
它指出“软件实体(类、模块、函数等)应该是对扩展开放,对修改关闭”。简单说,就是说更改已有代码应该尽可能地避免,而应该通过扩展来达到新的需求和需求变更。开闭原则的核心思想和目标是为了让代码更容易维护、扩展和重用,同时减少代码修改带来的风险。通过设计合理的抽象层次、接口和抽象类来实现开闭原则,可以使得系统具有更强的可扩展性和可维护性,同时也可以更好地支持系统的演进。
里氏替换原则(LSP - Liskov Substitution Principle)
定义:子类必须能够替换其父类,且不会影响系统的正确性。
作用:保证继承关系的正确性,避免子类破坏父类的功能。
示例:在继承关系中,子类应该能够使用父类的功能而不引发错误,如子类不应该删除或修改父类提供的关键功能。
它指出“子类对象可以替换父类对象,且程序逻辑不会受到影响”。简单说,就是说对于任何父类可以使用的地方,都可以用其子类进行替换,且程序的逻辑不能发生变化。简单说,Liskov替换原则的目标是使用一个父类的对象可以替换掉任意一个子类的对象。当我们在程序设计时,如果不能遵循LSP,就有可能会在程序运行时出现逻辑错误或者程序崩溃的情况。因此,遵循里氏替换原则是保证程序正确性和稳定性的一个重要方式。
接口隔离原则(ISP - Interface Segregation Principle)
定义:客户端不应该被迫依赖于它不使用的接口。
作用:避免臃肿的接口,通过多个精简接口提高系统灵活性。
示例:如果一个接口包含了过多的方法,不同的实现类可能只使用其中一部分,那么应该将接口拆分为多个小的接口,各类只需实现与自己相关的接口。
接口隔离原则(Interface Segregation Principle)ISP 则是指客户端不应该依赖它不使用的接口。这意味着,当我们设计接口时,应该尽可能将接口拆分成更小、更具体的接口,使得客户端只需要使用它们需要的接口,而不需要依赖整个接口。接口隔离原则的目的是通过尽可能少的关联来达到松耦合,并提高代码的可复用性和可维护性。通过遵循接口隔离原则,我们可以在保持代码的高内聚性的同时,减少不必要的耦合,并提高代码的可扩展性和可维护性。
依赖倒转原则(DIP - Dependency Inversion Principle)
定义:高层模块不应该依赖于低层模块,二者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。
作用:通过依赖抽象而不是具体实现类,使系统更灵活,便于修改和维护。
示例:在构建软件时,通过接口而不是具体类进行依赖注入,增加系统的可扩展性。
简单来说,依赖倒转原则要求我们依赖于抽象而不是具体实现。这样可以使我们的代码更具有扩展性和灵活性。使用依赖倒转原则,我们可以通过接口来引用具体实现,可以方便地更换具体实现,而不需要修改高层模块的代码。同时,我们也可以通过接口和抽象类来定义约束,让下层实现满足上层需求,更容易实现多态和可测试性。通俗的话语来说,就是对象存在依赖关系的时候,我们应该尽量依赖与对象的 接口 和 抽象类 。而且不是具体的实现子类。事例代码:
// 定义一个物品接口
interface Item {
void use();
}
// 定义一个物品拓展接口,继承自物品接口
interface ItemExtension extends Item {
void upgrade();
}
// 定义一个基础物品类,实现物品接口
class BaseItem implements Item {
@Override
public void use() {
System.out.println("使用了基础物品");
}
}
// 定义一个物品拓展类,实现物品拓展接口
class ItemUpgrade implements ItemExtension {
private Item item;
public ItemUpgrade(Item item) {
this.item = item;
}
@Override
public void use() {
this.item.use(); // 委托给基础物品类来实现使用方法
}
@Override
public void upgrade() {
System.out.println("升级了该物品");
}
}
// 示例代码
public class Main {
public static void main(String[] args) {
Item item = new BaseItem(); // 创建基础物品
ItemExtension itemExt = new ItemUpgrade(item); // 使用物品拓展类来拓展该物品
item.use();
itemExt.use();
itemExt.upgrade();
}
}
在上面的代码中,我们定义了一个物品接口,然后创建了一个基础物品类来实现这个接口。接下来,我们定义了一个物品拓展接口,并创建了一个物品拓展类来实现该接口。这个类使用在构造函数中传递的基础物品来实现拓展功能。
这个设计实现了依赖倒转原则,因为物品拓展类依赖于物品接口而不是具体的基础物品类。这意味着我们可以轻松地为任何实现物品接口的类添加拓展功能,而不需要修改拓展类代码。
这个示例虽然简单,但它展示了如何使用依赖倒转原则来设计代码。
迪米特法则(LoD - Law of Demeter)
定义:一个对象应该对其他对象有尽可能少的了解。
作用:降低耦合性,增强系统的模块化和封装性。
示例:类之间的交互尽量通过公开的方法进行,而不是直接访问其他类的内部数据。迪米特法则(Law of Demeter,LoD),又称作最少知识原则(Principle of Least Knowledge,PLK),是一种面向对象设计的原则。迪米特法则的定义是:一个对象应该对其他对象有尽可能少的了解,不和陌生人说话。
简单来说,迪米特法则要求在设计系统时,一个对象应该了解最少的其他对象,从而使系统更加松散耦合,更加易于维护和拓展。具体而言,迪米特法则要求:
- 一个对象可以调用另一个对象的方法,但是不应该了解该对象内部的详细信息;
- 对于与当前对象没有直接关系的对象,尽量以最少的方式进行交互,避免引入不必要的耦合。
迪米特法则的核心思想是减少对象之间的依赖关系,从而提高系统的模块化和灵活性。它与其他面向对象设计原则如单一职责原则、开闭原则等共同构成了面向对象设计的基础。
例如,在一个购物车系统中,如果一个客户对象需要获取他购物车中的商品数量,按照迪米特法则,客户对象应该直接向购物车对象请求商品数量,而不是直接访问商品对象获取数量。这样做可以避免客户对象了解过多的信息,也能够降低客户对象与商品对象之间的耦合度。最好的事例是:门面模式 和 调停者模式。
合成复用原则(CRP - Composition over Inheritance)
定义:应通过组合对象来达到复用的目的,而不是通过继承。
作用:避免类层次结构过于复杂,提供更灵活的对象复用方式。
示例:一个类应该包含另一个类的实例,而不是通过继承直接获得其功能。
依赖注入原则(Dependency Injection)
定义:对象通过外部注入其依赖对象,而不是在内部创建它们。
作用:解耦对象之间的依赖关系,便于测试和维护。
示例:在 Spring 框架中,通过注解或 XML 配置来注入依赖对象。
致谢
更多内容欢迎点击阅读原文,喜欢文章的话,也希望能给小编点个赞或者转发,你们的喜欢与支持是小编最大的鼓励,小巫编程室感谢您的关注与支持。好好学习,天天向上(good good study day day up)。