1.概述
適配器模式是一種結(jié)構(gòu)型設(shè)計(jì)模式,它提供了一個(gè)中間層,通過這個(gè)中間層,客戶端可以使用統(tǒng)一的接口與具有不同接口的類進(jìn)行交互,也就是說,將一個(gè)接口轉(zhuǎn)換成客戶期望的另一個(gè)接口,使得原本不兼容的接口能夠協(xié)同工作。
舉個(gè)現(xiàn)實(shí)中的例子,我們現(xiàn)在的很多輕薄筆記本為了減少厚度,一般不會(huì)設(shè)計(jì)網(wǎng)線的接口,或者說在筆記本上的可以插線的接口很少,這時(shí)候使用到的拓展塢就可以視為是一種適配器。
值得一提的是,與其他模式有點(diǎn)不同的是,適配器模式是一種補(bǔ)償模式,主要用于解決現(xiàn)有的設(shè)計(jì)或?qū)崿F(xiàn)與需求不匹配的問題,是在系統(tǒng)開發(fā)后期或者集成階段,為了兼容已存在的、難以修改的組件接口或者為了統(tǒng)一不同接口之間的差異而采取的一種補(bǔ)救措施。
2.兩種適配器模式
適配器模式有兩種常見的實(shí)現(xiàn)形式:
- 類適配器:通過繼承的方式,子類(適配器)繼承自需要被適配的類(適配者),并同時(shí)實(shí)現(xiàn)目標(biāo)接口。
- 對(duì)象適配器:通過組合的方式,適配器包含一個(gè)適配者的實(shí)例,并在自己的方法中調(diào)用適配者的功能來實(shí)現(xiàn)目標(biāo)接口的方法。
2.1.類適配器
按照上面的描述,類圖如下:
-
Target
:目標(biāo)接口,適配器將不適用于當(dāng)前系統(tǒng)的接口,轉(zhuǎn)換給目標(biāo)接口的形狀。 -
Adaptee
:被適配的類,也就是被轉(zhuǎn)換的對(duì)象。 -
Adapter
:適配器
Client
調(diào)用適配器,獲取到轉(zhuǎn)換成符合當(dāng)前系統(tǒng)要求的數(shù)據(jù)。
這么看可能有點(diǎn)抽象,我們通過一個(gè)簡(jiǎn)單的業(yè)務(wù)場(chǎng)景來理解一下這種模式:
有一個(gè)客服系統(tǒng),在了解到客戶的需求后會(huì)往客戶管理系統(tǒng)中推送線索,并且在客服系統(tǒng)中可以查看到銷售對(duì)當(dāng)前客戶的跟進(jìn)情況。現(xiàn)在由于舊客戶管理系統(tǒng)日漸不滿足使用要求了,于是建立了一個(gè)新的客戶管理系統(tǒng),客服系統(tǒng)需要從新的系統(tǒng)中獲取到跟進(jìn)數(shù)據(jù)。
但是,新舊兩個(gè)客戶系統(tǒng)對(duì)于跟進(jìn)日志的接口定義不一樣,這時(shí)候又不想對(duì)客服系統(tǒng)做大的改動(dòng),就可以使用適配器對(duì)接口進(jìn)行轉(zhuǎn)換,下面是簡(jiǎn)化過后的代碼。
- 舊系統(tǒng)的接口定義
/** * 跟進(jìn)記錄對(duì)象 */ @Data public class Record { /** * 跟進(jìn)內(nèi)容 */ private String followContent; /** * 附件地址 */ private String enclosure; } /** * 目標(biāo)接口 */ public interface RecordService { Record getRecord(); } /** * 目標(biāo)接口實(shí)現(xiàn) */ public class RecordServiceImpl implements RecordService { @Override public Record getRecord() { // 模擬從老系統(tǒng)獲取數(shù)據(jù) Record record = new Record(); record.setFollowContent("跟進(jìn)內(nèi)容(老系統(tǒng))"); record.setEnclosure("附件地址(老系統(tǒng))"); return record; } }
- 新系統(tǒng)中的定義
/**
* 新系統(tǒng)溝通記錄對(duì)象
*/
@Data
public class NewRecord {
/**
* 溝通內(nèi)容
*/
private String communicateContent;
/**
* 附件地址
*/
private String accessory;
}
/**
* 新系統(tǒng)接口
*/
public interface NewRecordService {
NewRecord getNewRecord();
}
/**
* 新系統(tǒng)接口實(shí)現(xiàn)
*/
public class newRecordServiceImpl implements NewRecordService{
@Override
public NewRecord getNewRecord() {
// 模擬從新系統(tǒng)獲取數(shù)據(jù)
NewRecord newRecord = new NewRecord();
newRecord.setCommunicateContent("跟進(jìn)內(nèi)容(新系統(tǒng))");
newRecord.setAccessory("附件地址(新系統(tǒng))");
return newRecord;
}
}
實(shí)際情況相對(duì)于上面的代碼可能會(huì)更加復(fù)雜,這里做演示就簡(jiǎn)化了一下代碼,新舊系統(tǒng)主要是返回對(duì)象不一樣,返回對(duì)象中的字段名不一樣。
確定了之后編寫適配器,按照類適配器的定義,我們先繼承新接口的實(shí)現(xiàn),再實(shí)現(xiàn)舊的模板接口:
public class RecordAdapter extends newRecordServiceImpl implements RecordService {
@Override
public Record getRecord() {
NewRecord newRecord = super.getNewRecord();
Record record = new Record();
record.setFollowContent(newRecord.getCommunicateContent());
record.setEnclosure(newRecord.getAccessory());
return record;
}
}
在適配器中,實(shí)現(xiàn)舊接口中的方法,并調(diào)用父類(新接口實(shí)現(xiàn))的新的方法,然后將新接口返回的對(duì)象值封裝到舊的日志對(duì)象中,做一下測(cè)試:
public class RecordAdapterTest {
@Test
public void testGetRecord() {
RecordService recordService = new RecordServiceImpl();
System.out.println(recordService.getRecord());
recordService = new RecordAdapter();
System.out.println(recordService.getRecord());
}
}
Record(followContent=跟進(jìn)內(nèi)容(老系統(tǒng)), enclosure=附件地址(老系統(tǒng)))
Record(followContent=跟進(jìn)內(nèi)容(新系統(tǒng)), enclosure=附件地址(新系統(tǒng)))
可以看到,在字段名沒變動(dòng)的情況下,兼容了新系統(tǒng)的值。
通過適配器的方式,不需要修改新系統(tǒng)的接口,也不需要修改客服系統(tǒng)的上層業(yè)務(wù)代碼,只需要在獲取數(shù)據(jù)這一層做一下轉(zhuǎn)換即可,返回給前端后,前端也不需要重新匹配字段,減少了代碼的修改范圍,降低了風(fēng)險(xiǎn)。
類適配器對(duì)這種簡(jiǎn)單的轉(zhuǎn)換用起來比較方便,但是也存在比較大的缺陷:
- 繼承帶來的常見問題,父類發(fā)生變化時(shí),子類可能也需要被迫的跟著變化。
- 對(duì)于Java這樣的單繼承語言來說,面對(duì)有多個(gè)需要被轉(zhuǎn)換的對(duì)象時(shí),就顯得有點(diǎn)力不從心了。
所以,在大部分情況下,尤其是使用Java語言的情況下,更建議使用對(duì)象適配器。
2.2.對(duì)象適配器
相對(duì)于類適配器,對(duì)象適配器能提供更高的靈活性和更低的耦合度,原理上也比較簡(jiǎn)單,就是將繼承修改為組合,也就是這樣。
將上面的適配器代碼做一下修改,如下:
public class RecordAdapter2 implements RecordService {
private NewRecordService newRecordService;
public RecordAdapter2(NewRecordService newRecordService) {
this.newRecordService = newRecordService;
}
@Override
public Record getRecord() {
NewRecord newRecord = newRecordService.getNewRecord();
Record record = new Record();
record.setFollowContent(newRecord.getCommunicateContent());
record.setEnclosure(newRecord.getAccessory());
return record;
}
}
在創(chuàng)建對(duì)象適配器時(shí),將被適配的對(duì)象直接傳入到適配器中即可,如果是Spring
的服務(wù),這些被適配的對(duì)象還可以自動(dòng)依賴注入,也很方便。
使用對(duì)象適配器時(shí),可以注入多個(gè)Adaptee
,當(dāng)目標(biāo)對(duì)象需要的數(shù)據(jù)需要從多個(gè)不同的接口中查詢出來再做聚合的時(shí)候,就可以使用這種方式來處理。用上面的代碼來說就是,跟進(jìn)記錄和附件地址需要從不同的接口進(jìn)行查詢的時(shí)候。
3.總結(jié)
本篇主要是將適配器模式的使用,作為一種補(bǔ)償模式,不建議一開始就使用適配器模式,如果能夠在設(shè)計(jì)初期盡可能的避免出現(xiàn)接口不兼容的情況,那么直接設(shè)計(jì)出符合需求的標(biāo)準(zhǔn)接口會(huì)更優(yōu)。但畢竟沒有完美的設(shè)計(jì),當(dāng)設(shè)計(jì)上存在一定的缺陷又沒有資源做大重構(gòu)的時(shí)候,適配器模式就派上用場(chǎng)了。
對(duì)于類適配器和對(duì)象適配器,區(qū)別就是在于類適配器通過繼承實(shí)現(xiàn),對(duì)象適配器通過組合實(shí)現(xiàn),對(duì)于Java
這樣的單繼承語言,更建議使用對(duì)象適配器,更加靈活。文章來源:http://www.zghlxwxcb.cn/news/detail-836574.html
再補(bǔ)充一下適配器的一些使用場(chǎng)景:文章來源地址http://www.zghlxwxcb.cn/news/detail-836574.html
- 替換依賴的系統(tǒng):例如上面的那個(gè)例子
- 接入第三方庫或API:第三方的API或接口設(shè)計(jì)我們不能控制,可以用適配器將傳輸?shù)膱?bào)文轉(zhuǎn)換成我們系統(tǒng)中的形式來落庫。
- 整合多個(gè)接口設(shè)計(jì):例如一個(gè)短信服務(wù)中,對(duì)于短信發(fā)送的內(nèi)容要做敏感詞、黑詞過濾,我們有自己的規(guī)則,不同的短信服務(wù)商也有自己的規(guī)則,可以用適配器將不同的規(guī)則整合起來,方便統(tǒng)一調(diào)用。
- ……
到了這里,關(guān)于【設(shè)計(jì)模式】使用適配器模式做補(bǔ)償設(shè)計(jì)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!