深入MyBatis原始碼,理解Java設計模式之介面卡模式
阿新 • • 發佈:2019-05-29
什麼是介面卡模式
定義:將一個系統的介面轉換成另外一種形式,從而使原來不能直接呼叫的介面變得可以呼叫。
介面卡模式角色劃分
介面卡模式涉及3個角色:
1.源(Adaptee):需要被適配的物件或型別,相當於插頭。
2.介面卡(Adapter):連線目標和源的中間物件,相當於插頭轉換器。
3.目標(Target):期待得到的目標,相當於插座。
介面卡模式包括3種形式:類介面卡模式、物件介面卡模式、介面介面卡模式(或又稱作預設介面卡模式)。
介面卡模式應用場景
1、新老版本介面的相容
2、Mybatis多種日誌框架的整合
介面卡建立的方式
1.物件介面卡(組合模式)
2.類介面卡(繼承模式)
介面卡快速入門例子
比如早期的時候V1版本訂單介面的入參為Map型別,隨著業務的更新和迭代在V2版本的時候該訂單介面的入參需要支援List的型別? 請問不改變的該介面程式碼的情況下,如何支援List型別。
1.源(Adaptee):需要被適配的物件或型別,相當於插頭。
public void froOrderMap(Map map) { for (int i = 0; i < map.size(); i++) { // 使用I作為MapKey獲取資料 String value = (String) map.get(i); System.out.println("value:" + value); } }
2.目標(Target):期待得到的目標,相當於插座。
public interface List<E> extends Collection<E> {
......
int size();
E get(int index);
E set(int index, E element);
}
3.介面卡(Adapter):連線目標和源的中間物件,相當於插頭轉換器
public class ListAdapter extends HashMap { private List list; public ListAdapter(List list) { this.list = list; } @Override public int size() { return list.size(); } @Override public Object get(Object key) { return list.get(Integer.valueOf(key.toString()).intValue()); } }
測試執行效果
public class Test {
public static void main(String[] args) {
// 1.獲取集合型別使用者
List member = new MemberService().getMember();
//new OrderService().froOrderMap(member);
ListAdapter listAdapter = new ListAdapter(member);
new OrderService().froOrderMap(listAdapter);
}
}
使用介面卡模式實現日誌收集
比如設計一個日誌收集系統,可能會考慮檔案寫入、也可能考慮寫入MQ、也可能考慮寫入資料庫等。
物件介面卡方式實現
定義基本實體類
@Data
public class LogBean {
/**
* 日誌ID
*/
private String logId;
/**
* 日誌內容
*/
private String logText;
}
1.源目標介面
public interface LogWriteFileService {
/**
* 將日誌寫入到檔案中
*/
void logWriteFile();
/**
* 從本地檔案中讀取日誌
*
* @return
*/
List<LogBean> readLogFile();
}
1.源目標介面實現類
public class LogWriteFileServiceImpl implements LogWriteFileService {
@Override
public void logWriteFile() {
System.out.println(">>>將日誌寫入檔案中...");
}
@Override
public List<LogBean> readLogFile() {
LogBean log1 = new LogBean();
log1.setLogId("0001");
log1.setLogText("Tomcat啟動成功..");
LogBean log2 = new LogBean();
log2.setLogId("0002");
log2.setLogText("Jetty啟動成功..");
List<LogBean> listArrayList = new ArrayList<LogBean>();
listArrayList.add(log1);
listArrayList.add(log2);
return listArrayList;
}
}
2.目標介面
public interface LogWriteDbService {
/**
* 將日誌寫入到資料庫中
*/
public void writeDbFile(LogBean logBean);
}
2.介面卡
//LogAdapter :介面卡
//LogWriteDbService:目標
public class LogAdapter implements LogWriteDbService {
//源介面
private LogWriteFileService logWriteFileService;
public LogAdapter(LogWriteFileService logWriteFileService) {
this.logWriteFileService = logWriteFileService;
}
@Override
public void writeDbFile(LogBean logBean) {
// 1.從檔案中讀取日誌檔案
List<LogBean> logBeans = logWriteFileService.readLogFile();
//目標
// 2.寫入到資料庫中
logBeans.add(logBean);
System.out.println(">>>將資料寫入到資料庫中..");
// 3.寫入到本地檔案中
logWriteFileService.logWriteFile();
}
}
介面卡模式優缺點
介面卡模式的優點
更好的複用性
系統需要使用現有的類,而此類的介面不符合系統的需要。那麼通過介面卡模式就可以讓這些功能得到更好的複用。
更好的擴充套件性
在實現介面卡功能的時候,可以呼叫自己開發的功能,從而自然地擴充套件系統的功能。
介面卡模式的缺點
過多的使用介面卡,會讓系統非常零亂,不易整體進行把握。比如,明明看到呼叫的是A介面,其實內部被適配成了B介面的實現,一個系統如果太多出現這種情況,無異於一場災難。因此如果不是很有必要,可以不使用介面卡,而是直接對系統進行重構。
Mbatis 日誌收集分析
Java開發中經常用到的日誌框架有很多,Log4j、Log4j2、slf4j等等,Mybatis定義了一套統一的日誌介面供上層使用,併為上述常用的日誌框架提供了相應的介面卡
在Mybatis的日誌模組中就是使用了介面卡模式。Mybatis內部在使用日誌模組時,使用了其內部介面 org.apache.ibatis.logging.Log,但是常用的日誌框架的對外介面各不相同,Mybatis為了複用和整合這些第三方日誌元件,在其日誌模組中,提供了多種Adapter,將這些第三方日誌元件對外介面適配成org.apache.ibatis.logging.Log,這樣Myabtis 就可以通過Log介面呼叫第三方日誌了
原始碼分析圖
原始碼剖析
Mybatis Log介面:相當於源介面
package org.apache.ibatis.logging;
public interface Log {
boolean isDebugEnabled();
boolean isTraceEnabled();
void error(String s, Throwable e);
void error(String s);
void debug(String s);
void trace(String s);
void warn(String s);
}
Mybatis源介面實現類
介面卡第一種
package org.apache.ibatis.logging.slf4j;
import org.apache.ibatis.logging.Log;
import org.slf4j.Logger;
//Slf4jLoggerImpl :相當於介面卡
//Log :相當於源介面
class Slf4jLoggerImpl implements Log {
//Logger:相當於目標介面
private Logger log;
public Slf4jLoggerImpl(Logger logger) {
//源=目標
log = logger;
}
介面卡第二種
package org.apache.ibatis.logging.log4j2;
import org.apache.ibatis.logging.Log;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.spi.AbstractLogger;
//Log4j2Impl :相當於介面卡
//Log :相當於源介面
public class Log4j2Impl implements Log {
private Log log;
//構造器
public Log4j2Impl(String clazz) {
//目標介面
Logger logger = LogManager.getLogger(clazz);
//判斷型別去實現:相當於目標
if (logger instanceof AbstractLogger) {
log = new Log4j2AbstractLoggerImpl((AbstractLogger) logger);
} else {
//源=目標
log = new Log4j2LoggerImpl(logger);
}
}
目標介面:Logger