1. 程式人生 > 其它 >01 設計模式原則

01 設計模式原則

設計模式

設計模式是對軟體設計中普遍存在(反覆出現)的各種問題,所提出的解決方案

一、 設計模式的目的:

設計模式是為了讓程式(軟體),具有更好

  1. 程式碼重用性(即:相同功能的程式碼,不用多次編寫)
  2. 可讀性(即:程式設計規範性,便於其他程式設計師的閱讀和理解)
  3. 可擴充套件性(即:當需要增加新的功能時,非常的方便)
  4. 可靠性(即:當我們在增加新的功能後,對原來的功能沒有影響)
  5. 使程式程式高內聚,低耦合的特性

二、設計模式的七大原則

  • 單一職責原則
  • 介面隔離原則
  • 依賴倒轉原則
  • 里氏替換原則
  • 開閉原則ocp
  • 迪米特法則
  • 合成複用原則

2.1 單一職責原則

基本介紹:

對類來說的,即一個類應該只負責一項職責,如類A負責兩個不同職責:職責1,職責2.當職責1需求變更而改變A時,可能造成職責2執行錯誤,所以需要將類A的粒度分解為A1,A2

public class SingleResponsibility1 {
    public static void main(String[] args) {
        Vehicle vehicle = new Vehicle();
        vehicle.run("摩托車");
        vehicle.run("飛機");
    }
}

class Vehicle{
    //1.在這個方法裡 違反了單一職責原則
    //2.解決方案:根據交通工具執行方法不同,分解成不同類即可
    public void run(String vehicle){
        System.out.println(vehicle + "在公路上執行");
    }
}

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("船");
    }

}

//方案2的分析
//1.遵守單一職責原則
//2.但是這樣改動很大,將類分解,同時修改客戶端
//2.改進:直接修改Vehicle類,改動的程式碼會比較少=>方案3
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+"在水上執行");
    }
}

public class SingleResponsibility3 {
    public static void main(String[] args) {
        Vehicle2 vehicle2 = new Vehicle2();
        vehicle2.run("車");
        vehicle2.runAir("飛機");
        vehicle2.run("船");
    }
}


//方案3的分析
//1.這種修改方法沒有對原來的類做大的修改,只是增加方法
//2.沒有在類這個級別上遵守單一職責原則,但是在方法上仍然遵守
class Vehicle2{

    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. 降低變更引起的風險
  4. 通常情況下,我們應當遵守單一職責原則,只有邏輯足夠簡單,才可以在程式碼違反單一職責原則;只有類中方法數量足夠少,可以在方法級別保持單一職責原則

2.2 介面隔離原則

基本介紹:

客戶端不應該依賴它不需要的介面,即一個類對另一類的依賴應該建立在最小的介面

類A通過Interface1 依賴類B,類C通過Interface1 依賴類D,如果介面Interface1 對於類A不是最小介面,那麼類B和類D必須去實現它們不需要

的方法

public class Segregation1 {

}

interface Interface1{
    void operation1();
    void operation2();
    void operation3();
    void operation4();
    void operation5();
}

class B implements Interface1{
    @Override
    public void operation1() {
        System.out.println("B實現了operation1");
    }

    @Override
    public void operation2() {
        System.out.println("B實現了operation2");
    }

    @Override
    public void operation3() {
        System.out.println("B實現了operation3");
    }

    @Override
    public void operation4() {
        System.out.println("B實現了operation4");

    }

    @Override
    public void operation5() {
        System.out.println("B實現了operation5");
    }
}

class D implements Interface1{
    @Override
    public void operation1() {
        System.out.println("D實現了operation1");
    }

    @Override
    public void operation2() {
        System.out.println("D實現了operation2");
    }

    @Override
    public void operation3() {
        System.out.println("D實現了operation3");
    }

    @Override
    public void operation4() {
        System.out.println("D實現了operation4");

    }

