# 設計模式的七大原則
23種設計模式遵循的原則
1. 單一職責原則。
2. 接口隔離原則。
3. 依賴倒轉(倒置)原則。
4. 里氏替換原則。
5. 開閉原則ocp。
6. 迪米特法原則。
7. 合成復用原則。
達到目的:
1. 代碼重用性:相同功能的代碼,不用多次編寫。
2. 可讀性:編程規范,便于其他程序員閱讀。
3. 可擴展性:添加新功能時方便。
4. 可靠性:添加新功能后對原功能沒有影響。
5. 高內聚,低耦合。
## 單一職責原則
單一職責原則指的是對類來說,一個類只負責一項職責,例如類A負責兩個不同的職責1和職責2,修改職責1的代碼會導致職責2執行出錯,這個時候就要將類A分解成類職責1和類職責2(粒度分解為A1和A2)。也可以從方法的級別分解不同的職責。
舉例如下:
`SingleReposibility1.java`
~~~
?zpackage cn.net.smrobot.design_principle;
??
?public class SingleResponsibility1 {
? ? ?public static void main(String[] args) {
? ? ? ? ?Vehicle vehicle = new Vehicle();
? ? ? ? ?vehicle.run("公交");
? ? ? ? ?vehicle.run("汽車");
? ? ? ? ?vehicle.run("飛機");
? ? }
??
?}
?/**
? * 方式1:這種方法違反了單一職責原則
? * 例如傳入“飛機”等不應該用run來執行
? * 解決方法:根據不同的交通工具,分解成不同的類
? */
?class Vehicle {
? ? ?public void run(String vehicle) {
? ? ? ? ?System.out.println(vehicle + "在公路上跑");
? ? }
?}
? ?
~~~
這種方式導致不同種類的對象調用了同一個方法,例如飛機并不能適用于run方法。
> 改進方式:根據不同的交通工具,分解成不同的工具類
`SingleResponsibility2.java`
~~~
?package cn.net.smrobot.design_principle;
??
?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("潛艇");
? ? }
?}
??
?/**
? * 方案二:遵守了單一職責原則
? * 但是改動大,將類分解的同時修改了客戶端
? * 改進:直接修改Vehicle類,而不修改其他類
? */
?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 + "在水里游...");
? ? }
?}
~~~
這種方式就遵循了`類級別`的單一職責原則,但是由于修改添加了太多的類,改動太大。
> 改進方法:直接修改Vehicle類,而不用添加其他類
`SingleResponsibility3.java`
~~~
?package cn.net.smrobot.design_principle;
??
?public class SingleResponsibility3 {
? ? ?public static void main(String[] args) {
??
? ? }
?}
?/**
? * 方式3:沒有對原來的類進行大修改,只是增加了方法
? * 類級別上遵守單一職責原則,但是在方法上遵守了單一職責原則
? */
?class Vehicle1 {
? ? ?public void run(String vehicle) {
? ? ? ? ?System.out.println(vehicle + "在公路上跑");
? ? }
??
? ? ?public void runAir(String vehicle) {
? ? ? ? ?System.out.println(vehicle + "在天空飛...");
? ? }
??
? ? ?public void runWater(String vehicle) {
? ? ? ? ?System.out.println(vehicle + "在水里游...");
? ? }
??
?}
~~~
這種方法通過增加方法的方式,在`方法級別`上遵守了單一職責原則,但是類級別上遵守單一職責原則.
**總結**
1. 單一職責原則可以降低類的復雜度。
2. 提高代碼的可讀性,降低變更代碼引起的風險。
3. 如果類的邏輯簡單,類中方法數量足夠上,可以在方法級別上保證單一職責原則(`靜態工具類`)。
## 接口隔離原則
Interface Segregation Principle
接口隔離原則指的是客戶端不應該依賴它不需要的接口,即一個類對另一個類的依賴應該建立在最小的接口上。
大致意思就是當一個類實現一個接口時,如果接口中的一些方法并不會通過該類的依賴被使用到,那么就不要去實現該接口的所有方法了,而是將該接口分解成多個更小的接口,使得實現這些接口的中的方法都會被使用到。
舉例:
`InterfaceSegregation1.java`
~~~
?package cn.net.smrobot.design_principle.interfaceSegregation;
??
?/**
? * 接口隔離原則
? */
?public class InterfaceSegregation1 {
? ? ?public static void main(String[] args) {
??
? ? }
?}
??
?interface Interface1 {
? ? ?void method1();
??
? ? ?void method2();
??
? ? ?void method3();
??
? ? ?void method4();
??
? ? ?void method5();
?}
??
?/**
? * B實現接口
? */
?class B implements Interface1 {
? ? ?@Override
? ? ?public void method1() {
? ? ? ? ?System.out.println("B:method1");
? ? }
? ? ?@Override
? ? ?public void method2() {
? ? ? ? ?System.out.println("B:method2");
? ? }
? ? ?@Override
? ? ?public void method3() {
? ? ? ? ?System.out.println("B:method3");
? ? }
? ? ?@Override
? ? ?public void method4() {
? ? ? ? ?System.out.println("B:method4");
? ? }
? ? ?@Override
? ? ?public void method5() {
? ? ? ? ?System.out.println("B:method5");
? ? }
?}
??
?/**
? * D也實現接口
? */
?class D implements Interface1{
? ? ?@Override
? ? ?public void method1() {
? ? ? ? ?System.out.println("D:method1");
? ? }
? ? ?@Override
? ? ?public void method2() {
? ? ? ? ?System.out.println("D:method2");
? ? }
? ? ?@Override
? ? ?public void method3() {
? ? ? ? ?System.out.println("D:method3");
? ? }
? ? ?@Override
? ? ?public void method4() {
? ? ? ? ?System.out.println("D:method4");
? ? }
? ? ?@Override
? ? ?public void method5() {
? ? ? ? ?System.out.println("D:method5");
? ? }
?}
??
?/**
? * A類通過接口依賴(使用)B類,但只會使用其中的1,2,3方法
? */
?class A {
? ? ?public void depend1(Interface1 interface1) {
? ? ? ? ?interface1.method1();
? ? }
? ? ?public void depend2(Interface1 interface1) {
? ? ? ? ?interface1.method2();
? ? }
? ? ?public void depend3(Interface1 interface1) {
? ? ? ? ?interface1.method3();
? ? }
?}
??
?/**
? * C 類通過接口依賴類D,但是只是會使用其中的1,4,5個方法
? */
?class C {
? ? ?public void depend1(Interface1 interface1) {
? ? ? ? ?interface1.method1();
? ? }
? ? ?public void depend2(Interface1 interface1) {
? ? ? ? ?interface1.method4();
? ? }
? ? ?public void depend3(Interface1 interface1) {
? ? ? ? ?interface1.method5();
? ? }
?}
??
~~~
> 因此需要將接口拆分成多個子接口
## 依賴倒轉原則
`Dependence Inversion Principle`
> 1. 高層模塊不要依賴底層模塊,二者都應該依賴其抽象
>
> 2. 抽象不應該依賴細節,細節應該依賴抽象
>
> 3. 中心思想就是面向接口編程
>
>
> 在java中,抽象指的是接口或者是抽象類,細節是對接口或抽象的實現
~~~
?public class DependenceInversion1 {
? ? ?public static void main(String[] args) {
? ? ? ? ?Person person = new Person();
? ? ? ? ?person.receive(new Email());
? ? }
?}
??
?class Email {
? ? ?public String getInfo() {
? ? ? ? ?return "電子郵件信息:hello,world!";
? ? }
?}
??
?/**
? * 依賴的方法1:
? * 缺點:如果是其他信息來源(WeiXin),receive方法將不能使用
? * 改進方法:引入一個抽象的接口IReceive,表示接受者,讓Person與接口IReceive發生依賴
? */
?class Person {
? ? ?public void receive(Email email) {
? ? ? ? ?System.out.println(email.getInfo());
? ? }
?}
~~~
> Email參數設計為傳遞接口的方式
~~~
?public class DependenceInversion2 {
? ? ?public static void main(String[] args) {
? ? ? ? ?Person person = new Person();
? ? ? ? ?person.receive(new Email());
? ? }
?}
??
?// 定義一個接受者接口
?interface IReceive{
? ? ?String getInfo();
?}
?// 實現接口
?class Email1 implements IReceive{
? ? ?@Override
? ? ?public String getInfo() {
? ? ? ? ?return "電子郵件信息:hello,world!";
? ? }
?}
??
?class WeiXin implements IReceive {
??
? ? ?@Override
? ? ?public String getInfo() {
? ? ? ? ?return "微信信息:hello,world!";
? ? }
?}
??
?/**
? * 依賴的方法2:將接口作為參數傳遞
? * 依賴接口更加抽象,穩定性更好
? */
?class Person1 {
? ? ?public void receive(IReceive iReceive) {
? ? ? ? ?System.out.println(iReceive.getInfo());
? ? }
?}
~~~
依賴關系的實現:
1. 通過接口傳遞實現依賴 - 多態
`上面的例子`
2. 通過構造方法實現依賴
~~~
?interface IReceive {
? ? ?String getInfo();
?}
?class Person {
? ? ?private IReceive iReceive;
? ? ?public Person(IReceive iReceive) { // 通過構造器傳遞一個已經實現好的接口實現類
? ? ? ? ?this.iReceive = iReceive;
? ? }
?}
~~~
3. 通過set方法實現依賴
~~~
?interface IReceive {
? ? ?String getInfo();
?}
?class Person {
? ? ?private IReceive iReceive;
? ? ?// 通過set方法傳遞一個已經實現了接口的實現類
? ? ?public setIReceive(IReceive iReceive) {
? ? ? ? ?this.iReceive = iReceive;
? ? }
?}
~~~
總結:
1. 底層模塊盡量都要有抽象類或接口,會使程序的穩定性更好
2. 變量聲明類型盡量使用抽象類或接口,這樣我們在變量引用和實際對象間,就存在一個緩沖區,有利于程序擴展和優化
3. 繼承時遵循里氏替換原則
## 里氏替換原則
面向對象中繼承體系的問題:
1. 增加程序之間的耦合性;
2. 如果一個父類被修改了,就要考慮其所有子類的功能,并且可能會導致所有子類的功能都故障;
`如何正確使用繼承` = `盡量滿足里氏替換原則`
> 如果對每個類型為T1的對象o1,都有類型T2的對象o2,使得在程序中用o2代替o1而不會對程序造成任何印象,即引用基類的地方必須能透明的使用其子類對象
* 在繼承時,子類盡量不要重寫父類的方法
* 在繼承中實際上是程序的耦合性增高,在適當情況下,可以通過聚合,組合,依賴來解決問題。
~~~
?package cn.net.smrobot.design_principle.liskov;
??
?/**
? * 里氏替換原則
? */
?public class Liskov1 {
? ? ?public static void main(String[] args) {
? ? ? ? ?A a = new A();
? ? ? ? ?System.out.println(a.fun1(10, 2));
? ? ? ? ?System.out.println("------------------");
? ? ? ? ?B b = new B();
? ? ? ? ?System.out.println(b.fun1(10, 2));
? ? ? ? ?System.out.println(b.fun2(10, 2));
? ? }
??
??
?}
??
?class A {
? ? ?public int fun1(int a, int b) {
? ? ? ? ?return a - b;
? ? }
?}
??
?class B extends A {
? ? ?@Override
? ? ?public int fun1(int a, int b) {
? ? ? ? ?return a + b;
? ? }
? ? ?public int fun2(int a, int b) {
? ? ? ? ?return fun1(a, b) + 9;
? ? }
?}
~~~
這里的fun1的方法被重寫了,可能導致想要的功能不準確。
> 在實際編程過程中,我們常常會通過重寫父類的方法完成新的功能,這樣寫會導致整個繼承體系的復用性差,特別是在運行多態比較頻繁的情況下。
>
> 解決方法:取出原有的繼承關系,抽象一個更加基礎的基類。
修改方案:
~~~
?package cn.net.smrobot.design_principle.liskov;
??
?public class Liskov2 {
? ? ?public static void main(String[] args) {
??
? ? }
?}
??
?// 創建一個更加基礎的類
?class Base {
??
?}
??
?class A1 extends Base{
? ? ?public int fun1(int a, int b) {
? ? ? ? ?return a - b;
? ? }
?}
??
?// B類不在繼承A類,如果B類要用到A類,可以用依賴,聚合等關系來替換
?class B1 extends Base{
? ? ?//B類使用A類中的方法
? ? ?private A1 a1 = new A1();
??
? ? ?public int fun1(int a, int b) {
? ? ? ? ?return a + b;
? ? }
? ? ?public int fun2(int a, int b) {
? ? ? ? ?return fun1(a, b) + 9;
? ? }
??
? ? ?// 用到A類中的方法
? ? ?public int fun3(int a, int b) {
? ? ? ? ?return a1.fun1(a, b);
? ? }
?}
??
~~~
總結:
1. 繼承會帶來程序的入侵性,降低程序的可移植性。
2. 里氏替換原則用來指導正確的使用繼承關系。
## 開閉原則
Open Closed Principle
開閉原則指的是,模塊和函數應該對擴展開放(提供方),對修改關閉(使用方),用抽象構建框架,用實現擴展細節。
當軟件需要變化時,盡量`通過擴展`軟件的實體行為來實現變化,而不是通過修改已有的代碼來實現變化。
**其他原則是為了實現開閉原則**
~~~
?package cn.net.smrobot.design_principle.opendClose;
??
?/**
? * 繪圖類
? */
?public class Ocp1 {
? ? ?public static void main(String[] args) {
? ? ? ? ?GraphicEditor graphicEditor = new GraphicEditor();
? ? ? ? ?graphicEditor.drawShape(new Rectangle());
? ? ? ? ?graphicEditor.drawShape(new Circle());
? ? }
?}
??
?/**
? * 繪制類
? * 使用方
? * 當添加繪制一個新的圖形時需要在使用方這里修改代碼
? */
?class GraphicEditor {
??
? ? ?public void drawShape(Shape shape) {
? ? ? ? ?if (shape.type == 1) {
? ? ? ? ? ? ?drawRectangle();
? ? ? ? }
? ? ? ? ?if (shape.type == 2) {
? ? ? ? ? ? ?drawCircle();
? ? ? ? }
??
? ? }
? ? ?public void drawRectangle() {
? ? ? ? ?System.out.println("繪制矩形");
? ? }
??
? ? ?public void drawCircle() {
? ? ? ? ?System.out.println("繪制圓形");
? ? }
??
?}
??
?class Shape {
? ? ?public int type;
?}
??
?class Rectangle extends Shape {
? ? ?public Rectangle() {
? ? ? ? ?this.type = 1;
? ? }
?}
??
?class Circle extends Shape {
? ? ?public Circle() {
? ? ? ? ?this.type = 2;
? ? }
?}
~~~
> 改進思路:可以將Shape類改成抽象類,然后讓子類重寫各自的實現。
~~~
?package cn.net.smrobot.design_principle.opendClose;
??
?/**
? * 繪圖類
? */
?public class Ocp1 {
? ? ?public static void main(String[] args) {
? ? ? ? ?GraphicEditor graphicEditor = new GraphicEditor();
? ? ? ? ?graphicEditor.drawShape(new Rectangle());
? ? ? ? ?graphicEditor.drawShape(new Circle());
? ? }
?}
??
?/**
? * 繪制類
? * 直接調用draw方法,多態的思想
? */
?class GraphicEditor {
??
? ? ?public void drawShape(Shape shape) {
? ? ? ? ?shape.draw();
? ? }
? ? ?
?}
??
?abstract class Shape {
? ? ?public int type;
? ? ?
? ? ?protected void draw();
?}
??
?class Rectangle extends Shape {
? ? ?public Rectangle() {
? ? ? ? ?this.type = 1;
? ? }
? ? ?
? ? ?@Override
? ? ?public void draw() {
? ? ? ? ?System.out.println("繪制矩形");
? ? }
?}
??
?class Circle extends Shape {
? ? ?public Circle() {
? ? ? ? ?this.type = 2;
? ? }
? ? ?
? ? ?@Override
? ? ?public void draw() {
? ? ? ? ?System.out.println("繪制圓形");
? ? }
?}
??
?class Triangle extends Shape {
? ? ?public Circle() {
? ? ? ? ?this.type = 2;
? ? }
? ? ?
? ? ?@Override
? ? ?public void draw() {
? ? ? ? ?System.out.println("繪制三角形");
? ? }
?}
??
~~~
總結:
1. 對于使用方,盡量不要修改提供方的內容,而是擴展提供方的內容。
2. 盡量使用的抽象的方式來修改程序的功能。
## 迪米特法則
Demeter Principle
> 1. 一個對象應該對其他對象保持最少的了解。
>
> 2. 又叫最少知道原則,即一個類對自己依賴的類知道得越少越好,即一個類盡量進行封裝。
>
> 3. 更簡單的定義:只和直接朋友通信。
>
直接朋友:
將出現在`成員變量,方法參數,方法返回值`中的類稱為直接朋友,而以局部變量的方式出現的類不是直接朋友,因此別的類盡量不要以局部變量的方式出現在類的內部。
總計:
1. 迪米特法則用來降低類之間的耦合關系。
2. 只是盡量減少類之間的依賴關系,而不是完全不依賴。
## 合成復用原則
> 盡量使用合成或者聚合的方式,而不是使用繼承的方式
## 總結
1. 找出應用中可能需要變化之處,把它們獨立出來,不要和那些不需要變化的代碼混合在一起。
2. 面向接口編程,而不是面向實現編程。
3. 主要目的是讓代碼達到松耦合。
## 類之間的關系
UML Unified Modeling Language 統一建模語言,是一套幫助軟件系統分析和設計的語言工具。
由一系列符號(類似于數學符號)和圖案組成用來表示**類之間的關系**。
由如下幾種關系:
1. Dependency 依賴
2. Association 關聯
3. Generalization 泛化/繼承
4. Realization 實現
5. Aggregation 聚合關系,關聯的一種,通過set方法引入
6. Composite 組合關系,關聯關系的一種,通過成員屬性A a = new A();的方法引入
**類圖分類**
1. 用例圖 use case
2. 靜態結構圖:`類圖`,對象圖,包圖,組件圖,部署圖
3. 動態行為圖:交互圖,狀態圖,活動圖
## 依賴關系
只要在類中用到了對方,他們之間就存在依賴關系。
:-: 
使用的位置:
1. 類的成員屬性
2. 方法的返回類型
3. 方法接收的參數類型
4. 方法中的局部變量。
## 泛化和實現關系
泛化關系:繼承關系,也是依賴關系的一種特例。
:-: 
實現關系:即A類實現B接口,也是依賴關系的一種特例。
:-: 
## 關聯關系
類與類之間的關系,是依賴關系的一種特例。
關聯關系具有導航性:即雙向關系或單向關系。
~~~
?// 單向一對一關系
?public class Person() {
? ? ?private IDCard card;
?}
?public class IDCard() {}
??
?// 雙向一對一關系
?public class Person() {
? ? ?private IDCard card;
?}
?public class IDCard() {
? ? ?private Person person;
?}
~~~
## 聚合關系
表示整體和部分的關系,`整體和部分可以分開`,聚合關系是關聯關系的特例。具有導航性和多重性。`對象的引入通過set方法或者構造方法`實現。
~~~
?// 鼠標類和顯示器類都可以和Computer類分開,因此是聚合關系
?public class Computer{
? ? ?private Mouse mouse;
? ? ?
? ? ?private Monitor monitor;
? ? ?
? ? ?// set方法
? ? ?
?}
~~~
:-: 
## 組合關系
整體和部分是不可以分開的稱為組合關系,對象的引入通過直接new實現。
~~~
?// 鼠標類和顯示器類在Computer一創建就會被創建,因此是不可分割的,屬于組合關系
?public class Computer{
? ? ?private Mouse mouse = new Mouse();
? ? ?
? ? ?private Monitor monitor = new Mouse();
? ? ?
?}
~~~
:-: 
聚合關系和組合關系是可以混合使用,主要是看整體和部分是否可以分開。
> 如果Person類中定義了對IDCard的級聯刪除,那么Person類和IDCard類就是組合關系。不一定直接使用new才是組合關系。
- 第一章 Java基礎
- ThreadLocal
- Java異常體系
- Java集合框架
- List接口及其實現類
- Queue接口及其實現類
- Set接口及其實現類
- Map接口及其實現類
- JDK1.8新特性
- Lambda表達式
- 常用函數式接口
- stream流
- 面試
- 第二章 Java虛擬機
- 第一節、運行時數據區
- 第二節、垃圾回收
- 第三節、類加載機制
- 第四節、類文件與字節碼指令
- 第五節、語法糖
- 第六節、運行期優化
- 面試常見問題
- 第三章 并發編程
- 第一節、Java中的線程
- 第二節、Java中的鎖
- 第三節、線程池
- 第四節、并發工具類
- AQS
- 第四章 網絡編程
- WebSocket協議
- Netty
- Netty入門
- Netty-自定義協議
- 面試題
- IO
- 網絡IO模型
- 第五章 操作系統
- IO
- 文件系統的相關概念
- Java幾種文件讀寫方式性能對比
- Socket
- 內存管理
- 進程、線程、協程
- IO模型的演化過程
- 第六章 計算機網絡
- 第七章 消息隊列
- RabbitMQ
- 第八章 開發框架
- Spring
- Spring事務
- Spring MVC
- Spring Boot
- Mybatis
- Mybatis-Plus
- Shiro
- 第九章 數據庫
- Mysql
- Mysql中的索引
- Mysql中的鎖
- 面試常見問題
- Mysql中的日志
- InnoDB存儲引擎
- 事務
- Redis
- redis的數據類型
- redis數據結構
- Redis主從復制
- 哨兵模式
- 面試題
- Spring Boot整合Lettuce+Redisson實現布隆過濾器
- 集群
- Redis網絡IO模型
- 第十章 設計模式
- 設計模式-七大原則
- 設計模式-單例模式
- 設計模式-備忘錄模式
- 設計模式-原型模式
- 設計模式-責任鏈模式
- 設計模式-過濾模式
- 設計模式-觀察者模式
- 設計模式-工廠方法模式
- 設計模式-抽象工廠模式
- 設計模式-代理模式
- 第十一章 后端開發常用工具、庫
- Docker
- Docker安裝Mysql
- 第十二章 中間件
- ZooKeeper