zoukankan      html  css  js  c++  java
  • 适用于Java开发人员的SOLID设计原则简介

    看看这篇针对Java开发人员的SOLID设计原则简介。抽丝剥茧,细说架构那些事——【优锐课】

    当你刚接触软件工程时,这些原理和设计模式不容易理解或习惯。我们都遇到了问题,很难理解SOLID + DP的思想,甚至很难正确实施它们。确实,“为什么要SOLID?”的整个概念,以及如何实施设计模式,这需要时间和大量实践。

    我可以说实话,关于SOLID设计模式以及TDD等其他领域,从本质上讲,它们很难教。很难以正确的方式将所有这些知识和信息传授给年轻人。

    让SOLID 变得容易

    在本文中,我将以尽可能简单的术语,通过简单易懂的示例来教授SOLID的每个字母。

    SOLID的“S”

    S代表SRP(单一责任原则)。基本思想是应用关注点分离,这意味着你应尝试将关注点分离到不同的类中。一堂课应该专注于单个问题,逻辑或单个领域。当域,规范或逻辑发生变化时,它只影响一个类。

    实施SRP之前

    下面,我们违反了SRP。VehicleServiceResource类实现了两种不同的方法,并以两种角色结束。如我们所见,该类具有两个标记其用法的注释。

    一种是向客户端公开和提供HTTP终结点服务的角色。

    第二个是车辆服务的角色,该服务从存储getVehicles()中获取车辆并计算总值calculateTotalValue()

     1 @EndPoint("vehicles")
     2 @Service
     3 public class VehicleServiceResource {
     4  5      @GET
     6      public List getVehicles(){
     7      }  
     8      public double calculateTotalValue(){}
     9 10 }

     实现SRP的简单目标是将VehicleServiceResource分为两个不同的类:一个用于端点,另一个用于服务。

    SRP实施后

    我们要做的是获取VehicleServiceResource类,并将其分为两个不同的类。

    VehicleResource类仅具有一项和一项工作。为了向客户端公开HTTP资源工具并向其提供服务,所有与业务逻辑相关的方法均导致VehicleService类。

     1 @EndPoint("vehicles")
     2 public class VehicleResource {
     3   @Service
     4   private VehicleService service;
     5   @GET
     6   public List getVehicles() {
     7       return this.service.getVehicles(); 
     8   }
     9   ...  
    10 }

    我们创建了一个名为 VehicleService的新类。此类实现所有与车辆有关的逻辑。

    1 @Service
    2 public class VehicleService {
    3     ...
    4     public List getVehciles() {} 
    5     public double calculateTotalValue(){}    
    6     ...
    7 }

    SOLID的“O”

    O代表OCP(开闭原理)。开闭原则指出:

     " ... 软件实体(例如模块,类,功能等)应打开以进行扩展,但应关闭以进行修改。"

    术语“开放扩展”是指我们可以在代码中扩展并包括额外的案例/功能,而不会更改或影响我们现有的实现。

    术语“关闭以进行修改”表示在添加了附加功能之后,我们不应修改现有的实现。

    一个简单的违反OCP的行为:

     1 public class VehicleValueCalculator {
     2     // lets assume a simple method to calculate the total value of a vehicle
     3     // with extra cost depending the type.
     4     public double calculateVehicle(Vehicle v){
     5         double value = 0;
     6         if(v instanceof Car){
     7             value = v.getValue() + 2.0;
     8         } else if(v instanceof MotorBike) {
     9             value = v.getValue() + 0.4;
    10         } 
    11         return value;
    12     }
    13 }

     当我们要包括一种新型车辆“卡车”时,就会违反OCP。需要对calculateVehicle方法进行重构和代码修改。

    解决

     1 public interface IVehicle {
     2       double calculateVehicle();
     3 }
     4 public class Car implements IVehicle {
     5     @Override
     6     public double calculateVehicle() {
     7         return this.getValue() + 2.0;
     8     }
     9 }
    10 public class MotorBike implements IVehicle {
    11     @Override
    12     public double calculateVehicle() {
    13         return this.getValue() + 0.4;
    14     }
    15 }

    我们的新卡车

    1 public class Truck implements IVehicle {
    2     @Override
    3     public double calculateVehicle() {
    4         return this.getValue() + 3.4;
    5     }
    6 }

    这样,通过使用一种接受IVehicle的方法,以后在每次添加新型车辆时都无需进行重构/代码修改。

    范例程式码

     1 public class Main {
     2     public static void main(String[] args){
     3         IVehicle car = new Car();
     4         IVhecile motorBike = new MotorBike();
     5         //new addition
     6         IVhecile truck = new Truck();
     7         double carValue       = getVehicleValue(car);
     8         double motorBikeValue = getVehicleValue(motorBike);
     9         double truckValue     = getVehicleValue(truck);
    10     }
    11     public double getVehicleValue(IVehicle v) {
    12         return v.calculateVehicle();
    13     }
    14 }

    SOLID的“L”

    L代表LSP(Liskov替代原理):

    为了使这篇文章成为SOLID的介绍,而不会引起混淆,我将尝试使LSP尽可能简单,并排除很多具体的细节,因为LSP又是另一天的讨论和辩论。

    LSP指出,当我们用任何子类型替换父类型时,该软件不应改变期望的结果。

    LSP不仅仅是一个设计模式,更是一个问题定义,而我们可以做的是防止不良影响。

    为了更清楚地说明这一点,我们将检查以下简单示例:

     1 /**
     2  * The Base Rectangle class
     3  * This class defines the structure and properties of all types of rectangles
     4  */
     5 public class Rectangle {
     6     private int width;
     7     private int height;
     8     public Rectangle(){}
     9     public Rectangle(int w,int h) {
    10         this.width = w;
    11         this.height = h;
    12     }
    13     public int getWidth() {
    14         return width;
    15     }
    16     public void setWidth(int width) {
    17         this.width = width;
    18     }
    19     public int getHeight() {
    20         return height;
    21     }
    22     public void setHeight(int height) {
    23         this.height = height;
    24     }
    25     public int getArea() {
    26         return this.height * this.width;
    27     }
    28     /**
    29      * LSP violation is case of a Square reference.
    30      */
    31     public final static void setDimensions(Rectangle r,int w,int h) {
    32           r.setWidth(w);
    33           r.setHeight(h);
    34           //assert r.getArea() == w * h
    35     }
    36 }
     1 /**
     2  * A Special kind of Rectangle
     3  */
     4 public class Square extends Rectangle {
     5     @Override
     6     public void setHeight(int h){
     7         super.setHeight(h);
     8         super.setWidth(h);
     9     }
    10     @Override
    11     public void setWidth(int w) {
    12         super.setWidth(w);
    13         super.setHeight(w);
    14     }
    15 }

     在谈论LSP时,我们在Rectangle类中有setDimensions方法,该方法接受Rectangle对象的类型并设置宽度和高度。这是违规的,因为行为发生了变化,并且在传递方形引用时我们的数据不一致。

    有很多解决方案。其中一些将应用“开放式封闭原则”和通过“合同”模式进行设计。

    还有许多其他解决LSP违规问题的方法,但是在此不做解释,因为它不在本文讨论范围之内。

     

    SOLID的“I”

    I代表ISP(接口隔离原理)。接口隔离原则是由Robert C. Martin在为Xerox咨询时定义的。他将其定义为:

    “不应强迫客户依赖他们不使用的接口。”

    ISP指出,我们应该将接口拆分为更小,更具体的接口。

    以下是代表两个不同角色的界面示例。一个角色是处理打开和关闭之类的连接,另一个角色是发送和接收数据。

    1 public interface Connection {
    2     void open();
    3     void close();
    4     byte[] receive();
    5     void send(byte[] data);  
    6 }

     在应用ISP之后,我们得到了两个不同的接口,每个接口代表一个确切的角色。

    1 public interface Channel {
    2     byte[] receive();
    3     void send(byte[] data);  
    4 }
    5 public interface Connection {
    6     void open();
    7     void close();  
    8 }

    SOLID的“D”

    D代表DIP(依赖性反转原理)。DIP声明我们应该依赖抽象(接口和抽象类),而不是具体的实现(类)。

    接下来是违反DIP。我们有一个Emailer类,具体取决于直接的SpellChecker类:

    1 public class Emailer{
    2     private SpellChecker spellChecker;    
    3     public Emailer(SpellChecker sc) {
    4         this.spellChecker = sc;  
    5     }
    6     public void checkEmail() {
    7         this.spellChecker.check();
    8     }
    9 }

    Spellchecker类:

    1 public class SpellChecker {
    2     public void check() throws SpellFormatException {
    3     }  
    4 }

    目前可能可以使用,但是过了一会儿,我们要包含两种不同的SpellChecker实现。我们有默认的SpellChecker和新greek spellchecker

    在当前的实现中,需要重构,因为Emailer类仅使用SpellChecker类。

    一个简单的解决方案是为不同的SpellChecker创建要实现的接口。

    1 // The interface to be implemented by any new spell checker.
    2 public interface ISpellChecker {
    3     void check() throws SpellFormatException; 
    4 }

     现在,Emailer类在构造函数上仅接受ISpellChecker引用。下面,我们将Emailer类更改为不关心/不依赖于实现(具体的类),而是依赖于接口(ISpellChecker

    1 public class Emailer{
    2     private ISpellChecker spellChecker;    
    3     public Emailer(ISpellChecker sc) {
    4         this.spellChecker = sc;  
    5     }
    6     public void checkEmail() {
    7         this.spellChecker.check();
    8     }
    9 }

    我们为ISpellChecker提供了许多实现:

     1 public class SpellChecker implements ISpellChecker {
     2     @Override
     3     public void check() throws SpellFormatException {
     4     }  
     5 }
     6 public class GreekSpellChecker implements ISpellChecker {
     7     @Override
     8     public void check() throws SpellFormatException {
     9     }  
    10 }

     这是另一个代码示例。无论实现是什么,我们都将ISpellChecker类型传递给Emailer构造函数。

    1 public static class Main{
    2     public static void main(String[] a) {
    3         ISpellChecker defaultChecker = new SpellChecker();
    4         ISpellChecker greekChecker = new GreekSpellChecker();
    5         new Emailer(defaultChecker).checkEmail();
    6         new Emailer(greekChecker).checkEmail();
    7     }
    8 }

     就是这样!希望你喜欢Java代码中SOLID设计原理的简单概述。

    【优锐课】Java学习资料交流qq群:907135806,在接下来的学习如果过程中有任何疑问,欢迎进群探讨。也可以添加vx:ddmsiqi,有更多JVM、Mysql、Tomcat、Spring Boot、Spring Cloud、Zookeeper、Kafka、RabbitMQ、RockerMQ、Redis、ELK、Git等Java学习资料和视频课程干货分享!
  • 相关阅读:
    【1801日語听解4】第14回:6月9日
    【日語听解2】第14回:6月8日
    【日語視聴説2】第14回:6月8日
    【日本語新聞編集】第13回:6月5日
    【1801日語写作】第13回:6月4日
    【日本語新聞選読】第13回:6月2日
    Win10版本
    家庭或小规模无线使用方
    批处理bat复制命令Copy与Xcopy
    批处理bat删除命令
  • 原文地址:https://www.cnblogs.com/youruike-/p/12033810.html
Copyright © 2011-2022 走看看