Java設計模式 --- 七大常用設計模式

原文鏈接:https://blog.csdn.net/weixin_40834464/article/details/82958187

 

設計模式分爲三種類型,共23種:
創建型模式:單例模式、抽象工廠模式、建造者模式、工廠模式、原型模式

結構型模式:適配器模式、橋接模式、裝飾模式、組合模式、外觀模式、享元模式、代理模式

行爲型模式:模板方法模式、命令模式、迭代器模式、觀察者模式、中介者模式、備忘錄模式、解釋器模式、狀態模式、策略模式

本文主要講解的是:

創建型模式:單例模式、建造者模式、工廠模式、

結構型模式:適配器模式、代理模式

行爲型模式:模板方法模式、策略模式

對應的GitHub項目地址:https://github.com/kkzhilu/DesignPattern

可以下載進行深入的理解

 

創建型模式例子
單例模式:
對應的類: SingleTon.java

參考博客:http://www.runoob.com/design-pattern/singleton-pattern.html

/***
 * 創建型模式 ---- 單例模式
 * 
 * @author kxm
 */
public class SingleTon {
    /***
     * 單例設計模式的一般定義:一個類中只允許有一個實例。 實現思路:讓類的構造方法私有化,同時提供一個靜態方法去實例化這個類。
     * 
     * 懶漢式:在靜態方法中初始化。時間換空間。(不推薦,時間很重要) 餓漢式:在聲明對象就初始化。空間換時間。(推薦,空間不是問題)
     * 
     * 懶漢式線程不安全,需要加上同步鎖,同步鎖影響了程序執行效率 餓漢式天生線程安全,類加載的時候初始化一次對象,效率比懶漢式高。
     * 
     * 注意私有構造方法
     */
    // 定義成私有構成方法,變成單例的 單例模式的核心
    private SingleTon() {}
 
    // 餓漢式:類加載的時候即進行初始化
    private static final SingleTon single = new SingleTon();
 
    public static SingleTon getTeacher() {
        return single;
    }
 
    /******************************* 分割線 *********************************/
    // 懶漢式 雙重校驗鎖保證線程安全,比較好的寫法  --- volatile 禁止指令重排 主要由於new SingleTon();可能出現問題
    private volatile static SingleTon myTest = null;
 
        public static SingleTon geTest() {
            if (myTest == null) {
                synchronized (SingleTon.class) {
                    if (myTest == null) {
                        myTest = new SingleTon();
                    }
                }
            }
            return myTest;
        }
    
 
    /***
     * 較爲標準的寫法 --- 靜態內部類寫法 
     * 是否 Lazy 初始化:是  
     * 是否多線程安全:是
     * @author kxm
     */
    private static class SingletonHolder {
        private static final SingleTon INSTANCE = new SingleTon();
    }
    public static final SingleTon getInstance() {
        return SingletonHolder.INSTANCE;
    }
}
工廠模式:
簡單工廠 --- SimpleFactoryTon.java:

/***
 * 創建型模式 ---- 工廠模式 (簡單工廠模式)
 * @author kxm
 */
public class SimpleFactoryTon {
    public Car creatCarFactory(int num) {
        Car car = null ;
        switch (num) {
        case 1:
            car = new Car("東風雪鐵龍");
            break;
        case 2:
            car = new Car("東風雪鐵龍","紅色");
            break;    
        default:
            car = new Car();
            break;
        }
        return car;
    }
}
 
 
class Car{
    private String band;
    private String color;
    public Car() {}
    public Car(String band) {
        this.band = band;
    }
    public Car(String band, String color) {
        this.band = band;
        this.color = color;
    }
    public String getBand() {
        return band;
    }
    public void setBand(String band) {
        this.band = band;
    }
    public String getColor() {
        return color;
    }
    public void setColor(String color) {
        this.color = color;
    }
}
工廠方法模式:

參考博客:https://www.jianshu.com/p/d0c444275827

abstract class Factory{
    public abstract Product Manufacture();
}
 
public class FactoryA extends Factory {
 
