目錄
- 第一章 遵守原則介紹
- 第二章 單一職責原則
- 2.1、原則介紹
- 2.2、錯誤示范
- 2.3、正確示范
- 第三章 開放封閉原則
- 3.1、原則介紹
- 3.2、錯誤示范
- 3.3、正確示范
- 第四章 里氏替換原則
- 4.1、原則介紹
- 4.2、錯誤示范
- 4.3、正確示范
- 第五章 介面隔離原則
- 5.1、原則介紹
- 5.2、錯誤示范
- 5.3、正確示范
- 第六章 依賴倒轉原則
- 6.1、原則介紹
- 6.2、錯誤示范
- 6.3、正確示范
- 第七章 其他兩大原則
- 7.1、合成復用原則
- 7.2、最少知識原則
專案地址:https://gitee.com/caochenlei/design-pattern
第一章 遵守原則介紹
撰寫軟體程序中,程式員面臨著來自耦合性,內聚性以及可維護性、可擴展性、重用性、靈活性等多方面的挑戰,設計模式是為了讓程式,具有更好代碼重用性、可讀性、可擴展性、可靠性,使程式呈現高內聚,低耦合的特性,設計模式原則,其實就是程式員在編程時,應當遵守的原則,也是各種設計模式的基礎,即設計模式為什么這樣設計的依據,
在程式設計領域,SOLID(單一職責、開閉原則、里氏替換、介面隔離以及依賴反轉)是由羅伯特·C·馬丁在21世紀早期引入的記憶術首字母縮略字,指代了面向物件編程和面向物件設計的五個基本原則,當這些原則被一起應用時,它們使得一個程式員開發一個容易進行軟體維護和擴展的系統變得更加可能,SOLID所包含的原則是通過引發編程者進行軟體源代碼的代碼重構進行軟體的代碼異味清掃,從而使得軟體清晰可讀以及可擴展時可以應用的指南,
除了以上五個經典的原則以外,其實還有兩個常被提到的原則:合成復用原則、最少知識原則(又稱迪米特法則),
第二章 單一職責原則
2.1、原則介紹
單一職責原則(single responsibility principle): 對類來說,即一個類應該只負責一項職責,如類 A 負責兩個不同職責:職責 1,職責 2,當職責 1 需求變更而改變 A 時,可能造成職責 2 執行錯誤,所以需要將類 A 的粒度分解為 A1,A2,
單一職責原則可以降低類的復雜度,一個類只負責一項職責,提高類的可讀性,可維護性,降低變更引起的風險,
2.2、錯誤示范
public class SingleResponsibility1 {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
vehicle.run("汽車");
vehicle.run("飛機");
vehicle.run("輪船");
}
}
class Vehicle {
public void run(String vehicle) {
System.out.println(vehicle + " 在公路運行...");
}
}
汽車 在公路運行...
飛機 在公路運行...
輪船 在公路運行...
2.3、正確示范
public class SingleResponsibility2 {
public static void main(String[] args) {
RoadVehicle roadVehicle = new RoadVehicle();
roadVehicle.run("汽車");
AirVehicle airVehicle = new AirVehicle();
airVehicle.run("飛機");
WaterVehicle waterVehicle = new WaterVehicle();
waterVehicle.run("輪船");
}
}
class RoadVehicle {
public void run(String vehicle) {
System.out.println(vehicle + " 在公路運行...");
}
}
class AirVehicle {
public void run(String vehicle) {
System.out.println(vehicle + " 在天空運行...");
}
}
class WaterVehicle {
public void run(String vehicle) {
System.out.println(vehicle + " 在水上運行...");
}
}
汽車 在公路運行...
飛機 在天空運行...
輪船 在水上運行...
第三章 開放封閉原則
3.1、原則介紹
開放封閉原則(open closed principle): 在面向物件編程領域中,開閉原則規定“軟體中的物件(類,模塊,函式等等)應該對于擴展是開放的,但是對于修改是封閉的”,這意味著一個物體是允許在不改變它的源代碼的前提下變更它的行為,該特性在產品化的環境中是特別有價值的,在這種環境中,改變源代碼需要代碼審查,單元測驗以及諸如此類的用以確保產品使用質量的程序,遵循這種原則的代碼在擴展時并不發生改變,因此無需上述的程序,
3.2、錯誤示范
public class OpenClosed1 {
public static void main(String[] args) {
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Rectangle());
graphicEditor.drawShape(new Circle());
graphicEditor.drawShape(new Triangle());//+新增繪制三角形
}
}
//這是一個用于繪圖的類
class GraphicEditor {
//接收 Shape 物件,然后根據 type 來繪制不同的圖形
public void drawShape(Shape s) {
if (s.m_type == 1)
drawRectangle(s);
else if (s.m_type == 2)
drawCircle(s);
else if (s.m_type == 3)//+新增繪制三角形
drawTriangle(s);
}
//繪制矩形
public void drawRectangle(Shape r) {
System.out.println("繪制矩形");
}
//繪制圓形
public void drawCircle(Shape r) {
System.out.println("繪制圓形");
}
//+新增繪制三角形
public void drawTriangle(Shape r) {
System.out.println("繪制三角形");
}
}
class Shape {
int m_type;
}
//以前就寫好的
class Rectangle extends Shape {
Rectangle() {
super.m_type = 1;
}
}
//以前就寫好的
class Circle extends Shape {
Circle() {
super.m_type = 2;
}
}
//+新增繪制三角形
class Triangle extends Shape {
Triangle() {
super.m_type = 3;
}
}
繪制矩形
繪制圓形
繪制三角形
3.3、正確示范
public class OpenClosed2 {
public static void main(String[] args) {
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Rectangle());
graphicEditor.drawShape(new Circle());
graphicEditor.drawShape(new Triangle());//+新增繪制三角形
}
}
//這是一個用于繪圖的類
class GraphicEditor {
//接收 Shape 物件,呼叫 draw 方法
public void drawShape(Shape s) {
s.draw();
}
}
abstract class Shape {
int m_type;
public abstract void draw();
}
//以前就寫好的
class Rectangle extends Shape {
Rectangle() {
super.m_type = 1;
}
@Override
public void draw() {
System.out.println("繪制矩形");
}
}
//以前就寫好的
class Circle extends Shape {
Circle() {
super.m_type = 2;
}
@Override
public void draw() {
System.out.println("繪制圓形");
}
}
//+新增繪制三角形
class Triangle extends Shape {
Triangle() {
super.m_type = 3;
}
@Override
public void draw() {
System.out.println("繪制三角形");
}
}
繪制矩形
繪制圓形
繪制三角形
第四章 里氏替換原則
4.1、原則介紹
里氏替換原則(liskov substitution principle): 子類可以擴展父類的功能,但不能改變原有父類的功能,
繼承包含這樣一層含義:父類中凡是已經實作好的方法,實際上是在設定規范和契約,雖然它不強制要求所有的子類必須遵循這些契約,但是如果子類對這些已經實作的方法任意修改,就會對整個繼承體系造成破壞,
繼承在給程式設計帶來便利的同時,也帶來了弊端,比如使用繼承會給程式帶來侵入性,程式的可移植性降低,增加物件間的耦合性,如果一個類被其他的類所繼承,則當這個類需要修改時,必須考慮到所有的子類,并且父類修改后,所有涉及到子類的功能都有可能產生故障,
在使用繼承時,遵循里氏替換原則,在子類中盡量不要重寫父類的方法,繼承實際上讓兩個類耦合性增強了,在適當的情況下,可以通過聚合、組合、依賴來
解決問題,
4.2、錯誤示范
public class LiskovSubstitution1 {
public static void main(String[] args) {
CalcCommon calcCommon = new CalcCommon();
calcCommon.add(100, 20);
calcCommon.sub(100, 20);
System.out.println("--------------------");
SubCalcCommon subCalcCommon = new SubCalcCommon();
subCalcCommon.add(100, 20);
subCalcCommon.sub(100, 20);
}
}
class CalcCommon {
//通用加法運算
public void add(int a, int b) {
System.out.println(a + "+" + b + "=" + (a + b));
}
//通用減法運算
public void sub(int a, int b) {
System.out.println(a + "-" + b + "=" + (a - b));
}
}
class SubCalcCommon extends CalcCommon {
//子類重寫父類的方法可能會改變父類方法的意思,從而導致繼承體系崩潰
@Override
public void add(int a, int b) {
System.out.println(a + "*" + b + "=" + (a * b));
}
//子類重寫父類的方法可能會改變父類方法的意思,從而導致繼承體系崩潰
@Override
public void sub(int a, int b) {
System.out.println(a + "/" + b + "=" + (a / b));
}
}
100+20=120
100-20=80
--------------------
100*20=2000
100/20=5
4.3、正確示范
public class LiskovSubstitution2 {
public static void main(String[] args) {
CalcCommon calcCommon = new CalcCommon();
calcCommon.add(100, 20);
calcCommon.sub(100, 20);
System.out.println("--------------------");
SubCalcCommon subCalcCommon = new SubCalcCommon();
subCalcCommon.add(100, 20);
subCalcCommon.sub(100, 20);
subCalcCommon.mul(100, 20);
subCalcCommon.div(100, 20);
}
}
class CalcCommon {
//通用加法運算
public void add(int a, int b) {
System.out.println(a + "+" + b + "=" + (a + b));
}
//通用減法運算
public void sub(int a, int b) {
System.out.println(a + "-" + b + "=" + (a - b));
}
}
class SubCalcCommon extends CalcCommon {
//子類特有乘法運算
public void mul(int a, int b) {
System.out.println(a + "*" + b + "=" + (a * b));
}
//子類特有除法運算
public void div(int a, int b) {
System.out.println(a + "/" + b + "=" + (a / b));
}
}
100+20=120
100-20=80
--------------------
100+20=120
100-20=80
100*20=2000
100/20=5
第五章 介面隔離原則
5.1、原則介紹
介面隔離原則(interface segregation principle): 客戶端不應該依賴它不需要的介面, 即一個類對另一個類的依賴應該建立在最小的介面上,
5.2、錯誤示范
//A、B總的介面
interface InterfaceAll {
void operation1();
void operation2();
void operation3();
}
//實作類A只用InterfaceAll中的operation1、operation2方法,所以實作兩個方法
class A implements InterfaceAll {
@Override
public void operation1() {
System.out.println("A 實作了 operation1...");
}
@Override
public void operation2() {
System.out.println("A 實作了 operation2...");
}
@Override
public void operation3() {
//A用不到,但是還需要空實作
}
}
//實作類B只用InterfaceAll中的operation1、operation3方法,所以實作兩個方法
class B implements InterfaceAll {
@Override
public void operation1() {
System.out.println("B 實作了 operation1...");
}
@Override
public void operation2() {
//B用不到,但是還需要空實作
}
@Override
public void operation3() {
System.out.println("B 實作了 operation3...");
}
}
5.3、正確示范
//介面Interface1
interface Interface1 {
void operation1();
}
//介面Interface2
interface Interface2 {
void operation2();
}
//介面Interface3
interface Interface3 {
void operation3();
}
//實作類A用Interface1中的operation1和Interface2中的operation2
class A implements Interface1, Interface2 {
@Override
public void operation1() {
System.out.println("A 實作了 operation1...");
}
@Override
public void operation2() {
System.out.println("A 實作了 operation2...");
}
}
//實作類B用Interface1中的operation1和Interface3中的operation3
class B implements Interface1, Interface3 {
@Override
public void operation1() {
System.out.println("B 實作了 operation1...");
}
@Override
public void operation3() {
System.out.println("B 實作了 operation3...");
}
}
第六章 依賴倒轉原則
6.1、原則介紹
依賴倒置原則(dependence inversion principle): 是程式要依賴于抽象介面,不要依賴于具體實作,簡單的說就是要求對抽象進行編程,不要對實作進行編程,這樣就降低了客戶與實作模塊間的耦合,
面向程序的開發,上層呼叫下層,上層依賴于下層,當下層劇烈變動時上層也要跟著變動,這就會導致模塊的復用性降低而且大大提高了開發的成本,
面向物件的開發很好的解決了這個問題,一般情況下抽象的變化概率很小,讓用戶程式依賴于抽象,實作的細節也依賴于抽象,即使實作細節不斷變動,只要抽象不變,客戶程式就不需要變化,這大大降低了客戶程式與實作細節的耦合度,
6.2、錯誤示范
public class DependenceInversion1 {
public static void main(String[] args) {
/**
* 當你功能需求明確時,我們實作了接收qq用戶資訊的方法
* 但是,在某一天,市場上突然qq沒有了,只有wx這一家了
* 你要是新增加wx用戶資訊接受的方法就必須修改receive
* receive(QQ qq) --》 receive(WX wx)
*/
Client1 client = new Client1();
client.receive(new QQ());
}
}
class WX {
public void getUserInfo(Integer uid) {
System.out.println("WX getUserInfo " + uid);
}
}
class QQ {
public void getUserInfo(Integer uid) {
System.out.println("QQ getUserInfo " + uid);
}
}
class Client1 {
public void receive(QQ qq) {
qq.getUserInfo(774908833);
}
}
QQ getUserInfo 774908833
6.3、正確示范
public class DependenceInversion2 {
public static void main(String[] args) {
Client2 client = new Client2();
client.receive(new QQImpl());
client.receive(new WXImpl());
}
}
interface IReceive {
public void getUserInfo(Integer uid);
}
class WXImpl implements IReceive {
public void getUserInfo(Integer uid) {
System.out.println("WX getUserInfo " + uid);
}
}
class QQImpl implements IReceive {
public void getUserInfo(Integer uid) {
System.out.println("QQ getUserInfo " + uid);
}
}
class Client2 {
public void receive(IReceive iReceive) {
iReceive.getUserInfo(774908833);
}
}
QQ getUserInfo 774908833
WX getUserInfo 774908833
第七章 其他兩大原則
7.1、合成復用原則
合成復用原則(Composite Reuse Principle,CRP)又叫組合/聚合復用原則(Composition/Aggregate Reuse Principle,CARP),它要求在軟體復用時,要盡量先使用組合或者聚合等關聯關系來實作,其次才考慮使用繼承關系來實作,
如果要使用繼承關系,則必須嚴格遵循里氏替換原則,合成復用原則同里氏替換原則相輔相成的,兩者都是開閉原則的具體實作規范,合成復用原則是通過將已有的物件納入新物件中,作為新物件的成員物件來實作的,新物件可以呼叫已有物件的功能,從而達到復用,
下面以汽車分類管理程式為例來介紹合成復用原則的應用,
分析:汽車按“動力源”劃分可分為汽油汽車、電動汽車等;按“顏色”劃分可分為白色汽車、黑色汽車和紅色汽車等,如果同時考慮這兩種分類,其組合就很多,圖 1 所示是用繼承關系實作的汽車分類的類圖,

