JAVA 编程中的SOLID设计原则

简介: JAVA 编程中的SOLID设计原则

在软件的生命周期中,完成并不代表着结束,往往维护运营往往需要投入更多的成本,包括精力成本和时间成本。而一个遵循着好的开发规范以及拥有着良好设计原则的系统,往往可以节约大量的后期维护升级成本。说起设计,往往大家第一反应是设计模式,殊不知,二十几种设计其实都遵循着一些基本的设计原则。S.O.L.I.D,是事实证明的良好设计原则。

SOLID,每个字母分别对应于一个原则:

  • S,SRP,Single Responsibility Principle
  • O,OCP,Open/Closed Principle
  • L,LSP,Liskov Substitution Principle
  • I,ISP,Interface Segregation Principle
  • D,DIP,Dependency Inversion Principle

SRP,单一职责原则

SRP states that every class should have a single responsibility. There should never be more than one (design-related) reason for a class to change. 意为每个类应该有且仅有一个职责,只负责该职责相关的事情。在设计上,不应该有多于一个原因导致该类的变化。

这个原则相对简单,比如一家餐厅,服务生负责为客人点餐倒水,保洁负责收拾桌子,厨师负责料理,收银员负责收银,财务负责核账。一般而言,越大型的软件设计,职责划分就越细致。这样不会因为收银方式变更,导致厨师重新培训,在软件开发中则不用因为一个类或者模块变更导致整个软件都要进行重新测试。

OCP,开闭原则

OCP states that objects or entities should be open for extension, but closed for modification. 意为软件中的对象或实体,比如类、模块、函数等,要尽量 允许扩展避免更改。按照这个原则,当我们需要为某个模块/类添加某个行为时,应该是通过增加一个类/方法而不是修改既有的某个类/方法达成目标。

这个原则,在我们的软件开发过程中,应该是很常见的,尤其是在使用第三方库的时候,会发现,一个优秀的第三方库,有一个更优的算法时,往往会增加一个新的类/方法去实现该算法并建议使用它,而不是直接修改旧有的算法类/方法。如果不遵循该原则,直接大刀阔斧地修改了某对象的行为,而恰巧该对象被系统的其他部分依赖怎么办?那岂不是要更改每一处对该对象行为的引用的地方,再检查逻辑是否因此变化,还得再做一堆的测试,凭空增加了工作量。

LSP,里氏替换原则

LSP states that objects in a program should be replaceable with instances of their subtypes without altering the correctness of the program. 该原则是说,在程序中,对象应该都是可以用它们的子类型来替换,而不影响程序的正确性,即不出异常不报错。

理论上来讲,如果父类能实现的逻辑,子类同样也能实现,那么它们才具备父子关系,否则请移除其父子关系。例如,有这样一句话『我用枪击杀了敌人』,其中的『枪』,我可以换成『手枪』、『狙击枪』,这样没问题,所以在这个系统中,枪和手枪、狙击枪之间可以有继承关系,如果我说『我用道具枪击杀了敌人』,因为道具枪不能杀人,显然这句话就出现了明显的逻辑漏洞,据此,在这句话所构成的系统中,水枪不能和枪成为父子类型的关系。当然,在不同的系统中,父子关系是不一定的,所以里氏替换原则,也只需要在特定系统中遵循即可。例如,『我听到了一声枪响』,在这个系统中,即使是演电影的道具枪,也可以有枪响,所以它就和枪具备了可替换性。

ISP,接口隔离原则

ISP states that many client-specific interfaces are better than one general-purpose interface. 意为许多客户特定的接口,要优于一个大而全的通用目的的接口。分拆合理的接口,能避免任意的实现,都需要实现一大堆根本不需要但又不得不去实现的方法。

这么理解,现在有一个系统,想要描述自然界各类动物的移动行为,我们看下面两种方式哪个更好一些。 方式一,只定义一个 Moveable ,描述动物有飞、走、游三种不同的移动方式:

public interface Moveable {   
void fly();  
void walk();  
void swim();
}

方式二,定义飞会飞的 Flyable ,会走的 Walkable ,会游的 Swimmable

public interface Flyable {   
void fly();
}
public interface Walkable {  
void walk();
}
public interface Swimmable { 
void swim();
}

