前言
作者在準(zhǔn)備秋招中,學(xué)習(xí)設(shè)計(jì)模式,做點(diǎn)小筆記,用寶可夢(mèng)為場(chǎng)景舉例,有錯(cuò)誤歡迎指出。
代碼同步更新到 github ,要是點(diǎn)個(gè)Star您就是我的神
適配器模式
意圖:將一個(gè)類的接口轉(zhuǎn)換成客戶希望的另一個(gè)接口
主要解決:把現(xiàn)有對(duì)象放到新環(huán)境里,而新環(huán)境要求的接口,現(xiàn)有對(duì)象不滿足
何時(shí)使用:現(xiàn)有的類被需要,而這個(gè)類的接口不符合要求;建立一個(gè)可復(fù)用的類,用于讓彼此之間無關(guān)的類或未來可能引入的類可以一起工作
適配器模式有3個(gè)角色:
- 目標(biāo)接口 (Target):當(dāng)前系統(tǒng)業(yè)務(wù)期待的接口
- 適配者類:被適配的現(xiàn)有組件的接口
- 適配器類:一個(gè)轉(zhuǎn)換器,繼承或引用適配者對(duì)象,把適配者接口轉(zhuǎn)為目標(biāo)接口
1 情景假設(shè)
現(xiàn)在我們假設(shè)這樣一個(gè)場(chǎng)景:小智在綠寶石的存檔中,有一只巨tm強(qiáng)的裂空座,性格好,個(gè)體高,努力值也刷得完美。現(xiàn)在已經(jīng)發(fā)售了朱紫,他還想用這只裂空座,但是問題來了,綠寶石是GBA主機(jī)的游戲,朱紫是Switch主機(jī)的游戲,他們存儲(chǔ)數(shù)據(jù)的格式不同??!這怎么辦呢?那么就只能推出一個(gè)適配器,把GBA的數(shù)據(jù)轉(zhuǎn)化成Switch的數(shù)據(jù),把裂空座從綠寶石移植到朱紫,可能熟悉寶可夢(mèng)游戲的朋友已經(jīng)想到了,Pokemon Home就是干這個(gè)事的,所以我們可以把這個(gè)故事中的角色抽象為適配器模式需要的三個(gè)角色:
- 目標(biāo)接口:Switch數(shù)據(jù)
- 適配者類:GBA數(shù)據(jù)
- 適配器類:Pokemon Home
2 代碼示例
首先定義個(gè)頂層接口:
public interface Game {
}
現(xiàn)有的接口,就是老版本GBA游戲:
public interface GbaGame extends Game{
void usePokemon(String dataFormat);
}
public class EmeraldVersion implements GbaGame{
/**
* 在綠寶石中使用寶可夢(mèng)
*/
public void usePokemon(String dataFormat, String version) {
System.out.println("Go! Rayquaza! (In " + version + " Version)");
}
public void usePokemon() {
System.out.println("Go! Rayquaza! (In GBA Version )");
}
}
有綠寶石一個(gè)實(shí)現(xiàn)類,然而,現(xiàn)在游戲已經(jīng)到了朱紫版本,需要的是Switch上的數(shù)據(jù):
public interface NsGame extends Game{
void usePokemon(String dataFormat);
}
public class ScarletVersion implements NsGame{
public void usePokemon(String dataFormat){
if ("nsData".equals(dataFormat)){
// 內(nèi)置功能,使用當(dāng)前版本的寶可夢(mèng)
System.out.println("Go! Chikorita (Get In Scarlet Version)");
}
}
}
于是為了讓NS端適配GBA的數(shù)據(jù),我們需要一個(gè)適配器,這個(gè)適配器要有舊版本的屬性(因?yàn)檫m配器是為已有的類設(shè)計(jì)的)
/**
* Pokemon Home
*/
public class DataAdapter implements GbaGame{
GbaGame gbaGame; // 舊世代
public DataAdapter(GbaGame gbaGame) {
this.gbaGame = gbaGame;
}
@Override
public void usePokemon(String dataFormat) {
if("gbaData".equals(dataFormat)){
gbaGame.usePokemon(dataFormat);
}
}
}
所以,要在新版本(NsGame)中使用舊版本的數(shù)據(jù),新版本應(yīng)該是:
public class ScarletVersion implements NsGame{
DataAdapter dataAdapter;
public void usePokemon(String dataFormat){
if ("gbaData".equals(dataFormat)){
dataAdapter = new DataAdapter(new EmeraldVersion());
dataAdapter.usePokemon(dataFormat, "Switch");
}else if ("nsData".equals(dataFormat)){
// 內(nèi)置功能,使用當(dāng)前版本的寶可夢(mèng)
System.out.println("Go! Chikorita (Get In Scarlet Version)");
}
}
}
注意:這里并不違背“開閉原則”,因?yàn)榍懊娴腟carletVersion類只是為了說明邏輯,并不是對(duì)代碼進(jìn)行修改
測(cè)試類
public class AdapterDemo {
public static void main(String[] args) {
// 老版本使用
EmeraldVersion emeraldVersion = new EmeraldVersion();
emeraldVersion.usePokemon();
// 新版本使用
NsGame pokemon = new ScarletVersion();
pokemon.usePokemon("gbaData");
pokemon.usePokemon("nsData");
}
}
Go! Rayquaza! (In GBA Version )
Go! Rayquaza! (In Switch Version)
Go! Chikorita (Get In Scarlet Version)
3 擴(kuò)展
有聰明的讀者就會(huì)問了:那既然有開閉原則,那如果有新的舊版本或者新版本要加入怎么辦呢?
- 舊版本增加:比如我想把3DS上的寶可夢(mèng)拿到朱紫用,要怎么辦?
如果無視開閉原則,可以這樣修改,但其實(shí)這種情況更適用于,一開始就告訴了開發(fā)者,要適配2個(gè)舊版本。
public class DataAdapter implements GbaGame, ThreeDS{
GbaGame gbaGame; // 舊世代
ThreeDS threeDS; // 3DS世代游戲
public DataAdapter(GbaGame gbaGame, ThreeDS threeDS) {
this.gbaGame = gbaGame;
this.threeDS = threeDS;
}
@Override
public void usePokemon(String dataFormat, String newVersion) {
if("gbaData".equals(dataFormat)){
gbaGame.usePokemon(dataFormat, newVersion);
}else if("threeDS".equals(dataFormat)){
threeDS.usePokemon(dataFormat, newVersion);
}
}
}
所以正確的做法是:為ThreeDS單獨(dú)再寫一個(gè)適配器類
public ThreeDsAdapter implements ThreeDS{
//...
}
- 目標(biāo)接口的新實(shí)現(xiàn):比如劍盾版本也是NS端的,和朱紫寫一樣的邏輯即可
3 應(yīng)用
2023/08/26 更新
在學(xué)習(xí)多線程八股文的時(shí)候,新了解了FutureTask
類。關(guān)于這個(gè)類的作用,不在本文中贅述。
FutureTask
類有2個(gè)構(gòu)造方法:
class FutureTask{
private Callable<V> callable;
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
}
可見,FutureTask
的構(gòu)造方法參數(shù)可以是Callable
也可以是Runnable
的實(shí)現(xiàn)類,然而,在傳入Runnable
實(shí)現(xiàn)類時(shí),
還是對(duì)變量callable
賦值,這是一個(gè)Callable
對(duì)象。所以,無論傳入什么,最終都變成了Callable
。
點(diǎn)進(jìn)Executors.callable()
:
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<T>(task, result);
}
可以看到一個(gè)RunnableAdapter
, 一個(gè)適配器類,傳入Runnable,得到的是繼承了Callable
接口的適配器對(duì)象。
4 另一個(gè)例子
題外話,在學(xué)習(xí)這個(gè)設(shè)計(jì)模式的時(shí)候想到的,在做機(jī)器學(xué)習(xí)的時(shí)候,通常有這么個(gè)流程:
數(shù)據(jù)集 -> 數(shù)據(jù)源 -> 模型 -> …
數(shù)據(jù)集的格式各不相同,然而,模型的輸入是固定的,我們把數(shù)據(jù)集轉(zhuǎn)換成能夠進(jìn)入模型的過程通常被叫做數(shù)據(jù)預(yù)處理,這是為了讓數(shù)據(jù)集和模型輸入的格式適配。比如:
文章來源:http://www.zghlxwxcb.cn/news/detail-668389.html
每個(gè)數(shù)據(jù)集的分割符不同,那么為了提供給模型一個(gè)模型能夠接受的格式,就可以使用適配器模式,讓數(shù)據(jù)從csv
或者dat
格式轉(zhuǎn)換為data_df
,即dataframe
對(duì)象的過程,也就是適配的過程。文章來源地址http://www.zghlxwxcb.cn/news/detail-668389.html
到了這里,關(guān)于學(xué)習(xí)設(shè)計(jì)模式之適配器模式,但是寶可夢(mèng)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!