分享 Java 開發中常用到的設計模式(一)

来源:https://www.cnblogs.com/Apluemxa/archive/2023/08/09/17594211.html
-Advertisement-
Play Games

不知道大家在開發的時候,有沒有想過(遇到)這些問題: 1、大家都是按需要開發,都是一個職級的同事,為什麼有些人的思路就很清晰,代碼也很整潔、易懂;而自己開發,往往不知道怎麼下手設計,寫完了也是bug一堆,codeReview的時候更是頻頻被懟... 2、感覺每天都是CURD,寫重覆的代碼,做類似的需... ...


分享 Java 開發中常用到的設計模式(一)

前言

不知道大家在開發的時候,有沒有想過(遇到)這些問題:

  • 大家都是按需要開發,都是一個職級的同事,為什麼有些人的思路就很清晰,代碼也很整潔、易懂;而自己開發,往往不知道怎麼下手設計,寫完了也是bug一堆,codereview的時候更是頻頻被懟...
  • 感覺每天都是CURD,寫重覆的代碼,做類似的需求,怎麼才能提高自己的水平?
  • 每每看到大佬的代碼,或者優秀框架的源碼,總是似懂非懂,懷疑自己是不是缺少了哪些知識?

如果你有這些問題,或者思考過這些問題,那麼你起碼意識到了自己的不足,這其實是沒有熟練掌握軟體開發中的重要技能-設計模式而導致的。

先大致來看一下關於設計模式的思維導圖,包括六大軟體開發原則、設計模式分類以及23種設計模式的名稱:

23種設計模式總結

作為軟體開發人員,看著這些設計模式的名稱,你是否多少會有一些印象呢?

今天就和大家一起分享一下幾個在 Java 開發中常見的設計模式,包括具體的應用場景、代碼 demo 以及 UML 圖解等。

圖解設計模式

一、觀察者模式

1.1基本概念

觀察者模式,即Observer模式,顧名思義,指的是:當被觀察對象發生變化時,會告知觀察者。適用於根據對象狀態進行相應處理的場景。

1.2Demo案例

下麵用一個demo來直觀地展示觀察者模式的實際應用場景:觀察者將會觀察一個能生成分數的對象,並且觀察者還可以通過觀察,得到不同形式的分數結果(數字形式、圖示形式)。

類和介面一覽,如表1.1所示:

表1.1
名稱 說明
Observer 觀察者的介面,感知來自被觀察對象的狀態變化
AbstractScoreGenerator 被觀察者的抽象類
ScoreGenerator 被觀察者的具體功能實現
DigitalObserver 具體的觀察者,感知被觀察者的變化,用數值的形式實現
GraphObserver 具體的觀察者,感知被觀察者的變化,用圖示的形式實現
Main 入口

介面與各個類的類圖,如圖1-1所示:

