欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-05-29 09:23:59
看看这篇针对Java开发人员的SOLID设计原则简介。抽丝剥茧,细说架构那些事——【优锐课】 当你刚接触软件工程时,这些原理和设计模式不容易理解或习惯。我们都遇到了问题,很难理解SOLID + DP的思想,甚至很难正确实施它们。确实,“为什么要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设计原理的简单概述。