    @Override
    public void operation5() {
        System.out.println("B實現了operation5");
    }
}



class A{//A類 通過介面Interface1 依賴(使用)B類,但是隻會用到1,2,3方法
    public void depend1(Interface1 i){
        i.operation1();
    }
    public void depend2(Interface1 i){
        i.operation2();
    }
    public void depend3(Interface1 i){
        i.operation3();
    }
}

class C{//C類 通過介面Interface1 依賴(使用)D類,但是隻會用到1,4,5方法
    public void depend1(Interface1 i){
        i.operation1();
    }
    public void depend4(Interface1 i){
        i.operation4();
    }
    public void depend5(Interface1 i){
        i.operation5();
    }
}


按隔離原則應當這樣處理:

將Interface1 拆分成幾個介面,類A和類C分別與它們需要的介面建立依賴關係,也就是隔離原則

根據實際情況,拆分成3個介面

public class Segregation1 {

    public static void main(String[] args) {
        A a = new A();
        a.depend1(new B());//A類通過介面去依賴B類
        a.depend2(new B());

        C c = new C();
        c.depend1(new D());

    }
}

//介面1
interface Interface1{
    void operation1();
}
//介面2
interface Interface2{
    void operation2();
    void operation3();
}
//介面3
interface Interface3{
    void operation4();
    void operation5();
}

class B implements Interface1,Interface2{
    @Override
    public void operation1() {
        System.out.println("B實現了operation1");
    }

    @Override
    public void operation2() {
        System.out.println("B實現了operation2");
    }

    @Override
    public void operation3() {
        System.out.println("B實現了operation3");
    }

}

class D implements Interface1,Interface3{
    @Override
    public void operation1() {
        System.out.println("D實現了operation1");
    }

    @Override
    public void operation4() {
        System.out.println("D實現了operation4");

    }

    @Override
    public void operation5() {
        System.out.println("B實現了operation5");
    }
}


class A{//A類 通過介面Interface1 Interface2依賴(使用)B類,但是隻會用到1,2,3方法
    public void depend1(Interface1 i){
        i.operation1();
    }
    public void depend2(Interface2 i){
        i.operation2();
    }
    public void depend3(Interface2 i){
        i.operation3();
    }
}

class C{//C類 通過介面Interface1 依賴(使用)D類,但是隻會用到1,4,5方法
    public void depend1(Interface1 i){
        i.operation1();
    }
    public void depend4(Interface3 i){
        i.operation4();
    }
    public void depend5(Interface3 i){
        i.operation5();
    }
}


2.3 依賴倒轉原則

  1. 高層模組不應該依賴底層模組,二者都應該依賴其抽象
  2. 抽象不應該依賴細節,細節應該依賴抽象
  3. 依賴倒轉(倒置)的中心思想是面向介面抽象
  4. 依賴倒轉原則是基於這樣的設計理念:相對於細節的多變性,抽象的東西要穩定的多,以抽象為基礎搭建的架構比以細節為基礎的架構要穩定的多。在java中,抽象指的是介面或抽象類,細節就是具體的實現類
  5. 使用介面或抽象類的目的是定好規範,而不涉及任何具體的操作,把展現細節的任務交給他們的實現類去完成

建立一個Person類用來接收資訊

public class DependencyInversion {
    public static void main(String[] args) {
        Person person = new Person();
        person.receive(new Email());
    }
}


class Email{
    public String getInfo(){
        return "電子郵件資訊:hello";
    }
}

//Person接收訊息的功能
//方式1 分析
//1.簡單 比較容易想到
//2.如果我們獲取的物件是微信,簡訊等等,則新增類,同時Person也需要增加相應的方法
//3.引入一個抽象的介面 IReceiver 表示接收者,這樣Person類與介面IReceiver發生依賴
//因為 Email ,weixin 等等屬於接受的範圍,各自實現IReceiver 介面就ok,這樣我們就符合依賴倒轉原則
class Person{
    public void receive(Email email){
        System.out.println(email.getInfo());
    }
}

2.3.1 依賴關係傳遞的三種方式

  1. 介面傳遞
  2. 構造方法傳遞
  3. setter方式傳遞
//方式1:通過介面傳遞實現依賴
interface IOpenAndClose{
    public void open(ITV tv);//抽象方式,接受介面
}

interface ITV{
    public void play();
}

class ChangHong implements ITV{
    @Override
    public void play() {
        System.out.println("開啟電視");
    }
}

class OpenAndClose implements IOpenAndClose{
    @Override
    public void open(ITV tv) {
        tv.play();
    }
}

//方式2 通過構造方法依賴傳遞
interface IOpenAndClose{
    public void open();//抽象方式,接受介面
}

interface ITV{
    public void play();
}

class ChangHong implements ITV{
    @Override
    public void play() {
        System.out.println("開啟電視");
    }
}

class OpenAndClose implements IOpenAndClose{

