適配器模式
設計意圖
將一個類的接口轉換成你希望的另外一個接口。適配器模式使得原本由于接口不兼容而不能一起工作的那些類可以一起工作。
適用場景
- 我們有個專利續(xù)費的功能,分為單個續(xù)費和批量續(xù)費,單個續(xù)費的API接口在很早之前就開發(fā)的了,最新需求是批量續(xù)費功能,而我們批量續(xù)費是支持單個專利號的續(xù)費。這個時候呢,APP的單個續(xù)費功能早已上線,且不能影響早期版本的APP的單個專利續(xù)費功能(也是因為批量續(xù)費內部處理的穩(wěn)定性也比單個得到了提升),但需要將單個續(xù)費計劃遷移到批量續(xù)費的邏輯,但是早期的單個專利續(xù)費和現(xiàn)在的專利續(xù)費返回參數(shù)也有部分不同。在這種情況下,我們就要對單個續(xù)費進行批量續(xù)費的兼容,此時,就可以采用適配器設計模式。(業(yè)務型適配,重構);
思路和因素
- 誰要被適配,適配器接口就繼承誰,因為我們要始終保持被適配對象不變;
- 適配器模式一般不是在初次設計就設計好的,一般在后期項目維護或者重構中才會用到;
步驟
- 聲明一個中間類,我們下文稱為適配器;
- 讓適配器繼承被適配類的接口;
- 在適配器中聲明一個屬性,類型為適配類的接口類型。(屬性代理手法)
上代碼
- 我們先定義兩個已存在且需要被兼容的接口和實現(xiàn)
① 被適配類:
/**
* 單個專利續(xù)費接口(舉例說明,實際業(yè)務不是這么簡單幾個接口)
*/
public interface SinglePatent {
/**
* 專利的續(xù)費年限獲取
*
* @param pId 一個專利號
*/
String[] getYear(String pId);
/**
* 專利的續(xù)費金額計算
*
* @param pId 專利號
* @param years 多年數(shù)組
* @return 總金額
*/
double getRenewalAmount(String pId, String[] years);
}
// 接口的實現(xiàn)
public class SinglePatentImpl implements SinglePatent {
@Override
public String[] getYear(String pId) {
return new String[]{"5", "6", "7"}; // 假設那個專利號有這些年份
}
@Override
public double getRenewalAmount(String pId, String[] years) {
Map<String, Double> pId_data = bigData.apply(pId);// 從數(shù)據(jù)中心獲取數(shù)據(jù)
double count = 0D;
for (String year : years) {
count += pId_data.getOrDefault(year, 0D);
}
return count;
}
// 模擬,當作數(shù)據(jù)庫或數(shù)據(jù)中心,我要調用它
private static final Function<String, Map<String, Double>> bigData =
(String id) -> {
Map<String, Double> data = new HashMap<>(); // 模擬續(xù)費年份價格
data.put("5", 650.0D);
data.put("6", 900.0D);
data.put("7", 1200.0D);
return data;
};
}
② 適配類:
/**
* 批量專利續(xù)費接口(舉例說明,實際業(yè)務不是這么簡單幾個接口)
*/
public interface BatchPatent {
/**
* 專利的續(xù)費年限獲取
*
* @param pIds 專利號
* @return 專利號和年份
*/
Map<String, List<String>> getYears(String[] pIds);
/**
* 專利的續(xù)費金額計算
*
* @param pIdAndYears 專利號和年份
* @return 專利號和金額
*/
Map<String, Double> getRenewalAmounts(Map<String, List<String>> pIdAndYears);
}
// 接口的實現(xiàn)
public class BatchPatentImpl implements BatchPatent {
@Override
public Map<String, List<String>> getYears(String[] pIds) {
Map<String, List<String>> resultMap = new HashMap<>(4);
resultMap.put("pid_0", Arrays.asList("1", "2"));
resultMap.put("pid_1", Arrays.asList("2", "3", "4"));
resultMap.put("pid_2", Arrays.asList("5", "6"));
return resultMap;
}
@Override
public Map<String, Double> getRenewalAmounts(Map<String, List<String>> pIdAndYears) {
Map<String, Double> resultMap = new HashMap<>(4);
pIdAndYears.forEach((pId, years) -> {
Map<String, Double> pId_data = bigData.apply(pId);// 從數(shù)據(jù)中心獲取數(shù)據(jù)
double count = 0D;
for (String year : years) {
count += pId_data.getOrDefault(year, 0D);
}
resultMap.put(pId, count);
});
return resultMap;
}
// 模擬,當作數(shù)據(jù)庫或數(shù)據(jù)中心,我要調用它
private static final Function<String, Map<String, Double>> bigData =
(String id) -> {
Map<String, Double> data = new HashMap<>(); // 模擬續(xù)費年份價格,共用一份數(shù)據(jù),避免講述復雜
data.put("1", 300.0D);
data.put("2", 400.0D);
data.put("3", 450.0D);
data.put("4", 500.0D);
data.put("5", 650.0D);
data.put("6", 900.0D);
return data;
};
}
- 準備一個適配類:聲明一個中間類,并繼承被適配類的接口,同時去注入一個需要接口類型的屬性
/**
* 單個到批量的適配器接口(我這里額外把他封裝了一個接口)
*/
public interface SingleToBatchAdapter extends SinglePatent {
}
// 聲明一個中間類,我們下文稱為適配器,在這里SingleToBatchAdapterImpl代表適配器
public class SingleToBatchAdapterImpl implements SingleToBatchAdapter { // 讓適配器繼承被適配類的接口
// 在適配器中聲明一個屬性,類型為適配類的接口類型
private final BatchPatent batchPatent; // 也可以采用spring注入
public SingleToBatchAdapterImpl(BatchPatent batchPatent) {
this.batchPatent = batchPatent;
}
@Override
public String[] getYear(String pId) {
Map<String, List<String>> years = batchPatent.getYears(new String[]{pId});
return years.get(pId).toArray(new String[]{});
}
@Override
public double getRenewalAmount(String pId, String[] years) {
// 構建適配類的入?yún)? Map<String, List<String>> pIdAndYears = new HashMap<>(2);
pIdAndYears.put(pId, new ArrayList<>(Arrays.asList(years)));
// 進行適配
Map<String, Double> renewalAmounts = batchPatent.getRenewalAmounts(pIdAndYears);
// 處理適配類的返回
return renewalAmounts.get(pId);
}
}
- 測試適配器能否正常工作:日常編寫測試類是重構的必備技能
// 這里直接對適配器測試的
public static void main(String[] args) {
BatchPatentImpl batchPatent = new BatchPatentImpl();
SingleToBatchAdapter singleToBatchAdapter = new SingleToBatchAdapterImpl(batchPatent);
String[] pid_0s = singleToBatchAdapter.getYear("pid_1");// 這里定義的默認值
System.out.println(new ArrayList<>(Arrays.asList(pid_0s)));
double amount = singleToBatchAdapter.getRenewalAmount("pid_1", pid_0s);
System.out.println(amount);
}
- 重構 被適配類代碼:
這里分為兩種:
- 一種是適配器適配了所有的邏輯,那就直接將適配器當作新邏輯,直接將上層代碼指向適配器(或直接繼承父類),但是我個人不推薦;
- 另一種是你原來的被適配類里面還有一些前置邏輯需要被處理,適配的是部分邏輯,則需要將適配器也注入到原來的被適配器類;
// 第一種就形式上面的測試方法,就忽略了
// 第二種,重構后
public class SinglePatentImpl implements SinglePatent {
private final SingleToBatchAdapter singleToBatchAdapter = new SingleToBatchAdapterImpl(new BatchPatentImpl());
/**
* 專利的續(xù)費年限獲取
*
* @param pId 一個專利號
*/
@Override
public String[] getYear(String pId) {
// TODO 這里還可以做一些校驗等處理,coding。。。
String[] year = singleToBatchAdapter.getYear(pId);
// TODO 這里還可以做一些原本具有的結果等處理,coding。。。
return year;
}
/**
* 專利的續(xù)費金額計算
*
* @param pId 專利號
* @param years 多年數(shù)組
* @return 總金額
*/
@Override
public double getRenewalAmount(String pId, String[] years) {
return singleToBatchAdapter.getRenewalAmount(pId, years);
}
}
- 再次測試業(yè)務主類,直到?jīng)]有變動原有的兼容性,完成