适用于Java开发人员的SOLID设计原则简介

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

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

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

让SOLID 变得容易

在本文中,我将以尽量简单的术语,经过简单易懂的示例来教授SOLID的每一个字母。函数

SOLID的“S”

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

实施SRP以前

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

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

第二个是车辆服务的角色,该服务从存储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分为两个不一样的类:一个用于端点,另外一个用于服务。code

SRP实施后

咱们要作的是获取VehicleServiceResource类,并将其分为两个不一样的类。orm

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设计原理的简单概述。

相关文章
相关标签/搜索