    public ITV tv;

    public OpenAndClose(ITV tv){
        this.tv = tv;
    }

    @Override
    public void open() {
        tv.play();
    }
}

//方式3  通過setter方法傳遞
interface IOpenAndClose{
    public void open();//抽象方式,接受介面
    
    public void setTv();
}

interface ITV{
    public void play();
}

class ChangHong implements ITV{
    @Override
    public void play() {
        System.out.println("開啟電視");
    }
}

class OpenAndClose implements IOpenAndClose{

    private ITV tv;

    @Override
    public void setTv() {
        this.tv = tv;
    }

    @Override
    public void open() {
        tv.play();
    }

}

依賴倒轉原則的注意事項和細節

  1. 低層模組儘量都要有抽象類或介面,或者兩者都有,程式穩定性更好
  2. 變數的宣告型別儘量是抽象類或介面,這樣我們的變數引用或實際物件間,就存在一個緩衝層,利於程式擴充套件和優化
  3. 繼承時遵循里氏替換原則

2.4 里氏替換原則

  1. 所有引用基類的地方必須能透明地使用其子類地物件
  2. 在使用繼承時,遵循里氏替換原則,在子類中儘量不要重寫父類地方法
  3. 里氏替換原則告訴我們,繼承實際上讓兩個耦合性增強了,在適當地情況下,可以通過聚合,組合,依賴來解決問題
public class Liskov {
    public static void main(String[] args) {
        B b = new B();
        //B類不在繼承A ,因此呼叫不會再func1是求減 法
    }
}

//建立一個基礎基類
class Base{
    //更加基礎地方法和成員
    public int func1(int num1,int num2){
        return num1 - num2;
    }
}

class A extends Base{
    public int func1(int a,int b){
        return a-b;
    }
}


class B extends Base{
    private A a = new A();
    //B類使用A類地方法,使用組合關係

    public int func1(int a,int b){
        return a+b;
    }

    public int func2(int a,int b){
        return func1(a,b) + 9;
    }

    //仍然想要使用A類地方法
    public int func3(int a,int b){
        return this.a.func1(a,b);
    }
}

2.5 開閉原則

  1. 一個類、模組和函式應該對擴充套件開發(對提供方),對修改關閉(對使用方)。用抽象構建框架,用實現擴充套件細節
  2. 當軟體需要變化時,儘量通過擴充套件軟體實體地行為來實現變化,而不是通過修改已有程式碼來實現變化
  3. 程式設計中遵循其他原則,以及使用設計模式地目的就是遵循開閉原則
public class Ocp {
    public static void main(String[] args) {
        Graph graph = new Graph();
        graph.drawShape(new Rectangle());
    }
}

class Graph{
    public void drawShape(Shape s){
        s.draw();
    }
}

abstract class Shape{
    int m_type;
    public abstract void draw();
}
class Rectangle extends Shape{
    Rectangle(){
        m_type = 2;
    }
    @Override
    public void draw() {
        System.out.println("繪製矩形");
    }
}

2.6 迪米特法則

  1. 一個物件應該對其他物件保持最少的瞭解

  2. 類與類關係越密切,耦合度越大

  3. 迪米特法則又叫最少知道原則,即一個類對自己依賴的類知道的越少越好。對外除了提供的公共方法,不對外洩露任何資訊

  4. 迪米特法則還有個更簡單的定義:只與直接的朋友通訊

