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