    @Override
    public Product Manufacture() {
        return new ProductA();
    }
}
 
public class FactoryB extends Factory {
    
    @Override
    public Product Manufacture() {
        return new ProductB();
    }
}
 
abstract class Product{
    public abstract void Show();
}
 
public class ProductA extends  Product {
    
    //具體產品A類
    @Override
    public void Show() {
        System.out.println("生產出了產品A");
    }
}
 
public class ProductB extends Product {
 
    //具體產品B類
    @Override
    public void Show() {
        System.out.println("生產出了產品B");
    }
}
 
/**
 * 創建型模式 ---- 工廠模式 (工廠方法模式)
 * 相比簡單工廠的優點:
 *   更符合開-閉原則
 *   符合單一職責原則
 *   不使用靜態工廠方法,可以形成基於繼承的等級結構
 *   
 * 參考文檔:https://www.jianshu.com/p/d0c444275827
 * @author kxm
 */
public class TestFactory {
 
    public static void main(String[] args) {
        //客戶要產品A
        FactoryA mFactoryA = new FactoryA();
        mFactoryA.Manufacture().Show();
 
        //客戶要產品B
        FactoryB mFactoryB = new FactoryB();
        mFactoryB.Manufacture().Show();
    }
}
 

建造者模式:
參考博客:https://blog.csdn.net/u010102390/article/details/80179754

// 1.需要的對象定義:產品(Product)
public class Human {
    private String head;
    private String body;
    private String hand;
    private String foot;
    public String getHead() {
        return head;
    }
    public void setHead(String head) {
        this.head = head;
    }
    public String getBody() {
        return body;
    }
    public void setBody(String body) {
        this.body = body;
    }
    public String getHand() {
        return hand;
    }
    public void setHand(String hand) {
        this.hand = hand;
    }
    public String getFoot() {
        return foot;
    }
    public void setFoot(String foot) {
        this.foot = foot;
    }
    @Override
    public String toString() {
        return "Human [head=" + head + ", body=" + body + ", hand=" + hand + ", foot=" + foot + "]";
    }
}
// 2.定義需要對象應有的方法及返回對象的抽象方法  --- 建造者角色(Builder)
public interface IBuildHuman {
    public void buildHead();
    public void buildBody();
    public void buildHand();
    public void buildFoot();
    public Human createHuman();
}
// 3.實現類實現抽象方法,進行建造 --- 具體創建者角色(ConcreteBuilder)
public class SmartManBuilder implements IBuildHuman {
    Human human;
    public SmartManBuilder() {
        human = new Human();
    }
    @Override
    public void buildHead() {
        human.setHead("頭腦智商180");
    }
    @Override
    public void buildBody() {
        human.setBody("身體");
    }
    @Override
    public void buildHand() {
        human.setHand("手");
    }
    @Override
    public void buildFoot() {
        human.setFoot("腳");
    }
    @Override
    public Human createHuman() {
        return human;
    }
}
// 3.實現類實現抽象方法,進行建造 --- 具體創建者角色 當前爲運動員
public class ActiveManBuilder implements IBuildHuman {
 
    Human human;
    public ActiveManBuilder() {
        human = new Human();
    }
    
    @Override
    public void buildHead() {
        human.setHead("頭腦智商180");
    }
 
    @Override
    public void buildBody() {
        human.setBody("身體無敵的運動員");
    }
 
    @Override
    public void buildHand() {
        human.setHand("手");
    }
 
    @Override
    public void buildFoot() {
        human.setFoot("腳");
    }
 
    @Override
    public Human createHuman() {
        return human;
    }
 
}
// 4.建造模式的核心 --- 指導者(Director,進行建造組裝)
public class Director {
    
    public Human createHumanByDirecotr(IBuildHuman bh) {
        bh.buildBody();
        bh.buildFoot();
        bh.buildHand();
        bh.buildHead();
        return bh.createHuman();
    }
}
/***
 * 創建型模式 ---- 建造者模式:
 * 1.需要的對象定義:產品(Product)
 * 2.定義需要對象應有的方法及返回對象的抽象方法  --- 建造者角色(Builder)
 * 3.實現類實現抽象方法,進行建造 --- 具體創建者角色(ConcreteBuilder)
 * 4.建造模式的核心 --- 指導者(Director)
 * @author kxm
 */