  5. 直接的朋友:每個物件都會與其他物件有耦合關係,只要兩個物件之間有耦合關係,我們就說這兩個物件之間是朋友關係。(耦合的方式很多,依賴,關聯,組合,聚合等。其中,我們稱出現成員變數,方法引數,方法返回值中的類為直接的朋友,而出現在區域性變數中的類不是直接的朋友。也就是說,陌生的類最好不要以區域性變數的形式出現在類的內部。

public class Demeter1 {
    public static void main(String[] args) {
        SchoolManager schoolManager = new SchoolManager();
        schoolManager.printAllEmployee(new CollegeManager());
    }
}

//學校總共員工
class Employee{
    private String id;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

//學院總共員工
class CollegeEmployee{
    private String id;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

class CollegeManager{
    //返回學院的所有員工
    public List<CollegeEmployee> getAllEmployee(){
        List<CollegeEmployee> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            CollegeEmployee emp = new CollegeEmployee();
            emp.setId("id=" +i);
            list.add(emp);
        }
        return list;
    }

}

class SchoolManager{
    //返回學校總部的員工
    List<CollegeEmployee> list = new ArrayList<>();
    public List<Employee> getAllEmployee(){
        List<Employee> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            Employee emp = new Employee();
            emp.setId("id=" +i);
            list.add(emp);
        }
        return list;
    }

    //該方法完成輸出學校總部和學院員工資訊
    void printAllEmployee(CollegeManager sub){
        //分析問題
        //1.這裡的CollegeEmployee不是SchoolManager 的直接朋友
        //2.CollegeEmployee是以區域性變數方式出現在 SchoolManager
        //3.違反了迪米特法則
        //獲取到學院員工
        List<CollegeEmployee> list1 = sub.getAllEmployee();
        System.out.println("- --學院員工-----.-----");
        for (CollegeEmployee e : list1) {
            System.out.println(e.getId());
        }
        //獲取到學校總部員工
        List<Employee> list2 = this.getAllEmployee();
        System.out.println(" ------------學校總部員工------------");
        for (Employee e : list2){
             System.out.println(e.getId());
         }
     }
}


public class Demeter1 {
    public static void main(String[] args) {
        SchoolManager schoolManager = new SchoolManager();
        schoolManager.printAllEmployee(new CollegeManager());
    }
}

//學校總共員工
class Employee{
    private String id;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

//學院總共員工
class CollegeEmployee{
    private String id;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

class CollegeManager{
    //返回學院的所有員工
    public List<CollegeEmployee> getAllEmployee(){
        List<CollegeEmployee> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            CollegeEmployee emp = new CollegeEmployee();
            emp.setId("id=" +i);
            list.add(emp);
        }
        return list;
    }

    //該方法完成輸出學校員工資訊
    void printAllEmployee(){

        //獲取到學院員工
        List<CollegeEmployee> list1 = getAllEmployee();
        System.out.println("- --學院員工-----.-----");
        for (CollegeEmployee e : list1) {
            System.out.println(e.getId());
        }
    }
}

class SchoolManager{
    //返回學校總部的員工
    List<CollegeEmployee> list = new ArrayList<>();
    public List<Employee> getAllEmployee(){
        List<Employee> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            Employee emp = new Employee();
            emp.setId("id=" +i);
            list.add(emp);
        }
        return list;
    }

    //該方法完成輸出學校總部和學院員工資訊
    void printAllEmployee(CollegeManager sub){
        //分析問題
        //1.將輸出學院的員工方法,封裝到CollegeManager
        sub.printAllEmployee();
        //獲取到學院員工
        List<CollegeEmployee> list1 = sub.getAllEmployee();
        System.out.println("- --學院員工-----.-----");
        for (CollegeEmployee e : list1) {
            System.out.println(e.getId());
        }
        //獲取到學校總部員工
        List<Employee> list2 = this.getAllEmployee();
        System.out.println(" ------------學校總部員工------------");
        for (Employee e : list2){
            System.out.println(e.getId());
        }
    }
}

迪米特法則注意事項和細節

  1. 迪米特法則的核心是降低類之間的耦合
  2. 注意:由於每個類都減少了不必要的依賴,因此迪米特法則只是要求降低類間(物件間)耦合關係,並不是要求完全沒有依賴關係

2.7 合成複用原則

儘量使用合成/聚成的方式,而不是使用繼承

設計原則核心思想:

  1. 找出應用中可能需要變化之處,把它們獨立出來,不要和那些不需要變化的程式碼混在一起
  2. 針對介面程式設計,而不是針對實現程式設計
  3. 為了互動物件之間的鬆耦合設計而努力