圖1 用繼承關系實作的汽車分類的類圖
從圖 1 可以看出用繼承關系實作會產生很多子類,而且增加新的“動力源”或者增加新的“顏色”都要修改源代碼,這違背了開閉原則,顯然不可取,但如果改用組合關系實作就能很好地解決以上問題,其類圖如圖 2 所示,

圖2 用組合關系實作的汽車分類的類圖
7.2、最少知識原則
迪米特法則(Law of Demeter,LOD)又叫作最少知識原則(The Least Knowledge Principle),一個類對于其他類知道的越少越好,就是說一個物件應當對其他物件有盡可能少的了解,只和朋友通信,不和陌生人說話 ,
迪米特法則可以簡單說成:talk only to your immediate friends, 對于OOD來說,又被解釋為下面幾種方式:一個軟體物體應當盡可能少的與其他物體發生相互作用,每一個軟體單位對其他的單位都只有最少的知識,而且局限于那些與本單位密切相關的軟體單位,迪米特法則的初衷在于降低類之間的耦合,由于每個類盡量減少對其他類的依賴,因此,很容易使得系統的功能模塊功能獨立,相互之間不存在(或很少有)依賴關系,
迪米特法則不希望類之間建立直接的聯系,如果真的有需要建立聯系,也希望能通過它的友元類來轉達,因此,應用迪米特法則有可能造成的一個后果就是:系統中存在大量的中介類,這些類之所以存在完全是為了傳遞類之間的相互呼叫關系——這在一定程度上增加了系統的復雜度,
下面以明星與經紀人的關系實體來介紹最少知識原則的應用,
分析:明星由于全身心投入藝術,所以許多日常事務由經紀人負責處理,如與粉絲的見面會,與媒體公司的業務洽淡等,這里的經紀人是明星的朋友,而粉絲和媒體公司是陌生人,所以適合使用迪米特法則,其類圖如圖 1 所示,

圖1 明星與經紀人的關系圖
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/265453.html
標籤:其他
