設計模式——結構性設計模式

結構性設計模式

針對類與對象的組織結構。(白話:類與對象之間的交互的多種模式

類/對象適配器模式

當需要傳入一個A類型參數,但只有B類型類時,就需要一個A類型的適配器裝入B類的數據,來將B數據轉成A類型,然後作為參數傳入

適配器,在生活中又稱轉換器。現在的手機基本都割去了3.5mm的耳機接口,此時只有有線耳機,要聽歌就需要一個轉換器將3.5mm接口轉成手機有的type-c的接口

類適配器(不建議)

繼承需要轉變的類

//主方法
public class Main {
    public static void main(String[] args) {
        TestSupplier supplier = new TestSupplier();
      	test( ? );   //我們沒有35MM類型的手機接口,只有type-c的手機接口,那這裡該填個type-c。所以需要一個轉接口將35MM轉為type-c接口
    }

    public static void test(typeC typec){   //現在我們需要調用test方法,但是test方法需要類型是typeC
        System.out.println("成功得到:"+typec.listen());
    }
}

//接口
public interface typeC {    //typeC接口也想聽歌
    String listen();
}

//父類
public class 35MM{	
    public String listenMusic(){
		return "有線耳機聽歌!"	//因為只有有線耳機,所以只有35MM才能聽歌
    }
}

//子類作適配器 繼承35MM,實現type-C接口
public class Adapter extends 35MM implements typeC{
    
    @Override
    public String listen() {  //現在不再繼承35MM,僅實現typeC接口
        return super.listenMusic();
    }
}

對象適配器

將需要轉變的類實例化,並用作與適配器類的構造方法

因為類適配器會佔用一個繼承位,而java又是單繼承的。如果typeC不是接口而是抽象類的話就用不了了。所以提出對象適配器:

//主方法
public class Main {
    public static void main(String[] args) {
        TestSupplier supplier = new TestSupplier();
      	test( ? );   //我們沒有35MM類型的手機接口,只有type-c的手機接口,那這裡該填個type-c。所以需要一個轉接口將35MM轉為type-c接口
    }

    public static void test(typeC typec){   //現在我們需要調用test方法,但是test方法需要類型是typeC
        System.out.println("成功得到:"+typec.listen());
    }
}

//接口
public interface typeC {    //typeC接口也想聽歌
    String listen();
}

//父類
public class 35MM{	
    public String listenMusic(){
		return "有線耳機聽歌!"	//因為只有有線耳機,所以只有35MM才能聽歌
    }
}

//子類作適配器 繼承35MM,實現type-C接口
public class Adapter implements typeC{	//現在不再繼承35MM,僅實現typeC接口
    35MM 35mm;	//實例化需要轉變的類
    public Adapter(35MM 35mm){	//將實例化的對象用於構造對象
		this.35mm = 35mm;
    }
    
    @Override
    public String listen() {   //接着實現listen方法,直接使用typeC提供的實現
        return 35mm.listenMusic();
    }
}

橋接模式

配置自定義

同一種產品有着不同的配置,就像手機有:運行內存 4 6 8g,存儲內存:64 128 256g,芯片:驍龍 A系列 麒麟 聯發科 獵戶座。不能每一種配置都寫一個類就太麻煩了,所以有了橋接模式,可以通過多個類橋接成一個產品類。

優勢:可以通過多個維度來自由設定配置

這裡以華為手機舉例:(小知識——華為手機是用自家的麒麟芯片)

//第一層類:繼承該類可以自定義芯片類型
public abstract class AbstractPhone {
	private Size size; //這裡是描述存儲內存。由於舉例簡單點方便看得懂就不寫運行內存了
    
    public AbstractPhone(Size size){
        this.size = size;
    }
    
    public abstract String getType();	//這裡的類型是指芯片類型
}

//接口及實現類
public interface Size{
    String getSize();
}
public class 256G implements Size{
    @Override
    public String getSize() {
        return "256g內存";
    }
}

//第二層類:繼承該類可以自定義芯片類型和存儲內存的尺度大小
public abstract class RefinedAbstractPhone extends AbstractPhone{
    protected RefinedAbstractPhone(Size size) {
        super(size);
    }
    
    public String getSize(){   //添加尺寸維度獲取方式
        return size.getSize();
    }
}

//產品類:繼承第二層類,然後自定義存儲內存大小和芯片種類
public class HUAWEI extends RefinedAbstractPhone{
    protected HUAWEI(Size size){	//構造方法指定具體存儲內存大小
        super(size);
    }
    
    @Override
    public String getType() {
        return "華為手機";   //返回手機品牌類型
    }
}

//主方法
public static void main(String[] args) {
	HUAWEI huawei = new HUAWEI(new 256G());
    System.out.println(huawei.getType());
    System.out.println(huawei.getSize());
}

組合模式

對多個組件進行統一一樣的操作

組合模式實際上就是將多個組件進行組合,讓用戶可以對它們進行一致性處理。比如我們的文件夾,一個文件夾中可以有很多個子文件夾或是文件。

它就像是一個樹形結構一樣,有分支有葉子,而組合模式則是可以對整個樹形結構上的所有節點進行遞歸處理,比如我們現在希望將所有文件夾中的文件的名稱前面都添加一個前綴,那麼就可以使用組合模式。

image

組合模式的示例如下,這裡我們就用文件和文件夾的例子來講解:

/**
 * 首先創建一個組件抽象,組件可以包含組件,組件有自己的業務方法
 */
public abstract class Component {
    public abstract void addComponent(Component component);    //添加子組件
    public abstract void removeComponent(Component component);   //刪除子組件
    public abstract Component getChild(int index);   //獲取子組件
    public abstract void test();   //執行對應的業務方法,比如修改文件名稱
}

接着我們來編寫兩種實現類:文件夾實現類,文件實現類

public class Directory extends Component{   //目錄可以包含多個文件或目錄

    List<Component> child = new ArrayList<>();   //這裡我們使用List來存放目錄中的子組件

    @Override
    public void addComponent(Component component) {
        child.add(component);
    }

    @Override
    public void removeComponent(Component component) {
        child.remove(component);
    }

    @Override
    public Component getChild(int index) {
        return child.get(index);
    }

    @Override
    public void test() {
        child.forEach(Component::test);   //將繼續調用所有子組件的test方法執行業務
    }
}
public class File extends Component{   //文件就相當於是樹葉,無法再繼續添加子組件了

    @Override
    public void addComponent(Component component) {
        throw new UnsupportedOperationException();   //不支持這些操作了
    }

    @Override
    public void removeComponent(Component component) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Component getChild(int index) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void test() {
        System.out.println("文件名稱修改成功!"+this);   //具體的名稱修改操作
    }
}

最後,我們來測試一下:可以看到我們對最外層目錄進行操作後,會遞歸向下處理當前目錄和子目錄中所有的文件

public static void main(String[] args) {
    Directory outer = new Directory();   //新建一個外層目錄
    Directory inner = new Directory();   //新建一個內層目錄
    outer.addComponent(inner);
    outer.addComponent(new File());   //在內層目錄和外層目錄都添加點文件,注意別導錯包了
    inner.addComponent(new File());
    inner.addComponent(new File());
    outer.test();    //開始執行文件名稱修改操作
}

裝飾模式

通過B類 實現對A類方法執行前後,分別多執行一些操作。類似於AOP

適用:業務功能前後實現一些操作。如:在支付前提醒是否需要支付xxx元。

//頂層抽象類
public abstract class Base {   //頂層抽象類,定義了一個test方法執行業務
    public abstract void test();
}

//業務實現類
public class BaseImpl extends Base{
    @Override
    public void test() {
        System.out.println("我是業務方法");   //具體的業務方法
    }
}

//裝飾業務類(這裡的構造方法參數是需要傳入實現業務類對象)
public class Decorator extends Base{   //裝飾者需要將裝飾目標組合到類中

    protected Base base;

    public Decorator(Base base) {
        this.base = base;
    }

    @Override
    public void test() {
        base.test();    //這裡暫時還是使用目標的原本方法實現
    }
}

//具體實現裝飾業務類
public class DecoratorImpl extends Decorator{   //裝飾實現

    public DecoratorImpl(Base base) {
        super(base);
    }

    @Override
    public void test() {    //對原本的方法進行裝飾,我們可以在前後都去添加額外操作
        System.out.println("裝飾方法:我是操作前邏輯");
        super.test();
        System.out.println("裝飾方法:我是操作後邏輯");
    }
}

//主方法
public static void main(String[] args) {
    Base base = new BaseImpl();
    Decorator decorator = new DecoratorImpl(base);  //將Base實現裝飾一下
    Decorator outer = new DecoratorImpl(decorator);  //裝飾者還可以嵌套,此時是裝飾兩次

    decorator.test();	//裝飾一次:裝飾前——業務方法——裝飾後

    outer.test();	//裝飾兩次:裝飾前——裝飾前——業務方法——裝飾後——裝飾後
}

代理模式

和裝飾模式代碼一模一樣,但核心是思想不同

裝飾模式和代理模式:

  1. 結構相同:都實現同一個接口/抽象類
  2. 作用不同:
    • 裝飾器模式強調的是增強自身,在被裝飾之後你能夠在被增強的類上使用增強後的功能,增強後你還是你,只不過被強化了而已;
    • 代理模式強調要讓別人幫你去做事情,以及添加一些本身與你業務沒有太多關係的事情(記錄日誌、設置緩存等)重點在於讓別人幫你做。

代理模式一般代碼:

//頂層抽象類
public abstract class Base {   //頂層抽象類,定義了一個test方法執行業務
    public abstract void test();
}

//業務實現類
public class BaseImpl extends Base{
    @Override
    public void test() {
        System.out.println("我是業務方法");   //具體的業務方法
    }
}

//代理業務類(這裡的構造方法參數是需要傳入實現業務類對象)
public class Decorator extends Base{   //代理者需要將代理目標組合到類中

    protected Base base;

    public Decorator(Base base) {
        this.base = base;
    }

    @Override
    public void test() {
        base.test();    //這裡暫時還是使用目標的原本方法實現
    }
}

//具體實現代理業務類
public class DecoratorImpl extends Decorator{   //代理實現

    public DecoratorImpl(Base base) {
        super(base);
    }

    @Override
    public void test() {    //對原本的方法進行代理,我們可以在前後都去添加額外操作
        System.out.println("裝飾方法:我是操作前邏輯");
        super.test();
        System.out.println("裝飾方法:我是操作後邏輯");
    }
}

//主方法
public static void main(String[] args) {
    Base base = new BaseImpl();
    Decorator decorator = new DecoratorImpl(base);  //將Base實現代理一下
    Decorator outer = new DecoratorImpl(decorator);  //代理者還可以嵌套,此時是代理兩次

    decorator.test();	//代理一次:代理前——業務方法——代理後

    outer.test();	//代理兩次:代理前——代理前——業務方法——代理後——代理後
}

實現代理模式除了和裝飾模式一樣的代碼情況外還有兩種實現方式:【因為都是動態代理所以生成的代理類是看不到的】

  1. JDK提供的動態代理:我們不再需要手動編寫繼承關係創建代理類,它能夠在運行時通過反射機製為我們自動生成代理類:【只能代理接口】

    //接口
    public interface Subject {  //JDK提供的動態代理只支持接口
        void test();
    }
    
    //接口實現類
    public class SubjectImpl implements Subject{
    
        @Override
        public void test() {
            System.out.println("我是測試方法!");
        }
    }
    
    //創建動態代理的處理邏輯(就是執行業務前後的方法編寫在裏面)
    public class TestProxy implements InvocationHandler {    //代理類,需要實現InvocationHandler接口
    
        private final Object object;   //這裡需要保存一下被代理的對象,下面需要用到
    
        public TestProxy(Object object) {
            this.object = object;
        }
    
        @Override   //此方法就是調用代理對象的對應方法時會進入,這裡我們就需要編寫如何進行代理了
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         		//method就是調用的代理對象的哪一個方法,args是實參數組
            System.out.println("代理的對象:"+proxy.getClass());   //proxy就是生成的代理對象了,我們看看是什麼類型的
            Object res = method.invoke(object, args);   //在代理中調用被代理對象原本的方法,因為你是代理,還是得執行一下別人的業務,當然也可以不執行,但是這樣就失去代理的意義了,注意要用上面的object
            System.out.println("方法調用完成,返回值為:"+res);   //看看返回值是什麼
            return res;   //返回返回值
        }
    }
    
  2. Spring在使用的CGLib框架代理。

    maven依賴:

    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.1</version>
    </dependency>
    

    代碼實現:

    //接口
    public interface Subject {  //JDK提供的動態代理只支持接口
        void test();
    }
    
    //接口實現類
    public class SubjectImpl implements Subject{
    
        @Override
        public void test() {
            System.out.println("我是測試方法!");
        }
    }
    
    //創建動態代理的處理邏輯(就是執行業務前後的方法編寫在裏面)
    public class TestProxy implements MethodInterceptor {  //首先還是編寫我們的代理邏輯
    
        private final Object target;   //這些和之前JDK動態代理寫法是一樣的
    
        public TestProxy(Object target) {
            this.target = target;
        }
    
        @Override   //我們也是需要在這裡去編寫我們的代理邏輯
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("現在是由CGLib進行代理操作!"+o.getClass());
            return method.invoke(target, objects);   //也是直接調用代理對象的方法即可
        }
    }
    
    //主方法
    public static void main(String[] args) {
        SubjectImpl subject = new SubjectImpl();
    
        Enhancer enhancer = new Enhancer();   //增強器,一會就需要依靠增強器來為我們生成動態代理對象
        enhancer.setSuperclass(SubjectImpl.class);    //直接選擇我們需要代理的類型,直接不需要接口或是抽象類,SuperClass作為代理類的父類存在,這樣我們就可以按照指定類型的方式去操作代理類了
        enhancer.setCallback(new TestProxy(subject));  //設定我們剛剛編寫好的代理邏輯
    
        SubjectImpl proxy = (SubjectImpl) enhancer.create();   //直接創建代理類
    
        proxy.test();   //調用代理類的test方法
    }
    