public class BuildTest {
    public static void main(String[] args) {
        Director director = new Director();
        Human human = director.createHumanByDirecotr(new SmartManBuilder());
        Human humanAgain = director.createHumanByDirecotr(new ActiveManBuilder());
        System.out.println(human);
        System.out.println(humanAgain);
    }
}
 
測試結果:
Human [head=頭腦智商180, body=身體, hand=手, foot=腳]
Human [head=頭腦智商180, body=身體無敵的運動員, hand=手, foot=腳]
結構型模式例子
適配器模式:
參考博客:https://www.cnblogs.com/V1haoge/p/6479118.html

適配器模式分爲三種:類適配器,對象適配器,接口適配器

前兩種的主要作用相當於生活中的真實適配器,如5v的充電器需要插入220v的電壓,這裏就需要適配器

另外一種是接口適配器,如MouseAdapter,我們只需要重寫我們需要的方法,不需要的方法就不管它,可以大大減少我們的代碼量,提高效率

類適配器:

比如,我們現在有USB標準接口,但是PS2也想插入用一下,直接使用是不行的,因此需要適配器

public interface Usb {
    void isUsb();
}
 
public interface Ps2 {
    void isPs2();
}
 
public class Usber implements Usb {
 
    @Override
    public void isUsb() {
        System.out.println("USB口");
    }
}
創建適配器:

/***
 * 類適配器實現思路: 適配器繼承標準類,實現需要適配的接口,實現接口內方法即可
 * @author kxm
 */
public class UsbAdapter extends Usber implements Ps2 {
    @Override
    public void isPs2() {
        super.isUsb();
    }
}
測試類:

/***
 * 結構型模式:適配器模式 --- 類適配器
 * Usb接口,Ps2接口,無法直接互相使用,因此通過類適配器的方式,進行適配
 * 文章參考:https://www.cnblogs.com/V1haoge/p/6479118.html
 * 
 * 類適配器與對象適配器的使用場景一致,僅僅是實現手段稍有區別,二者主要用於如下場景:
 * (1)想要使用一個已經存在的類,但是它卻不符合現有的接口規範,導致無法直接去訪問,這時創建一個適配器就能間接去訪問這個類中的方法
 * (2)我們有一個類,想將其設計爲可重用的類(可被多處訪問),我們可以創建適配器來將這個類來適配其他沒有提供合適接口的類
 * @author kxm
 */
public class TestAdapter {
     public static void main(String[] args) {
        Ps2 ps2 = new UsbAdapter();
        ps2.isPs2();
    }
}
 
測試結果:
USB口  ---- 可以發現,我們調用的是Ps2的接口方法,返回的是Usb口,達到了適配的目的
對象適配器:

由於類適配器寫好之後就只能針對一個類使用,因此衍生出對象適配器,代碼變化不大,主要是適配器有所變化

/***
 * 對象適配器實現思路: 適配器實現被適配的接口,通過構造方法獲取到標準對象,再把需要被適配的方法重寫,替換成標準方法
 * @author kxm
 */
public class UsbObjectAdapter implements Ps2 {
    private Usber usb;
    public UsbObjectAdapter(Usber usber) {
        this.usb = usber;
    }
    @Override
    public void isPs2() {
        usb.isUsb();
    }
}
測試類:

public class TestObjectAdapter {
    public static void main(String[] args) {
        Usber usber = new Usber();
        Ps2 ps2 = new UsbObjectAdapter(usber);
        ps2.isPs2();
    }
}
 
結果:USB口
接口適配器 --- 用來減少代碼量,提高可讀性:

public interface A {
    void a();
    void b();
    void c();
    void d();
    void e();
    void f();
}
/****
 * 抽象類適配接口,實現空方法
 * @author kxm
 */