OK,我现在想描述具体的鲸鱼的移动方式,如果按照方式一,我可能需要些一个 Whale 类,去实现其中的所有三个方法,而事实上,fly() 和 walk() 方法,与鲸毫无关系。而第二种方式,我知道鲸是游的,那么只需要去实现 Swimmable 接口,并实现其中的 swim() 方法即可。同理,当描述麻雀、大象时,只需要分别实现对应的 Flyable 和 Walkable, 而对于青蛙这种既会游又会走的,只需要实现 Swimmable 和 Walkable 两个接口即可完美描述其移动方式。

值得说明的一点是,该原则中的接口并不特指 Java 中的 interface ,而是类似于 API 中的 I一样的泛义的接口,抽象类甚至具体实现类都可能包含在这个概念中。

DIP,依赖倒置原则

DIP states that the high level module must not depend on low level module, but they should depend on abstractions. 即高层模块不能依赖于具体的底层模块,而是应该依赖于底层模块的抽象。换句话说,要尽量使用抽象最小化对象之间的依赖。

例如现在有一个 App 类,这个类可能有发邮件(Email)、发短信(SMS)、数据入库(Database)等操作,其中我们认为 Email/SMS/Database 都是具体的特定类,与其让 App 类去依赖于这三个具体的类,DIP 指导我们对这三个具象类抽象出一个 Service 类。如此一来,App 只依赖于一个抽象类 Service。这样的好处显而易见,我们不仅能随时替换 Service 的功能(想发邮件发邮件、想发短信发短信、想数据入库就入库),甚至还能扩展功能,比如添加日志,审计功能。

遵循良好的设计原则,有利于我们平常在开发中写出更可维护的代码,便于团队协作也有利于后来者。道理上讲,设计模式、设计原则等等,也理应成为OOP程序员之间的常用术语,这样一来,才能显得更具专业性。

结语

如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、评论、收藏➕关注,您的支持是我坚持写作最大的动力。


目录
相关文章
|
1月前
|
Java 程序员
Java编程中的异常处理:从基础到高级
在Java的世界中,异常处理是代码健壮性的守护神。本文将带你从异常的基本概念出发,逐步深入到高级用法,探索如何优雅地处理程序中的错误和异常情况。通过实际案例,我们将一起学习如何编写更可靠、更易于维护的Java代码。准备好了吗?让我们一起踏上这段旅程,解锁Java异常处理的秘密!
|
29天前
|
存储 缓存 Java
Java 并发编程——volatile 关键字解析
本文介绍了Java线程中的`volatile`关键字及其与`synchronized`锁的区别。`volatile`保证了变量的可见性和一定的有序性,但不能保证原子性。它通过内存屏障实现,避免指令重排序,确保线程间数据一致。相比`synchronized`,`volatile`性能更优,适用于简单状态标记和某些特定场景,如单例模式中的双重检查锁定。文中还解释了Java内存模型的基本概念,包括主内存、工作内存及并发编程中的原子性、可见性和有序性。
Java 并发编程——volatile 关键字解析
|
1月前
|
算法 Java 调度
java并发编程中Monitor里的waitSet和EntryList都是做什么的
在Java并发编程中,Monitor内部包含两个重要队列:等待集(Wait Set)和入口列表(Entry List)。Wait Set用于线程的条件等待和协作,线程调用`wait()`后进入此集合,通过`notify()`或`notifyAll()`唤醒。Entry List则管理锁的竞争,未能获取锁的线程在此排队,等待锁释放后重新竞争。理解两者区别有助于设计高效的多线程程序。 - **Wait Set**:线程调用`wait()`后进入,等待条件满足被唤醒,需重新竞争锁。 - **Entry List**:多个线程竞争锁时,未获锁的线程在此排队,等待锁释放后获取锁继续执行。
66 12
|
29天前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
160 2
|
2月前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
|
2月前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
1月前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
1月前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
66 3
|
1月前
|
开发框架 安全 Java
Java 反射机制:动态编程的强大利器
Java反射机制允许程序在运行时检查类、接口、字段和方法的信息,并能操作对象。它提供了一种动态编程的方式,使得代码更加灵活,能够适应未知的或变化的需求,是开发框架和库的重要工具。
70 4
|
2月前
|
安全 Java 开发者
Java中的多线程编程:从基础到实践
本文深入探讨了Java多线程编程的核心概念和实践技巧,旨在帮助读者理解多线程的工作原理,掌握线程的创建、管理和同步机制。通过具体示例和最佳实践,本文展示了如何在Java应用中有效地利用多线程技术,提高程序性能和响应速度。
78 1