外觀模式

可以理解為門面模式,將需要通過操作多個類實現的一個功能封裝到一個類中,便於使用

當每個功能是一個系統,完成一個業務需要多個功能時就需要分別調用多個系統,此時就可以將一個業務需要使用的多個系統封裝成一個門面系統,只要調用該門面系統即可完成該業務。

image

舉例:比如現在我們設計了三個子系統,分別是排隊、結婚、領證,正常情況下我們是需要分別去找這三個部門去完成的,但是現在我們通過門面統一來完成

//系統一
public class SubSystemA {
    public void test1(){
        System.out.println("排隊");
    }
}

//系統二
public class SubSystemB {
    public void test2(){
        System.out.println("結婚");
    }
}

//系統三
public class SubSystemC {
    public void test3(){
        System.out.println("領證");
    }
}

//門面
public class Facade {

    SubSystemA a = new SubSystemA();
    SubSystemB b = new SubSystemB();
    SubSystemC c = new SubSystemC();

    public void marry(){   //紅白喜事一條龍服務
        a.test1();
        b.test2();
        c.test3();
    }
}

//主方法
public static void main(String[] args) {
    Facade facade = new Facade();
    facade.marry();
}

享元模式

核心是共享。當A類方法里寫了一個方法,B類中需要同樣的方法就可以直接創建A類對象來調用方法或者通過一個方法工廠類收集各個方法,然後B類通過工廠類調用A類方法

舉例:通過享元工廠類實現共享方法

//A類
public class DBUtil {
    public void selectDB(){
        System.out.println("我是數據庫操作...");
    }
}

//享元工廠
public class DBUtilFactory {
    private static final DBUtil UTIL = new DBUtil();   //享元對象被存放在工廠中

    public static DBUtil getFlyweight(){   //獲取享元對象
        return UTIL;
    }
}

//B類
public class UserService {   //用戶服務

    public void service(){
        DBUtil util = DBUtilFactory.getFlyweight();   //通過享元工廠拿到DBUtil對象
        util.selectDB();    //該幹嘛幹嘛
    }
}