public abstract class Adapter implements A {
 
    @Override
    public void a() {
        // TODO Auto-generated method stub
        
    }
 
    @Override
    public void b() {
        // TODO Auto-generated method stub
        
    }
 
    @Override
    public void c() {
        // TODO Auto-generated method stub
        
    }
 
    @Override
    public void d() {
        // TODO Auto-generated method stub
        
    }
 
    @Override
    public void e() {
        // TODO Auto-generated method stub
        
    }
 
    @Override
    public void f() {
        // TODO Auto-generated method stub
        
    }
}
// 真正的工具類想要使用時,繼承適配器類,只需要重寫我們需要的方法即可
public class Ashili extends Adapter {
    public void a(){
        System.out.println("實現A方法被調用");
    }
}
/***
 * 接口適配器模式
 * 原理:通過抽象類來實現適配,用來減少不必要代碼的效果 --- MouseAdapter
 * 
 * 接口適配器使用場景:
 * (1)想要使用接口中的某個或某些方法,但是接口中有太多方法,
 *  我們要使用時必須實現接口並實現其中的所有方法,可以使用抽象類來實現接口,
 *  並不對方法進行實現(僅置空),然後我們再繼承這個抽象類來通過重寫想用的方法的方式來實現。這個抽象類就是適配器。
 *  
 *  好處:不需要完全實現內部的所有方法,只需要選擇有需要的去使用
 * @author kxm
 */
public class Test {
 
    public static void main(String[] args) {
        Ashili ashili = new Ashili();
        ashili.a();
        ashili.b();
        ashili.c();
    }
}
 
測試結果:
實現A方法被調用
代理模式
代理模式分爲靜態代理,JDK動態代理,CGLIB動態代理,三種方式,代理模式是Spring中面向切面編程的核心

靜態代理:

// 定義普通接口
public interface Subject {
    public void shopping();
}
 
// 普通實現類實現接口
public class SuperMan implements Subject {
    @Override
    public void shopping() {
        System.out.println("超人要去購物了~~~");
    }
}
 
// 代理類 --- 代理SuperMan這個類,增強其方法,控制其訪問
public class Proxy implements Subject {
 
    private SuperMan superman;
    public Proxy(SuperMan superMan) {
        this.superman = superMan;
    }
    
    @Override
    public void shopping() {
        //代購之前要做的事情
        System.out.println("做大量的商品專業評估");
        System.out.println("==========代理之前==========");
        superman.shopping();//被代理人真正的業務
        System.out.println("==========代理之後==========");
        //代購之後要做的事情
        System.out.println("代購之後的客戶滿意度調查");     
    }
}    
 
// 測試類
/***
 * 結構型模式: 代理模式 --- 靜態代理
 * 主要的思路就是把,繼承同一的接口的類A,放到繼承接口的類B中調用,在調用前後可以加上新的方法
 * 
 * Java中線程的設計就使用了靜態代理設計模式,其中自定義線程類實現Runable接口,
 * Thread類也實現了Runalbe接口,在創建子線程的時候,
 * 傳入了自定義線程類的引用,再通過調用start()方法,調用自定義線程對象的run()方法。實現了線程的併發執行。
 * 
 * @author kxm 
 */
public class TestProxy {
    public static void main(String[] args) {
        SuperMan superMan = new SuperMan();
        Subject subject = new Proxy(superMan);
        subject.shopping();
    }
}
 
測試結果:
做大量的商品專業評估
==========方法調用之前==========
超人要去購物了~~~
==========方法調用之後==========
代購之後的客戶滿意度調查
JDK動態代理:

// 普通接口
public interface UserService {
    void saveUser();
}
 
// 普通實現
public class UserServiceImpl implements UserService {
    @Override
    public void saveUser() {
        System.out.println("調用 saveUser() 方法");
    }
}
 
