【本人秃顶程序员】Java的SOLID编程原则

蔚落 2022-03-16 10:48 397阅读 0赞

←←←←←←←←←←←← 快!点关注

SOLID阐述了五种设计原则,可帮助开发人员轻松扩展和维护软件:

S - 单一责任原则
O - 开放原则
L - Liskov替代原理
I - 界面隔离原理
D - 依赖倒置原则

单一责任原则

一个类应该有一个,而且只有一个改变的理由。

一个类应该只有一个责任,这意味着类应该高度凝聚并实现强相关的逻辑。实现功能1和功能2和功能3(依此类推)的类违反了SRP。

SRP示例:

  1. // BAD
  2. public class UserSettingService {
  3. public void changeEmail(User user) {
  4. if (checkAccess(user)) {
  5. //Grant option to change
  6. }
  7. }
  8. public boolean checkAccess(User user) {
  9. //Verify if the user is valid.
  10. }
  11. }
  12. // GOOD
  13. public class UserSettingService {
  14. public void changeEmail(User user) {
  15. if (securityService.checkAccess(user)) {
  16. //Grant option to change
  17. }
  18. }
  19. }
  20. public class SecurityService {
  21. public static boolean checkAccess(User user) {
  22. //check the access.
  23. }
  24. }

SRP味道

  • 单个类中不止一个上下文分隔的代码
  • 测试中的大型setup初始化设置(TDD在检测SRP违规时非常有用)

SRP好处

  • 负责给定用例的分隔类现在可以在应用程序的其他部分中重用
  • 负责给定用例的分隔类现在可以单独测试

开/闭原则

您应该能够扩展类行为,而无需对其进行修改。

类应该打开以进行扩展并关闭以进行修改。您应该能够扩展类行为而无需修改其实现:

  1. // BAD
  2. public class Logger {
  3. String logging;
  4. public Logger(String logging) {
  5. this.logging = logging;
  6. }
  7. public void log() {
  8. if ("console".equals(logging)) {
  9. // Log to console
  10. } else if ("file".equals(logging)) {
  11. // Log to file
  12. }
  13. }
  14. }
  15. // GOOD
  16. public interface Log {
  17. void log();
  18. }
  19. public class ConsoleLog implements Log {
  20. void log() {
  21. // Log to console
  22. }
  23. }
  24. public class FileLog implements Log {
  25. void log() {
  26. // Log to file
  27. }
  28. }
  29. public class Logger {
  30. Log log;
  31. public Logger(Log log) {
  32. this.log = log;
  33. }
  34. public void log() {
  35. this.log.log();
  36. }
  37. }

OCP代码味道:

  • 如果你注意到类X直接引用其代码库中的其他类Y,则表明类Y应该传递给类X(通过构造函数/单个方法),例如通过依赖注入
  • 复杂的if-else或switch语句

OCP好处:

  • 使用封装在单独类中的新功能可以轻松扩展X类功能,而无需更改类X实现(它不知道引入的更改)
  • 代码松散耦合
  • 注入的Y类可以在测试中轻易模拟

利斯科夫替代原则

派生类必须可替代其基类。

这是开/闭原则的​​延伸。派生类不应更改基类的行为(继承方法的行为)。如果类Y是类X的子类,则任何引用类X的实例也应该能够引用类Y(派生类型必须完全替代它们的基类型)。

  1. // BAD
  2. public class DataHashSet extends HashSet {
  3. int addCount = 0;
  4. public boolean function add(Object object) {
  5. addCount++;
  6. return super.add(object);
  7. }
  8. // the size of count will be added twice!
  9. public boolean function addAll(Collection collection) {
  10. addCount += collection.size();
  11. return super.addAll(collection);
  12. }
  13. }
  14. // GOOD: Delegation Over Subtyping
  15. public class DataHashSet implements Set {
  16. int addCount = 0;
  17. Set set;
  18. public DataHashSet(Set set) {
  19. this.set = set;
  20. }
  21. public boolean add(Object object) {
  22. addCount++;
  23. return this.set.add(object);
  24. }
  25. public boolean addAll(Collection collection) {
  26. addCount += collection.size();
  27. return this.set.addAll(collection);
  28. }
  29. }