圖1-1
  • Observer介面

    public interface Observer {
        /**
         * 1、觀察者的抽象方法,作用是感知來自被觀察對象的狀態變化;
         * 2、即兩個具體的觀察者實現該方法後,可以得到來自被觀察者的一些變化。
         * @param abstractScoreGenerator
         */
        void update(AbstractScoreGenerator abstractScoreGenerator);
    }
    
  • AbstractScoreGenerator抽象類

    public abstract class AbstractScoreGenerator {
        /**
         * 獲取分數的抽象方法
         * @return 分數
         */
        public abstract int getScore();
    
        /**
         * 生成分數的抽象方法
         */
        public abstract void execute();
    
        /**
         * 初始化觀察者Observer集合
         */
        private final ArrayList<Observer> observerList = new ArrayList<>();
    
        /**
         * 添加/註冊Observer觀察者
         * @param observer
         */
        public void addObserver(Observer observer){
            observerList.add(observer);
        }
    
        /**
         * 向Observer觀察者發送通知
         */
        public void notifyObservers(){
            for (Observer observer : observerList) {
                observer.update(this);
            }
        }
        
    }
    
  • ScoreGenerator功能實現類

    public class ScoreGenerator extends AbstractScoreGenerator {
    
        /**
         * 隨機數對象
         */
        private final Random random = new Random();
    
        private int score;
    
        /**
         * 子類必須重寫父類的抽象方法
         * @return
         */
        @Override
        public int getScore() {
            return score;
        }
    
        /**
         * 子類必須重寫父類的抽象方法
         * @return
         */
        @Override
        public void execute() {
            for (int i = 0; i < 10; i++) {
                score = random.nextInt(20);
                this.notifyObservers();
            }
        }
    
    }
    
  • DigitalObserver感知類

    public class DigitalObserver implements Observer {
        /**
         * 具體的觀察者,感知被觀察者的變化,用數值的形式實現
         * @param abstractScoreGenerator
         */
        @Override
        public void update(AbstractScoreGenerator abstractScoreGenerator) {
            System.out.println("數值觀察者:" + abstractScoreGenerator.getScore());
            try {
                Thread.sleep(100);
            }catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
    
  • GraphObserver感知類

    public class GraphObserver implements Observer {
        /**
         * 具體的觀察者,感知被觀察者的變化,用圖示的形式實現
         * @param abstractScoreGenerator
         */
        @Override
        public void update(AbstractScoreGenerator abstractScoreGenerator) {
            System.out.print("圖示觀察者:");
            int count = abstractScoreGenerator.getScore();
            for (int i = 0; i < count; i++) {
                System.out.print("*");
            }
            System.out.println("");
            try {
                Thread.sleep(100);
            }catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
    
  • Main入口

    public class Main {
        public static void main(String[] args) {
            // 抽象類不可以實例化,子類可以實例化,且由於繼承關係,子類可以調用父類的普通方法
            ScoreGenerator scoreGenerator = new ScoreGenerator();
            // 介面不能實例化,但是可以通過實例化實現介面的類,從而”構造“該介面,並實現介面的抽象方法
            DigitalObserver digitalObserver = new DigitalObserver();
            GraphObserver graphObserver = new GraphObserver();
            // 註冊兩個觀察者(數值和圖示),實際上就是成生成兩個”介面對象“,目的是調用介面的具體實現
            scoreGenerator.addObserver(digitalObserver);
            scoreGenerator.addObserver(graphObserver);
            // 計算分數結果:引入兩個具體的觀察者,將本方法計算的結果調用各自的具體實現
            scoreGenerator.execute();
        }
    }
    

    運行結果(部分),如圖1-2所示:

圖1-2

1.3模式要點

  • 四種角色:觀察者(介面)、具體觀察者(介面實現類)、被觀察者(抽象類),被觀察者實現(子類)

  • 利用抽象類和介面從具體的類中抽出抽象方法

  • 將實例作為參數傳遞到類中,且不使用具體類型,而使用抽象類型或介面

  • 觀察者(Observer)不關心要觀察的對象是誰,被觀察者(Subject)也不關心是誰在觀察自己,兩者之間通過抽象類和介面產生關聯


二、策略模式

2.1基本概念

策略模式,即Strategy模式:用不同的演算法(方式)去解決同一個問題,並各個演算法間得到替換,其主要目的是通過定義相似的演算法,替換if-else寫法。

2.2Demo案例

接下來同樣還是使用一個簡單的demo來介紹策略模式。

Java 在進行數值計算的時候,會經常用到加減乘除方法。如果我們想得到兩個數字相加的和,我們需要用到“+”符號,得到相減的差,需要用到“-”符號等。

雖然我們可以通過字元串比較使用if-else寫成通用方法,但是計算的符號每次增加,我們就不得不加在原先的方法中進行增加相應的代碼,如果後續計算方法增加、修改或刪除,那麼會使後續的維護變得困難。
但是在這些方法中,我們發現其基本的加減乘除是固定的,這時我們就可以通過策略模式來進行開發,可以有效避免通過if-else來進行判斷,即使後續增加其他的計算規則也可靈活進行調整。

類和介面一覽,如表2.1所示:

表2.1
名稱 說明
Strategy 策略的對外介面,所有具體策略類都需實現該介面
CalculatorContext 策略的上下文
OperationAddition 一個策略的具體實現
OperationDivision 一個策略的具體實現
OperationMultiplication 一個策略的具體實現
Main 入口

下麵則是UML類圖,如圖2-1所示:

圖2-1
  • Strategy介面

    public interface Strategy {
        /**
         * 策略的對外介面,所有具體策略類都需實現該介面
         * @param num1
         * @param num2
         * @return
         */
        int calculate(int num1, int num2);
    }
    
  • CalculatorContext 策略上下文類

    public class CalculatorContext {
    
        private final Strategy strategy;
    
        /**
         * 介面作為類的屬性,有參構方法造,構造策略上下文對象
         * @param strategy
         */
        public CalculatorContext(Strategy strategy){
            this.strategy = strategy;
        }
    
        /**
         * 策略執行方法,調用後會直接實現對應的策略
         * @param num1
         * @param num2
         * @return
         */
        public int executeStrategy(int num1, int num2){
            return strategy.calculate(num1,num2);
        }
    }
    
  • OperationAddition 策略的具體實現類(加法)

    public class OperationAddition implements Strategy {
    
        /**
         * 表示加法的具體策略,兩數相加
         * @param num1
         * @param num2
         * @return
         */
        @Override
        public int calculate(int num1, int num2) {
            return num1 + num2;
        }
    
    }
    
  • OperationDivision 策略的具體實現類(減法)

    public class OperationDivision implements Strategy {
    
        /**
         * 表示減法的具體策略,兩數相減
         * @param num1
         * @param num2
         * @return
         */
        @Override
        public int calculate(int num1, int num2) {
            return num1 - num2;
        }
    
    }
    
  • OperationMultiplication 策略的具體實現(乘法)

    public class OperationMultiplication implements Strategy {
    
        /**
         * 表示乘法的具體策略,兩數相乘
         * @param num1
         * @param num2
         * @return
         */
        @Override
        public int calculate(int num1, int num2) {
            return num1 * num2;
        }
    
    }
    
  • Main入口

    public class Main {
        public static void main(String[] args) {
    
            // 初始化分值
            int num1 = 6;
            int num2 = 8;
    
            // 實例化上下文對象,通過實例化實現介面的某個類去生成”介面對象“,目的是調用對應實現類的抽象方法實現
            CalculatorContext calculatorContext1 = new CalculatorContext(new OperationAddition());
            System.out.println("加法策略:num1+num2= " + calculatorContext1.executeStrategy(num1, num2));
            // 通過調用每個不同實現類的不同抽象方法實現,可以解決if-else的邏輯判斷
            CalculatorContext calculatorContext2 = new CalculatorContext(new OperationDivision());
            System.out.println("減法策略:num1-num2= " + calculatorContext2.executeStrategy(num1, num2));
    		// 策略模式最核心的:客戶端可以通過實例化不同的”介面對象“去調用不同的具體實現
            CalculatorContext calculatorContext3 = new CalculatorContext(new OperationMultiplication());
            System.out.println("乘法策略:num1*num2= " + calculatorContext3.executeStrategy(num1, num2));
        }
    }
    

運行結果,如圖2-2所示:

圖2-2

2.3模式角色

主要由這3個角色組成:

  • 策略上下文角色(Context),提供給客戶端使用,持有該類的一個策略對象的引用,核心就是串聯策略介面與其具體實現。
  • 抽象策略角色(Strategy):這是一個抽象角色,通常是介面或者抽象類,給出了所有具體策略類所需的介面。
  • 具體策略角色:封裝了一些具體的實現方法(行為)或演算法。

2.4模式要點

  • 為什麼需要抽象策略角色?

    通常我們在開發的時候,具體的行為會寫在方法中,而策略模式卻將演算法與其它部分分離開,只是暴露了演算法的API介面,然後在需要使用的地方以委托的方式來使用。

    這樣看起來代碼好像變複雜了,有種脫褲子放屁的嫌疑。其實不然,當業務上有調整需要新增、修改這些演算法時,我們只需要選擇性地調用即可,就不必再修改策略角色了。最重要的是,利用委托這種弱關聯關係可以方便地整體替換演算法。


三、建造者模式

大都市中林立著許多高樓大廈,這些高樓大廈都是具有建築結構的大型建築,在英文中通常把這些建築稱為 Builder。

建造這些龐然大物時,一般難以一氣呵成。此時我們需要先建造組成這個物體的各個部分,然後再分階段將它們組裝起來。

在編寫代碼時,我們肯定遇到過新建對象、併為對象的屬性賦值的場景。下麵就介紹 Builder 建造者模式在創建對象並賦值時的作用。

3.1傳統的Builder模式

首先介紹的是更為抽象的傳統Builder模式,其核心思想在於:分階段組裝具有複雜結構的實例,並隱藏具體的組裝過程,本質還是一種調用抽象方法具體實現的思想。

需要藉助的角色有4個:需要被Builder的類、抽象的Builder類、具體實現Builder的類、調用具體實現的Director。如表3.1所示:

表3.1
名稱 說明
Computer 一個需要被建造的實體類,其屬性就是這個“建築的一部分”
ComputerBuilder 一個抽象類,定義了建造屬性的抽象方法
ConcreteComputerBuilder 一個具體的實現類,實質上就是使用setter方法為屬性賦值
Director 監督者,實質上就是調用方
Main 入口

下麵是對應的UML圖,如圖3-1所示:

圖3-1
具體的場景:模擬由中央處理器、記憶體和硬碟這3種部件組裝成一臺電腦的過程。以下是按照角色出場順序的代碼 demo 示例:
  • Computer類
@Data
public class Computer {
    /**
     * 中央處理器
     */
    private String cpu;

    /**
     * 記憶體
     */
    private String memory;

    /**
     * 硬碟
     */
    private String disk;
}
  • ComputerBuilder建造抽象類
public abstract class ComputerBuilder {

    // 創建產品對象
    protected Computer computer = new Computer();

    // 創建產品對象的各組成部件,即設置對象的屬性
    public abstract void setCpu();
    public abstract void setMemory();
    public abstract void setDisk();

    // 返回產品對象
    public Computer getComputer(){
        return computer;
    }
}
  • ConcreteComputerBuilder具體的建造過程
public class ConcreteComputerBuilder extends ComputerBuilder {

    @Override
    public void setCpu() {
        computer.setCpu("i5-7500");
    }

    @Override
    public void setMemory() {
        computer.setMemory("16GB");
    }

    @Override
    public void setDisk() {
        computer.setDisk("500GB");
    }
}
  • Director監督者調用
public class Director {

    private final ComputerBuilder computerBuilder;

    /**
     * 有參構造
     * @param builder
     */
    public Director(ComputerBuilder computerBuilder) {
        this.computerBuilder = computerBuilder;
    }

    /**
     * 調用抽象方法的具體實現
     * @return
     */
    public Computer computerConstruct() {
        // 調用 builder 的屬性設置方法
        computerBuilder.setCpu();
        computerBuilder.setMemory();
        computerBuilder.setDisk();
        // 返回組裝好的電腦
        return computerBuilder.getComputer();
    }
}
  • Main主類
public class Main {
    public static void main(String[] args) {
        ConcreteComputerBuilder builder = new ConcreteComputerBuilder();
        Director director = new Director(builder);
        Computer computer = director.computerConstruct();
        // 查看電腦信息
        System.out.println("cpu型號:"+computer.getCpu()+"," +"記憶體大小:"+computer.getMemory()+ ","+"硬碟大小:"+ computer.getDisk());
    }
}

運行結果,如圖3-2所示:

圖3-2
#### 3.2改良後的Builder模式

而改良後的Builder模式,只需要兩步就可以實現建造一個類併為其賦值的過程,同時還可以靈活地調整順序,也可以只建造一部分。

具體分為兩個部分:1、添加@Builder註解;2、鏈式調用建造。

  • 添加@Builder註解
@Builder
public class Computer {
    /**
     * 中央處理器
     */
    private String cpu;

    /**
     * 記憶體
     */
    private String memory;

    /**
     * 硬碟
     */
    private String disk;
}
  • 鏈式調用建造
public class Main {
    public static void main(String[] args) {
       Computer computer = Computer.builder()
               .cpu("R7-6800H")
               .memory("SAMSUNG 32GB")
               .disk("SSD 1TB")
               .build();
        // 查看電腦信息
        System.out.println("cpu型號:" + computer.getCpu() + ","
                         + "記憶體大小:"+ computer.getMemory() + ","
                         + "硬碟大小:"+ computer.getDisk());
    }
}

運行結果,如圖3-3所示:

圖3-3

3.3模式要點

先說說改良版的優點:

  1. 對象的創建過程更加靈活,可以選擇性的初始化對象的某些屬性,而非所有屬性;
  2. 相對通過構造函數創建對象,代碼可讀性更高:能將屬性和所賦的值關聯起來,也能清晰地知道對象的內容。

再說缺點(不算缺點的缺點):

  1. 一旦需要建造的對象屬性有變化,在鏈式調用的地方也需要同步修改。

傳統版的建造者模式,在許多優秀的開源框架中有大量地應用,由於本人的水平、認識有限,在實際的項目中很少使用到這樣的思想去創建對象並賦值。

但不乏舉出兩個顯而易見的優點:

  1. 封裝性好,實現了對象的創建與表示分離;
  2. 擴展性好,具體建造者之間相互獨立,有利於系統的解耦。

四、外觀(門面)模式

隨著時間的推移、需求的增加,程式的結構很有可能會越來越大,子系統可能會越來越多。

我們可以為項目或者程式準備一個對外的”視窗“,這樣用戶不需要關註每個類和介面之間的聯繫,只需簡單地對這個視窗提出請求即可。

4.1基本概念

facade 來源於法語單詞,原意為”建築物的正面“,使用 facade 模式,其中的 facade 角色可以讓整個系統對外只有一個簡單的API,並且還會考慮到系統內部各個類之間的依賴關係,同時按照正確的順序調用各個類。

4.2Demo案例

下麵以一個博客系統項目的設計為 demo 舉例,博客系統里主要包括博客(文章)後臺,APP端(H5)。

其中後臺又具體包括:博客編輯(用戶)、博客審核(管理員)、數據統計(點贊&收藏)、用戶列表等模塊;

下麵以博客編輯為例,看看 facade 模式在這樣的系統中扮演了什麼角色,又起到了什麼作用。

類和介面一覽,如表4.1所示:

表4.1
名稱 說明
BlogController 與前端交互的介面暴露,Restful-API 風格
BlogFacade facade 角色,BlogController 直接引用該類的對象
BlogService 抽象方法的集合
BlogServiceImpl 抽象方法的具體實現
BlogMapper 操作 MySQL 的封裝框架
Main 程式入口

在寫具體的代碼之前,我們可以先梳理一下項目的基本結構,如圖4-1所示,除了經典的 controller、service、model、mapper 外,還有一個 facade 層

大家可以重點觀察一下,看新加了 facade 後,系統內部各個類、介面之間的調用是怎麼樣的關係。如圖4-1所示:

圖4-1
下麵則是對應的UML圖,如圖4-2所示:
圖4-2
  • BlogController
@RestController
@RequestMapping("/Blog")
@Api(value = "博客介面", tags = "Blog")
public class BlogController {

    @Resource
    private BlogFacade blogFacade;

    @ApiOperation(value = "新增博客", httpMethod = "POST", response = Boolean.class)
    @PostMapping("/createBlog")
    public ResponseData createBlog(@RequestBody CreateBlogDto dto){
        Boolean boolean = blogFacade.createBlog(dto);
        return ResponseData.success(boolean);
    }

    @ApiOperation(value = "編輯博客", httpMethod = "POST", response = Boolean.class)
    @PostMapping("/updateBlog")
    public ResponseData updateBlog(@RequestBody UpdateBlogDto dto){
        Boolean boolean = blogFacade.updateBlog(dto);
        return ResponseData.success(boolean);
    }
}
  • BlogFacade
@Service
public class BlogFacade {
    /**
     * 註入interface
     */
    @Resource
    private BlogService blogService;
    /**
     * 註入interface
     */
    @Resource
    private BlogLikeService blogLikeService;
    
    /**
     * 具體實現
     */
    @Transactional(rollbackFor = Exception.class)
    @SneakyThrows
    public Boolean createBlog(CreateBlogDto dto){
        Blog blog = new Blog();
        BeanUtils.copyProperties(dto, blog);
        blog.setCreateTime(new Date());
        Integer blogId = blogService.saveBlog(blog);
    }
    
    @Transactional(rollbackFor = Exception.class)
    @SneakyThrows
    public Boolean updateBlog(UpdateBlogDto dto){
        // 具體實現
        // 略
        }
        
}

與經典的在Controller類中註入service不同,這裡引入的是facade類,在facade類中再去註入service的介面,整個facade類中做的是Controller中所需要的具體實現。

  • BlogService
public interface BlogService extends IService<Blog> {

    /**
     * 新建博客
     * @param blog
     * @return 是否成功
     */
    Integer saveBlog(Blog blog);
}
  • BlogServiceImpl
@Service
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements BlogService {

    @Resource
    private BlogMapper blogMapper;

    /**
     * 新建博客
     * @param blog
     * @return
     */
    @Override
    public Integer saveBlog(Blog blog) {
        this.save(essayManage);;
        return blog.getId();
    }

而service層中的impl實現類,只做與資料庫相關的操作。

  • BlogMapper
@Mapper
public interface BlogMapper extends BaseMapper<Blog> {
}

有的同學可能已經發現了,facade 好像只是把原來在 impl 層做的邏輯實現,放到了 facade 層里,但仍然還是要實現 service 介面的抽象方法,那還有必要再分一個facade 層出來嗎?

答案:在整個系統複雜且子系統多的時候,比較適合使用 facade 模式。

4.3模式要點

facade 模式出現的角色比較簡單,分為3個:facade 角色、系統其它角色(類或介面等)、請求方。

  • facade 角色:抽象角色,負責將請求中轉(轉發)給子系統處理
  • 系統其它角色:功能的具體實現,目的是完成內部封裝,只返回數據或結果給API;
  • 請求方:其實就是項目啟動後,調用介面的用戶,或者說是對外暴露可供調用的API;

由以上分析可知,Facade設計模式更註重從構架的層次去看整個系統,而不是單個類、介面的層次,對於各個子系統的解耦很有幫助。

解耦的重點在於:起碼從直觀上可以很明顯地發現——interface變少了,這裡的API指的是系統內部的介面,而非暴露給外部的Restful-API。

在開發的時候,如果有這麼一種模式:能讓介面變少的同時,還能讓我們專註邏輯實現,且可以方便地對外暴露請求的 facade 角色,該是多麼地美好!


五、適配器模式

5.1基本概念

常見的設計模式之一,其最核心的思想:在不改變現有系統結構的情況下,將一個類的介面轉換成用戶希望的另一個介面

5.2Demo案例

下麵介紹兩種不同實現,這兩種都是很經典的適配器模式實現。

5.2.1類適配器

本質是通過繼承的方式來實現介面的適配的,具體看代碼大家就明白了,這個繼承的妙處到底在哪裡。

背景簡述:我到香港迪士尼去游玩,晚上在酒店想給筆記本充電,但我發現香港的插座是英式三角插座,我的充電器插不進去,這個時候就可以使用適配器模式進行適配。

類與介面的關係如表5.1所示:

表5.1
名稱 說明
BritishStandard 已經存在的角色介面,是面向用戶的、最終使用的介面
ChineseStandard 也是已存在的角兒,是需要被轉換(被適配)的介面
StandardAdapter 新的角色,也是核心角色,作用是轉換介面
Main 程式入口

對應的UML圖所圖5-1所示:

圖5-1
  • BritishStandard介面
public interface BritishStandard {
    /**
     * 目標角色,用戶只適配這個介面
     * @return
     */
    String getBritishStandard();

}
  • ChineseStandard類
public class ChineseStandard {

    /**
     * 已存在的角色,但是需要被轉換才能被用戶使用
     * @return
     */
    public String getChineseStandard() {
        return "中式插座";
    }

}
  • StandardAdapter適配器類
public class StandardAdapter extends ChineseStandard implements BritishStandard {

    /**
     * 實質是通過繼承,將源方法放入目標方法中
     * @return
     */
    @Override
    public String getBritishStandard() {
        return this.getChineseStandard();
    }
}
  • 啟動入口(筆記本)
public class Notebook {

    public Boolean charge(BritishStandard britishStandard) {
        if ("中式插座".equals(britishStandard.getBritishStandard())) {
            System.out.println("充電成功!");
            return Boolean.TRUE;
        } else {
            throw new BusinessException("充電失敗!");
        }
    }

    public static void main(String[] args) {
        // 通過實例化實現介面的類來傳遞"介面對象"
        Boolean result = new Notebook().charge(new StandardAdapter());
        Assert.isTrue(result,"適配失敗!請重試");
    }

}

運行結果如圖5-2所示:

圖5-2
5.2.2對象適配器

本質上是通過構造器傳遞(委托)的方式來實現適配的,具體看代碼:

背景簡述:我的車有車載音樂播放系統,一個是播放數字音樂的介面MusicPlayer,另一個是播放CD光碟的介面CdPlayer。而我想要將CD光碟中我喜歡的音樂轉化成 mp3 的數字音樂格式來播放。這個時候就可以使用適配器模式進行適配。

類與介面的關係如表5.2所示:

表5.2
名稱 說明
MusicPlayer 已經存在的角色介面,是面向用戶的、最終使用的介面
CdPlayer 也是已存在的角兒,是需要被轉換(被適配)的介面
PlayerAdapter 新的角色,也是核心角色,作用是轉換介面
Main 程式入口

對應的UML圖所圖5-3所示:

圖5-3
  • MusicPlayer介面
public interface MusicPlayer {

    /**
     * 已存在的角色,用戶只能使用這個介面
     * @param fileName
     */
    String playMusic(String fileName);
}
  • CdPlayer類
public class CdPlayer {

    /**
     * 已存在的角色,需要被轉換才能使用
     * @param fileName
     * @return
     */
    String playCD(String fileName){
        return "播放CD歌曲"+ fileName +"成功!";
    }
}
  • PlayerAdapter適配器類
@AllArgsConstructor
public class PlayerAdapter implements MusicPlayer{

    @Resource
    private CdPlayer cdPlayer;

    /**
     * 使用有參構造的方式,傳遞對象
     * @param fileName
     * @return
     */
    @Override
    public String playMusic(String fileName) {
       return cdPlayer.playCD(fileName);
    }
}
  • 入口(播放音樂)
public class Play {
    public static void main(String[] args) {
        PlayerAdapter playerAdapter = new PlayerAdapter(new CdPlayer());
        String result = playerAdapter.playMusic("《韓寶儀-往事只能回味》");
        System.out.println(result);
        Assert.hasLength(result, "播放失敗,請重試!");
    }
}

運行結果如圖5-4所示:

圖5-4

4.3模式要點

出現的3種角色:

  • 目標角色(Target):已經存在的角色,是用戶最終需要的介面;
  • 源角色(Adaptee):需要被轉換的介面,也是已經存在的角色;
  • 適配器角色(Adapter):核心角色,通過繼承或者類關聯的方式將源角色轉換為目標角色。

優缺點分析:

類適配器

  • 優點:可以根據需求重寫 Target 的方法,使得 Adapter 的靈活性增強了。

  • 缺點:有一定局限性。因為類適配器需要繼承 Adaptee 類,而 Java 是單繼承機制,所以要求 Adaptee 必須是一個類。

對象適配器

  • 優點:同一個 Adapter 可以把 Adaptee 類和他的子類都適配到目標介面。

  • 缺點:需要重新定義 Adaptee 行為時,需要重新定義 Adaptee 的子類,並將適配器組合適配。


文章小結

到這裡5種常用的設計模式就和大家分享完了,熟練使用設計模式可以提高我們的代碼質量,且能使得我們的程式設計地更優雅,也更易讀易懂。

由於本人水平有限,對於文章有問題的地方歡迎大家指正,不吝賜教,有其它想法也可以在評論區一起交流學習。


參考文獻

  1. 《圖解設計模式》【日】結城浩 著,楊文軒 譯,中國工信出版集團,人民郵電出版社;
  2. https://www.cnblogs.com/xuwujing/p/9954263.html#5195605
  3. https://blog.csdn.net/u014454538/article/details/122377789
  4. https://blog.csdn.net/qq_36566262/article/details/124242610
  5. https://blog.csdn.net/weixin_51466332/article/details/123345199

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、升級webview版本 (1). 下載需要更新的Webview apk。如果不能翻牆可以用下載好的版本(相容32/64位):Webview-115.0.5790.138 (2). 在路徑\aosp\external\chromium-webview\prebuilt\下替換arm或arm64架構 ...
  • 本文介紹了Android13中的凍結進程功能,它是一種重要的資源管理策略,可以提高系統性能和穩定性,同時最大限度地節省設備的資源和電池消耗。 文章討論瞭如何合理分配資源,包括CPU、記憶體等,以提高設備性能和用戶體驗。此外,文章還提到了凍結進程對應用程式線程的影響,並介紹了Android13與Andr... ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 思路分析 在開始動手之前,分析一下整個功能的實現過程: 根據圖片大小創建 canvas1 畫布,並將原圖片直接定位在 canvas1 上; 在畫布上添加一個蒙層,以區分當前 canvas 圖像是被裁剪的原圖像; 在蒙層上方,對裁剪區域(鼠 ...
  • Avue 是一個基於Element-plus低代碼前端框架,它使用JSON 配置來生成頁面,可以減少頁面開發工作量,極大提升效率; 雖然Avue官網上面都有這些配置說明,但是如果剛開始接觸不熟悉框架的話需要很久才找到自己需要的參數配置,為了方便自己今後查找使用,現將一些開發中常用的配置梳理在下 一、 ...
  • 這幾天在學vue3, 用Element-plus 加 vue3 搭了個框架,在這裡記錄一下項目搭建中遇到的問題。 1、使用 Element-plus 的 icon 圖標,顯示不出來 首先,用命令行中安裝 Element-plus 的圖標: npm install @element-plus/icon ...
  • >我們是[袋鼠雲數棧 UED 團隊](http://ued.dtstack.cn/),致力於打造優秀的一站式數據中台產品。我們始終保持工匠精神,探索前端道路,為社區積累並傳播經驗價值。 >本文作者:景明 ## 升級背景 目前公司產品有關 react 的工具版本普遍較低,其中 react router ...
  • 在現代的 Web 應用程式中,頁面訪問攔截是非常重要的一個方面。它可以用於確保用戶只能訪問他們有許可權的頁面,提高應用程式的安全性和用戶體驗。本篇博文將介紹如何使用 Vue 框架來實現頁面訪問攔截的功能。 ...
  • 本文主要做推薦系統淺析,主要介紹推薦系統的定義,推薦系統的基礎框架,簡單介紹設計推薦的相關方法以及架構。適用於部分對推薦系統感興趣的同學以及有相關基礎的同學,本人水平有限,歡迎大家指正。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...