// 代理類 --- 進行JDK動態代理 注意Proxy.newProxyInstance()方法的參數
// 參數是:被代理的類加載器,接口,重寫InvocationHandler方法
public class MyProxyUtil {
    public static UserService getProxyByJDK(UserService service) {
        UserService userService = (UserService) Proxy.newProxyInstance(service.getClass().getClassLoader(),
                service.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("記錄日誌-開始");
                        Object obj = method.invoke(service, args);
                        System.out.println("記錄日誌-結束");
                        return obj;
                    }
                });
        return userService;
    }
}
 
// 測試類
/***
 * 通過JDK動態代理技術,還有一種是CGLIB動態代理,詳情見Spring中的代碼
 * @author kxm
 */
public class Test {
    public static void main(String[] args) {
        // 創建目標對象
        UserService userService = new UserServiceImpl();
        // 生成代理對象
        UserService proxy = MyProxyUtil.getProxyByJDK(userService);
        // 調用目標對象方法
        userService.saveUser();
        System.out.println("===================================");
        // 調用代理對象方法
        proxy.saveUser();
    }
}
 
測試結果:
調用 saveUser() 方法
===================================
記錄日誌-開始
調用 saveUser() 方法
記錄日誌-結束
行爲型模式例子
模板方法模式:
參考博客:https://blog.csdn.net/carson_ho/article/details/54910518

模板方法模式簡單來說,就是定義一個公共模板,比如說,我要做菜,做炒空心菜和蒜蓉兩種菜,做這兩種菜的步驟是不完全一樣,但是重複的步驟有很多的過程,因此我可以定義一個炒菜模板,如下:

public abstract class TemplateClass {
 
    // 模板方法,用來控制炒菜的流程 (炒菜的流程是一樣的-複用)
    // 申明爲final,不希望子類覆蓋這個方法,防止更改流程的執行順序
    final void cookProcess() {
        // 第一步:倒油 --- 一樣的
        this.pourOil();
        // 第二步:熱油 --- 一樣的
        this.HeatOil();
        // 第三步:倒蔬菜 -- 不一樣
        this.pourVegetable();
        // 第四步:倒調味料 -- 不一樣
        this.pourSauce();
        // 第五步:翻炒 --- 一樣的
        this.fry();
    }
 
    // 定義結構裏哪些方法是所有過程都是一樣的可複用的,哪些是需要子類進行實現的
    // 第一步:倒油是一樣的,所以直接實現
    void pourOil() {
        System.out.println("倒油");
    }
 
    // 第二步:熱油是一樣的,所以直接實現
    void HeatOil() {
        System.out.println("熱油");
    }
    // 第三步:倒蔬菜是不一樣的(一個下包菜,一個是下菜心)
    // 所以聲明爲抽象方法,具體由子類實現
    abstract void pourVegetable();
 
    // 第四步:倒調味料是不一樣的(一個下辣椒,一個是下蒜蓉)
    // 所以聲明爲抽象方法,具體由子類實現
    abstract void pourSauce();
 
    // 第五步:翻炒是一樣的,所以直接實現
    void fry() {
        System.out.println("炒啊炒啊炒到熟啊");
    }
 
}
炒包菜 --- 核心步驟中,蔬菜材料和調味料不同,因此如下:

// 繼承模板抽象類,實現尚未實現的兩種抽象方法
public class BaoCai extends TemplateClass {
 
    @Override
    void pourVegetable() {
        System.out.println("下鍋的蔬菜是包菜");
    }
 
    @Override
    void pourSauce() {
        System.out.println("下鍋的醬料是辣椒");
    }
 
}
炒蒜蓉 --- 核心步驟中,蔬菜材料和調味料不同,因此如下:

public class SuanRong extends TemplateClass {
    @Override
    void pourVegetable() {
        System.out.println("下鍋的蔬菜是菜心");
    }
    @Override
    void pourSauce() {
        System.out.println("下鍋的醬料是蒜蓉");
    }
 
}
測試類如下:

/***
 * 行爲型模式:模版方法模式
 * 核心:抽象父類定義相同的部分,實現相同的方法,子類實現不同的部分
 * 即:現在有炒菜這個公共行爲,但是炒的兩個菜不同,具體來說是蔬菜和佐料,不同,因此需要重寫的也是這兩個部分的方法
 * 參考博客:https://blog.csdn.net/carson_ho/article/details/54910518
 * @author kxm
 */