LSP代码味道:

  • 如果它看起来像一只鸭子,嘎嘎叫像鸭子但需要电池才能达到这个目的 - 这可能违反了LSP
  • 修改子类中的继承行为
  • 在重写的继承方法中引发的异常

LSP好处:

  • 避免意外和不正确的结果
  • 明确区分共享继承接口和扩展功能

接口隔离原理

制作客户端特定的细粒度接口。

一旦接口变得太大/太胖,我们绝对需要将其拆分为更具体的小接口。接口将由将使用它的客户端定义,这意味着接口的客户端将只知道与它们相关的方法。

  1. // BAD
  2. public interface Car {
  3. Status open();
  4. Speed drive(Gas gas);
  5. Engine changeEngine(Engine newEngine);
  6. }
  7. public class Driver {
  8. public Driver(Car car) {}
  9. public Speed ride() {
  10. this.car.open();
  11. return this.car.drive(new Gas(10));
  12. }
  13. }
  14. public class Mechanic {
  15. public Mechanic(Car car) {}
  16. public Engine fixEngine(Engine newEngine) {
  17. return this.car.changeEngine(newEngine);
  18. }
  19. }
  20. // GOOD
  21. public interface RidableCar {
  22. Status open();
  23. Speed drive(Gas gas);
  24. }
  25. public interface FixableCar {
  26. Engine changeEngine(Engine newEngine);
  27. }
  28. public class Driver {
  29. // Same with RidableCar
  30. }
  31. public class Mechanic {
  32. // Same with FixableCar
  33. }

ISP代码味道

  • 由许多类实现的一个胖接口,其中没有一个类实现100%的接口方法。这种胖接口应该分成适合客户需求的较小接口

ISP好处

  • 高度凝聚力的代码
  • 避免使用单个胖接口在所有类之间进行耦合(一旦单个胖接口中的方法得到更新,所有类 - 无论是否使用此方法 - 都被迫相应地更新)
  • 通过将职责分组到单独的界面中,明确分离业务逻辑

依赖倒置原则

依赖于抽象,而不是实现

如果您的实现细节将取决于更高级别的抽象,它将帮助您获得正确耦合的系统。而且,它将影响该系统的封装和内聚。

  1. // BAD
  2. public class SQLDatabase {
  3. public void connect() {
  4. String connectionstring = System.getProperty("MSSQLConnection");
  5. // Make DB Connection
  6. }
  7. public Object search(String key) {
  8. // Do SQL Statement
  9. return query.find();
  10. }
  11. }
  12. public class DocumentDatabase {
  13. // Same logic but with document details
  14. }
  15. // GOOD
  16. public interface Connector {
  17. Connection open();
  18. }
  19. public interface Finder {
  20. Object find(String key);
  21. }
  22. public class MySqlConnector implements Connector {}
  23. public class DocumentConnector implements Connector {}
  24. public class MySqlFinder implements Finder {}
  25. public class DocumentFinder implements Finder {}
  26. public class Database {
  27. public Database(Connector connector,
  28. Finder finder) {
  29. this.connector = connector;
  30. this.finder = finder;
  31. }
  32. public Connection connect() {
  33. return connector.open();
  34. }
  35. public Object search(String key) {
  36. return finder.find(key);
  37. }
  38. }

DIP味道:

  • 在高级模块中实例化低级模块
  • 调用低级模块/类的类方法

DIP好处:

  • 通过使更高级别的模块独立于较低级别的模块来提高其可重用性
  • 可以采用依赖性注入1来促进所选择的低级组件实现的运行时供应到高级组件
  • 注入类可以在测试中轻易模拟

欢迎大家加入粉丝群:963944895,群内免费分享Spring框架、Mybatis框架SpringBoot框架、SpringMVC框架、SpringCloud微服务、Dubbo框架、Redis缓存、RabbitMq消息、JVM调优、Tomcat容器、MySQL数据库教学视频及架构学习思维导图

写在最后:

秃顶程序员的不易,看到这里,点了关注吧!
点关注,不迷路,持续更新!!!

发表评论

表情:
评论列表 (有 0 条评论,397人围观)

还没有评论,来说两句吧...

相关阅读