不知道大家在開發的時候,有沒有想過(遇到)這些問題: 1、大家都是按需要開發,都是一個職級的同事,為什麼有些人的思路就很清晰,代碼也很整潔、易懂;而自己開發,往往不知道怎麼下手設計,寫完了也是bug一堆,codeReview的時候更是頻頻被懟... 2、感覺每天都是CURD,寫重覆的代碼,做類似的需... ...
分享 Java 開發中常用到的設計模式(一)
前言
不知道大家在開發的時候,有沒有想過(遇到)這些問題:
- 大家都是按需要開發,都是一個職級的同事,為什麼有些人的思路就很清晰,代碼也很整潔、易懂;而自己開發,往往不知道怎麼下手設計,寫完了也是bug一堆,codereview的時候更是頻頻被懟...
- 感覺每天都是CURD,寫重覆的代碼,做類似的需求,怎麼才能提高自己的水平?
- 每每看到大佬的代碼,或者優秀框架的源碼,總是似懂非懂,懷疑自己是不是缺少了哪些知識?
如果你有這些問題,或者思考過這些問題,那麼你起碼意識到了自己的不足,這其實是沒有熟練掌握軟體開發中的重要技能-設計模式而導致的。
先大致來看一下關於設計模式的思維導圖,包括六大軟體開發原則、設計模式分類以及23種設計模式的名稱:
![](https://img2023.cnblogs.com/blog/2458865/202307/2458865-20230731183700843-1890062854.png)
作為軟體開發人員,看著這些設計模式的名稱,你是否多少會有一些印象呢?
今天就和大家一起分享一下幾個在 Java 開發中常見的設計模式,包括具體的應用場景、代碼 demo 以及 UML 圖解等。
![](https://img2023.cnblogs.com/blog/2458865/202308/2458865-20230809102736460-416350711.png)
一、觀察者模式
1.1基本概念
觀察者模式,即Observer模式,顧名思義,指的是:當被觀察對象發生變化時,會告知觀察者。適用於根據對象狀態進行相應處理的場景。
1.2Demo案例
下麵用一個demo來直觀地展示觀察者模式的實際應用場景:觀察者將會觀察一個能生成分數的對象,並且觀察者還可以通過觀察,得到不同形式的分數結果(數字形式、圖示形式)。
類和介面一覽,如表1.1所示:
名稱 | 說明 |
---|---|
Observer | 觀察者的介面,感知來自被觀察對象的狀態變化 |
AbstractScoreGenerator | 被觀察者的抽象類 |
ScoreGenerator | 被觀察者的具體功能實現 |
DigitalObserver | 具體的觀察者,感知被觀察者的變化,用數值的形式實現 |
GraphObserver | 具體的觀察者,感知被觀察者的變化,用圖示的形式實現 |
Main | 入口 |
介面與各個類的類圖,如圖1-1所示:
![](https://img2023.cnblogs.com/blog/2458865/202307/2458865-20230731183748680-2146419028.png)
-
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所示:
![](https://img2023.cnblogs.com/blog/2458865/202307/2458865-20230731183859071-322908669.png)
1.3模式要點
-
四種角色:觀察者(介面)、具體觀察者(介面實現類)、被觀察者(抽象類),被觀察者實現(子類)
-
利用抽象類和介面從具體的類中抽出抽象方法
-
將實例作為參數傳遞到類中,且不使用具體類型,而使用抽象類型或介面
-
觀察者(Observer)不關心要觀察的對象是誰,被觀察者(Subject)也不關心是誰在觀察自己,兩者之間通過抽象類和介面產生關聯
二、策略模式
2.1基本概念
策略模式,即Strategy模式:用不同的演算法(方式)去解決同一個問題,並各個演算法間得到替換,其主要目的是通過定義相似的演算法,替換if-else寫法。
2.2Demo案例
接下來同樣還是使用一個簡單的demo來介紹策略模式。
Java 在進行數值計算的時候,會經常用到加減乘除方法。如果我們想得到兩個數字相加的和,我們需要用到“+”符號,得到相減的差,需要用到“-”符號等。
雖然我們可以通過字元串比較使用if-else寫成通用方法,但是計算的符號每次增加,我們就不得不加在原先的方法中進行增加相應的代碼,如果後續計算方法增加、修改或刪除,那麼會使後續的維護變得困難。
但是在這些方法中,我們發現其基本的加減乘除是固定的,這時我們就可以通過策略模式來進行開發,可以有效避免通過if-else來進行判斷,即使後續增加其他的計算規則也可靈活進行調整。
類和介面一覽,如表2.1所示:
名稱 | 說明 |
---|---|
Strategy | 策略的對外介面,所有具體策略類都需實現該介面 |
CalculatorContext | 策略的上下文 |
OperationAddition | 一個策略的具體實現 |
OperationDivision | 一個策略的具體實現 |
OperationMultiplication | 一個策略的具體實現 |
Main | 入口 |
下麵則是UML類圖,如圖2-1所示:
![](https://img2023.cnblogs.com/blog/2458865/202307/2458865-20230731183942871-359861766.png)
-
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所示:
![](https://img2023.cnblogs.com/blog/2458865/202307/2458865-20230731184012554-441207576.png)
2.3模式角色
主要由這3個角色組成:
- 策略上下文角色(Context),提供給客戶端使用,持有該類的一個策略對象的引用,核心就是串聯策略介面與其具體實現。
- 抽象策略角色(Strategy):這是一個抽象角色,通常是介面或者抽象類,給出了所有具體策略類所需的介面。
- 具體策略角色:封裝了一些具體的實現方法(行為)或演算法。
2.4模式要點
-
為什麼需要抽象策略角色?
通常我們在開發的時候,具體的行為會寫在方法中,而策略模式卻將演算法與其它部分分離開,只是暴露了演算法的API介面,然後在需要使用的地方以委托的方式來使用。
這樣看起來代碼好像變複雜了,有種脫褲子放屁的嫌疑。其實不然,當業務上有調整需要新增、修改這些演算法時,我們只需要選擇性地調用即可,就不必再修改策略角色了。最重要的是,利用委托這種弱關聯關係可以方便地整體替換演算法。
三、建造者模式
大都市中林立著許多高樓大廈,這些高樓大廈都是具有建築結構的大型建築,在英文中通常把這些建築稱為 Builder。
建造這些龐然大物時,一般難以一氣呵成。此時我們需要先建造組成這個物體的各個部分,然後再分階段將它們組裝起來。
在編寫代碼時,我們肯定遇到過新建對象、併為對象的屬性賦值的場景。下麵就介紹 Builder 建造者模式在創建對象並賦值時的作用。
3.1傳統的Builder模式
首先介紹的是更為抽象的傳統Builder模式,其核心思想在於:分階段組裝具有複雜結構的實例,並隱藏具體的組裝過程,本質還是一種調用抽象方法具體實現的思想。
需要藉助的角色有4個:需要被Builder的類、抽象的Builder類、具體實現Builder的類、調用具體實現的Director。如表3.1所示:
名稱 | 說明 |
---|---|
Computer | 一個需要被建造的實體類,其屬性就是這個“建築的一部分” |
ComputerBuilder | 一個抽象類,定義了建造屬性的抽象方法 |
ConcreteComputerBuilder | 一個具體的實現類,實質上就是使用setter方法為屬性賦值 |
Director | 監督者,實質上就是調用方 |
Main | 入口 |
下麵是對應的UML圖,如圖3-1所示:
![](https://img2023.cnblogs.com/blog/2458865/202308/2458865-20230803164347091-1801686988.png)
- 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所示:
![](https://img2023.cnblogs.com/blog/2458865/202308/2458865-20230803164423176-211970630.png)
而改良後的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所示:
![](https://img2023.cnblogs.com/blog/2458865/202308/2458865-20230803164500073-310188837.png)
3.3模式要點
先說說改良版的優點:
- 對象的創建過程更加靈活,可以選擇性的初始化對象的某些屬性,而非所有屬性;
- 相對通過構造函數創建對象,代碼可讀性更高:能將屬性和所賦的值關聯起來,也能清晰地知道對象的內容。
再說缺點(不算缺點的缺點):
- 一旦需要建造的對象屬性有變化,在鏈式調用的地方也需要同步修改。
傳統版的建造者模式,在許多優秀的開源框架中有大量地應用,由於本人的水平、認識有限,在實際的項目中很少使用到這樣的思想去創建對象並賦值。
但不乏舉出兩個顯而易見的優點:
- 封裝性好,實現了對象的創建與表示分離;
- 擴展性好,具體建造者之間相互獨立,有利於系統的解耦。
四、外觀(門面)模式
隨著時間的推移、需求的增加,程式的結構很有可能會越來越大,子系統可能會越來越多。
我們可以為項目或者程式準備一個對外的”視窗“,這樣用戶不需要關註每個類和介面之間的聯繫,只需簡單地對這個視窗提出請求即可。
4.1基本概念
facade 來源於法語單詞,原意為”建築物的正面“,使用 facade 模式,其中的 facade 角色可以讓整個系統對外只有一個簡單的API,並且還會考慮到系統內部各個類之間的依賴關係,同時按照正確的順序調用各個類。
4.2Demo案例
下麵以一個博客系統項目的設計為 demo 舉例,博客系統里主要包括博客(文章)後臺,APP端(H5)。
其中後臺又具體包括:博客編輯(用戶)、博客審核(管理員)、數據統計(點贊&收藏)、用戶列表等模塊;
下麵以博客編輯為例,看看 facade 模式在這樣的系統中扮演了什麼角色,又起到了什麼作用。
類和介面一覽,如表4.1所示:
名稱 | 說明 |
---|---|
BlogController | 與前端交互的介面暴露,Restful-API 風格 |
BlogFacade | facade 角色,BlogController 直接引用該類的對象 |
BlogService | 抽象方法的集合 |
BlogServiceImpl | 抽象方法的具體實現 |
BlogMapper | 操作 MySQL 的封裝框架 |
Main | 程式入口 |
在寫具體的代碼之前,我們可以先梳理一下項目的基本結構,如圖4-1所示,除了經典的 controller、service、model、mapper 外,還有一個 facade 層。
大家可以重點觀察一下,看新加了 facade 後,系統內部各個類、介面之間的調用是怎麼樣的關係。如圖4-1所示:
![](https://img2023.cnblogs.com/blog/2458865/202308/2458865-20230808103448489-2109553403.png)
![](https://img2023.cnblogs.com/blog/2458865/202308/2458865-20230808103539305-1500026934.png)
- 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所示:
名稱 | 說明 |
---|---|
BritishStandard | 已經存在的角色介面,是面向用戶的、最終使用的介面 |
ChineseStandard | 也是已存在的角兒,是需要被轉換(被適配)的介面 |
StandardAdapter | 新的角色,也是核心角色,作用是轉換介面 |
Main | 程式入口 |
對應的UML圖所圖5-1所示:
![](https://img2023.cnblogs.com/blog/2458865/202308/2458865-20230809101040747-664118012.png)
- 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所示:
![](https://img2023.cnblogs.com/blog/2458865/202308/2458865-20230809101123202-894540777.png)
5.2.2對象適配器
本質上是通過構造器傳遞(委托)的方式來實現適配的,具體看代碼:
背景簡述:我的車有車載音樂播放系統,一個是播放數字音樂的介面MusicPlayer,另一個是播放CD光碟的介面CdPlayer。而我想要將CD光碟中我喜歡的音樂轉化成 mp3 的數字音樂格式來播放。這個時候就可以使用適配器模式進行適配。
類與介面的關係如表5.2所示:
名稱 | 說明 |
---|---|
MusicPlayer | 已經存在的角色介面,是面向用戶的、最終使用的介面 |
CdPlayer | 也是已存在的角兒,是需要被轉換(被適配)的介面 |
PlayerAdapter | 新的角色,也是核心角色,作用是轉換介面 |
Main | 程式入口 |
對應的UML圖所圖5-3所示:
![](https://img2023.cnblogs.com/blog/2458865/202308/2458865-20230809101203877-452049435.png)
- 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所示:
![](https://img2023.cnblogs.com/blog/2458865/202308/2458865-20230809101247856-72453987.png)
4.3模式要點
出現的3種角色:
- 目標角色(Target):已經存在的角色,是用戶最終需要的介面;
- 源角色(Adaptee):需要被轉換的介面,也是已經存在的角色;
- 適配器角色(Adapter):核心角色,通過繼承或者類關聯的方式將源角色轉換為目標角色。
優缺點分析:
類適配器
-
優點:可以根據需求重寫 Target 的方法,使得 Adapter 的靈活性增強了。
-
缺點:有一定局限性。因為類適配器需要繼承 Adaptee 類,而 Java 是單繼承機制,所以要求 Adaptee 必須是一個類。
對象適配器
-
優點:同一個 Adapter 可以把 Adaptee 類和他的子類都適配到目標介面。
-
缺點:需要重新定義 Adaptee 行為時,需要重新定義 Adaptee 的子類,並將適配器組合適配。
文章小結
到這裡5種常用的設計模式就和大家分享完了,熟練使用設計模式可以提高我們的代碼質量,且能使得我們的程式設計地更優雅,也更易讀易懂。
由於本人水平有限,對於文章有問題的地方歡迎大家指正,不吝賜教,有其它想法也可以在評論區一起交流學習。
參考文獻
- 《圖解設計模式》【日】結城浩 著,楊文軒 譯,中國工信出版集團,人民郵電出版社;
- https://www.cnblogs.com/xuwujing/p/9954263.html#5195605
- https://blog.csdn.net/u014454538/article/details/122377789
- https://blog.csdn.net/qq_36566262/article/details/124242610
- https://blog.csdn.net/weixin_51466332/article/details/123345199