public class TestTemplate {
 
    public static void main(String[] args) {
        BaoCai baoCai = new BaoCai();
        SuanRong suanRong = new SuanRong();
        baoCai.cookProcess();
        System.out.println("================== 分割線  ===============");
        suanRong.cookProcess();
    }
}
 
測試結果:
倒油
熱油
下鍋的蔬菜是包菜
下鍋的醬料是辣椒
炒啊炒啊炒到熟啊
================== 分割線  ===============
倒油
熱油
下鍋的蔬菜是菜心
下鍋的醬料是蒜蓉
炒啊炒啊炒到熟啊
策略模式:
將算法的責任和本身進行解耦,使得:

算法可獨立於使用外部而變化

客戶端方便根據外部條件選擇不同策略來解決不同問題

我們舉一個銷售策略的例子,在不同的時節,需要使用不同的銷售方式,因此定義如下:

// 定義接口方法
public abstract class Strategy {  
    public abstract void show();
}
 
//爲春節準備的促銷活動A
class StrategyA extends Strategy{
    @Override
    public void show() {
        System.out.println("爲春節準備的促銷活動A");
    }
}
 
//爲中秋節準備的促銷活動B
class StrategyB extends Strategy{
    @Override
    public void show() {
        System.out.println("爲中秋節準備的促銷活動B");
    }
}
 
//爲聖誕節準備的促銷活動C
class StrategyC extends Strategy{
    @Override
    public void show() {
        System.out.println("爲聖誕節準備的促銷活動C");
    }
}
定義銷售人員選擇策略:

public class SalesMan {
    //持有抽象策略角色的引用
    private Strategy strategy;
    //生成銷售員實例時告訴銷售員什麼節日(構造方法)
    //使得讓銷售員根據傳入的參數(節日)選擇促銷活動(這裏使用一個簡單的工廠模式)
    public  SalesMan(String festival) {
        switch ( festival) {
            //春節就使用春節促銷活動
            case "A":
                strategy = new StrategyA();
                break;
            //中秋節就使用中秋節促銷活動
            case "B":
                strategy = new StrategyB();
                break;
            //聖誕節就使用聖誕節促銷活動
            case "C":
                strategy = new StrategyC();
                break;
        }
    }
    //向客戶展示促銷活動
    public void SalesManShow(){
        strategy.show();
    }
}
測試類展示效果:

/***
 * 行爲型模式:策略模式
 * 定義一系列算法,將每個算法封裝到具有公共接口的一系列策略類中,
 * 從而使它們可以相互替換 & 讓算法可在不影響客戶端的情況下發生變化
 * 優點:
 *    策略類之間可以自由切換
 *    易於擴展
 *    避免使用多重條件選擇語句(if else),充分體現面向對象設計思想。
 * 
 * 參考文檔:https://www.jianshu.com/p/0c62bf587b9c
 * @author kxm
 */
public class StrategyPattern {
    public static void main(String[] args) {
        SalesMan mSalesMan ;
        //春節來了,使用春節促銷活動
        System.out.println("對於春節:");
        mSalesMan = new SalesMan("A");
        mSalesMan.SalesManShow();
        
      //中秋節來了,使用中秋節促銷活動
        System.out.println("對於中秋節:");
        mSalesMan =  new SalesMan("B");
        mSalesMan.SalesManShow();
 
        //聖誕節來了,使用聖誕節促銷活動
        System.out.println("對於聖誕節:");
        mSalesMan =  new SalesMan("C");
        mSalesMan.SalesManShow();
    }
}
 
測試結果:
對於春節:
爲春節準備的促銷活動A
對於中秋節:
爲中秋節準備的促銷活動B
對於聖誕節:
爲聖誕節準備的促銷活動C

————————————————
版權聲明:本文爲CSDN博主「山人宿置酒」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_40834464/article/details/82958187

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章