如果你喜欢这篇帖子,请点赞。
SOLID (以下简称 SOLID) 原则和设计模式是面向对象编程中常用的软件开发原则和技术,旨在使软件设计更灵活、可扩展和易于理解。SOLID 原则最初是由美国软件工程师和讲师罗伯特·C. 首次提出的。
在软件开发生命周期中,尤其是在对象的设计阶段,其易访问性和灵活性非常重要,以确保其简洁性、可维护性和易于实现,同时提高其易访问性,使软件易于创建并优秀。
SOLID 原则为什么重要:
SOLID 原则是用来指导我们设计软件系统的一套基本原则,如果正确应用,可以大大降低出现糟糕设计的概率。罗伯特·马丁在他的文章《面向对象设计原则》中列出了应避免的一些不良设计的重要方面。他指出糟糕的依赖管理会导致代码变得僵硬、脆弱且难以改动。另一方面,良好的依赖管理则可以让代码易于维护、灵活且可重用。SOLID 原则向开发人员详细介绍了面向对象设计中依赖管理的部分,它们特别强调依赖管理的重要性。以下是使用 SOLID 原则的一些好处。
- SOLID 原则是每个开发者都应该了解的一般原则,以正确地开发软件并避免设计上的臭味。
- 主要目标是改进依赖管理,让代码更加灵活、易于理解、且可重用。
- 如果正确地应用 SOLID 原则,设计会更容易理解、维护和扩展。
- 这些原则帮助开发人员避免设计问题,并构建有效的、灵活的、可维护的和敏捷的软件。
SOLID代表以下五个设计原则,具体来说是这五个原则。
- S — 单一职责原则(SRP)
- O — 开闭原则 (OCP)
- L — 里氏替换原则 (LSP)
- I — 接口隔离原则 (ISP)
- D — 依赖倒置原则 (DIP)
单一职责原则(SRP)意味着一个类应该只负责一件事情,只有一个需要改变的原因。
在Spring Boot中的一个例子说明,我们来看一个Spring Boot应用,该应用有一个服务类处理用户管理以及通知逻辑。由于该类现在有不止一个修改的理由(比如用户管理逻辑或通知逻辑发生变化),所以它已经违反了单一职责原则(SRP)。这就违反了单一职责原则。
在违反SRP之前,
导入 org.springframework.stereotype.Service
@Service
public class UserNotificationService {
public void addUser(String userName) {
// 处理添加用户的逻辑
}
public void sendEmailNotification(String email) {
// 处理发送邮件通知的逻辑
}
}
遵循单一职责原则后
我们应该将这拆分为两个独立的类,各自只负责单一职责。
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void addUser(String userName) {
// 添加用户逻辑
}
}
@Service
public class NotificationService {
public void sendEmailNotification(String email) {
// 发送电子邮件通知逻辑
}
}
在重构后的版本中,**UserService**
管理与用户相关的操作,而 **NotificationService**
负责发送通知。这符合单一责任原则(SRP,即单一职责原则),使 Spring Boot 应用程序更模块化,更易理解和维护。
接下来,我们将通过一个专为Spring Boot设计的示例进一步探讨开闭原则这一概念(即OCP)。
O 开闭性原则 (OCP)开闭原则(OCP)规定,任何软件组件,如类、模块或函数,应该易于扩展但不可修改。这意味着我们可以在不影响现有行为的情况下,为现有对象添加新功能。
在 Spring Boot 应用中,我们可以展示 OCP(开闭原则),允许扩展类以表现出多态行为,而无需实际修改给定的类。
在可能违反OCP之前
比如说我们有一个处理消息的服务程序,不过它只能处理纯文本消息。
import org.springframework.stereotype.Service;
@Service
public class MessageService {
public void processMessage(String message) {
// 处理一条文本消息
System.out.println("处理文本消息: " + message);
}
}
遵守了OCP之后
我们引入了一个接口,并扩展了其功能来处理不同类型的消息,而无需修改原有的类。
import org.springframework.stereotype.Service;
public interface 消息处理接口 {
void 处理(String 消息);
}
@Service
public class 文本消息处理类 implements 消息处理接口 {
@Override
public void 处理(String 消息) {
System.out.println("处理文本消息: " + 消息);
}
}
@Service
public class 图像消息处理类 implements 消息处理接口 {
@Override
public void 处理(String 消息) {
// 处理图像消息的代码
System.out.println("处理图像消息: " + 消息);
}
}
在本 Spring Boot 示例中,MessageProcessor
是一个定义了 processMessage
方法的接口。TextMessageProcessor
(文本消息处理器)和 ImageMessageProcessor
(图片消息处理器)是该接口的两个实现,分别处理文本消息和图片消息。这种设计遵循了 OCP 原则,使得系统可以在不修改现有代码的情况下支持新的消息类型。
接下来,我们将通过Spring Boot示例进一步探讨LSP(里氏替换原则)。
L — 里士满替代原则, (LSP)里斯科夫替换原则(LSP)指出,超类对象可以被它的任何子类对象替换,而不会影响程序的正确运行。这样就保证了任何子类都可以替换超类对象而不会引入错误或意外情况。
Spring Boot示例
为了在Spring Boot应用中展示LSP的应用,让我们考虑一个关于用户角色和访问权限的情景。
在潜在的LSP违反规定之前
假设我们有一个 **User**
基类及其子类 **AdminUser**
。如果 **AdminUser**
以未预期的方式改变了基类的行为,这可能会违反LSP原则。
import org.springframework.stereotype.Component;
@Component
public class User {
public String accessResource() {
return "访问用户资源";
}
}
@Component
public class AdminUser extends User {
@Override
public String accessResource() {
throw new RuntimeException("管理员不能访问此资源");
}
}
在这个例子中,**AdminUser**
在访问资源时意外抛出了一个异常,这可能会带来一些麻烦,因为通常我们期望 AdminUser
就像一个普通的 **User**
。
遵循LSP后如下
为了遵守LSP,子类应该增强,而不是替换或限制基类的行为表现。
import org.springframework.stereotype.Component;
@Component
public class User {
public String accessResource() {
return "访问用户资源";
}
}
@Component
public class AdminUser extends User {
@Override
public String accessResource() {
return "访问管理员资源";
}
}
在修改后的例子中,**AdminUser**
通过访问管理员专属的资源扩展了 **User**
的功能,确保 AdminUser
可以像 User
一样在任何地方使用,从而遵守 LSP 原则。
理解了LSP在Spring Boot中的应用,我们现在可以通过一个例子来进一步探讨接口分离原则。
一 — 接口隔离原则接口隔离原则(ISP)。该原则建议将大的通用接口拆分细化,变成更小且更专注于特定功能的接口。实际上,这个原则非常有助于确保实现类只包含与它们相关的接口方法,从而使类更加模块化、内聚性更强。
Spring Boot示例
为了在一个Spring Boot应用中说明ISP原则,让我们考虑一个具有多种职责的服务接口。
在违反(ISP)之前
这里有一个接口,结合了不同的功能,但并非每个实现的类都会用到这些功能。
import org.springframework.stereotype.Service;
public interface 用户操作接口 {
void 添加用户(String 用户名);
void 更新用户(String 用户名);
void 删除用户(String 用户名);
void 发送用户通知(String 用户名);
}
@Service
public class 用户服务 implements 用户操作接口 {
@Override
public void 添加用户(String 用户名) {
// 添加用户相关逻辑
}
@Override
public void 更新用户(String 用户名) {
// 更新用户相关逻辑
}
@Override
public void 删除用户(String 用户名) {
// 删除用户相关逻辑
}
@Override
public void 发送用户通知(String 用户名) {
// 发送用户通知功能暂不实现
}
}
遵循ISP之后
我们可以将 **UserOperations**
接口拆分出更小,更具体的接口。
import org.springframework.stereotype.Service;
public interface 用户管理服务 {
void 添加用户(String 用户);
void 更新用户(String 用户);
void 删除用户(String 用户);
}
public interface 用户通知服务 {
void 发送用户通知(String 用户);
}
@Service
public class 用户服务类 implements 用户管理服务 {
@Override
public void 添加用户(String 用户) {
// 添加用户的逻辑处理
}
@Override
public void 更新用户(String 用户) {
// 更新用户的逻辑处理
}
@Override
public void 删除用户(String 用户) {
// 删除用户的逻辑处理
}
}
@Service
public class 通知服务类 implements 用户通知服务 {
@Override
public void 发送用户通知(String 用户) {
// 发送通知的逻辑处理
}
}
在这个重构的例子中,**UserService**
实现了 UserManagement
功能,处理与用户有关的操作,而 **NotificationService**
实现了 UserNotification
功能,负责发送通知信息。这样每个服务类只处理相关的功能,遵循 ISP 原则。
最后,我们来看看一个基于Spring Boot的例子,来探讨DIP原则。
D — 依赖倒置原则 (DIP 原则)依赖倒置原则(DIP)指出,高层模块不应该依赖于低层模块,两者都应依赖于抽象。抽象不应依赖于细节,而细节应依赖于抽象。其中一个核心理念允许设计系统,使高层策略与低层细节解耦,从而可以独立替换或更改各个部分。
在 Spring Boot 中的一个示例 比如,在一个 Spring Boot 场景中,这可以定义依赖项的接口(或抽象),然后允许在运行时注入这些依赖——通常,Spring 提供的依赖注入机制通常会在这里发挥作用。
在...之前(未完全遵守DIP)
这里是一个高层次的类直接依赖于低层次的类的例子。
import org.springframework.stereotype.Component;
@Component
public class UserProcessor {
private DatabaseService databaseService = new DatabaseService();
public void processUser(String user) {
databaseService.saveUser(user);
}
}
@Component
public class DatabaseService {
public void saveUser(String user) {
// 保存用户到数据库中
}
}
(遵循DIP之后)
为了遵循依赖倒置原则,我们应该依赖抽象的接口而不是具体的实现。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
interface DatabaseService {
void saveUser(String user);
}
@Service
public class DatabaseServiceImpl implements DatabaseService {
@Override
public void saveUser(String user) {
// 把用户存进数据库
}
}
@Component
public class UserProcessor {
private DatabaseService databaseService;
@Autowired
public UserProcessor(DatabaseService databaseService) {
this.databaseService = databaseService;
}
public void processUser(String user) {
databaseService.saveUser(user);
}
}
在改进后的例子中,**UserProcessor**
依赖于 **DatabaseService**
抽象,而不是具体的 **DatabaseServiceImpl**
。这样,当需要替换或修改 **DatabaseService**
时,**UserProcessor**
无需做出任何改变。这展示了依赖倒置原则的应用。Spring Boot 利用 @Autowired
注解在运行时注入具体的实现,从而提高了系统的灵活性并减少了组件间的耦合。
因此,我们在Spring Boot背景下对SOLID设计原则的简短探索展示了如何在开发Java应用程序时应用这些原则,从而构建出更易维护、可扩展且稳健的系统这样的系统。通过持续应用这些原则,开发人员确保他们的应用程序设计良好,能够应对现代软件开发中的各种挑战。
👏如果你觉得我的文章有用,请考虑给我点个赞,并与你们的朋友和同事分享一下。
共同学习,写下你的评论
评论加载中...
作者其他优质文章