一般做業務開發,不太容易有大量使用設計模式的場景。這裡總結一下在業務開發中使用較為頻繁的設計模式。當然語言為Java,基於Spring框架。
1 介面卡模式(Adapter Pattern)
已存在的介面、服務,跟我們所需、目的介面不相容時,我們需要透過一定的方法將二者進行相容適配。一個常見的例子,家用電源(國標)220V,而手機標準輸入一般為5V,此時我們便需要一個介面卡來將220V轉換為5V使用。
介面卡模式一般有3個角色:
- Target: 目標介面
- Adaptee: 需要進行適配的類(受改造者)
- Adapter: 介面卡(將Adaptee轉為Target)
這個出現的場景其實挺多,但實際完全按照介面卡模式編寫程式碼的場景可能並不多。簡單業務場景,直接就將適配、相容程式碼混雜在業務程式碼中了。並沒有將其摘出來處理。大部分業務程式碼,可能後續並不會再做擴充套件之類,過度設計反而會降低可讀性並增加程式碼的複雜性。
介面卡模式一般分為類介面卡
和物件介面卡
。
類介面卡的話,使用繼承方式實現:class Adapter extends Adaptee implements Target
;而物件介面卡的話,則使用組合方式實現。這種情況更靈活一些。畢竟大家都推薦多用組合少用繼承。
在學生髮生約課完課等事件時,我們需要將部分資料同步到外部CRM系統中。課程的話,按班級型別分為:1v1,小班課、大班課。不同的班級型別課程資料有所不同。事件上報時,並不是全量資料,有些資料需要消費者按需查詢。如課程課程編號、名稱、預約上課時間等。
不同班級型別的課程由三個不同的FeignClient(Adaptee)提供服務,而我們想要的就是查詢課程相關資訊(Target)。
為了模擬服務提供者,我們Mock如下服務。
@Data
@Builder
public class OneClass {
// 課程編號
private String lessonNo;
// 課程名稱
private String lessonName;
// 其他資訊
private String one;
}
@Data
@Builder
public class SmallClass {
// 課程編號
private String lessonNo;
// 課程名稱
private String lessonName;
// 其他資訊
private String small;
}
@Data
@Builder
public class BigClass {
// 課程編號
private String lessonNo;
// 課程名稱
private String lessonName;
// 其他資訊
private String big;
}
public interface RemoteClassClient {
default OneClass getOne() {
return OneClass.builder().lessonNo("one").lessonName("1V1").build();
}
default SmallClass getSmall() {
return SmallClass.builder().lessonNo("small").lessonName("小班課").build();
}
default BigClass getBig() {
return BigClass.builder().lessonNo("big").lessonName("大班課").build();
}
}
public class RemoteClassClientImpl implements RemoteClassClient {
}
該服務統一由RemoteClassClient
對外提供各個班級型別的查詢服務。
ClassService (Target目標介面、及目標物件)
/**
* 課程資訊
*/
@Data
@Builder
public class ClassInfoBO {
// 課程型別 1:1v1 2:small 3:big
private String type;
// 班級ID
private String classId;
// 課程編號
private String lessonNo;
// 課程名稱
private String lessonName;
}
/**
* 目標介面
*/
public interface ClassService {
boolean match(String classType);
ClassInfoBO getClassInfo(String classId);
}
下面我們就需要幾個介面卡來完成適配。
1.1 物件介面卡
OneClassAdapter
/**
* 1v1介面卡
*/
@Component
@RequiredArgsConstructor
public class OneClassAdapter implements ClassService {
private static final String TYPE = "1";
private final RemoteClassClient classClient;
@Override
public boolean match(String classType) {
return TYPE.equals(classType);
}
@Override
public ClassInfoBO getClassInfo(String classId) {
final OneClass one = classClient.getOne();
return ClassInfoBO.builder()
.type("1")
.classId(classId)
.lessonNo(one.getLessonNo()).lessonName(one.getLessonName())
.build();
}
}
SmallClassAdapter
/**
* 小班課介面卡
*/
@Component
@RequiredArgsConstructor
public class SmallClassAdapter implements ClassService {
private static final String TYPE = "2";
private final RemoteClassClient classClient;
@Override
public boolean match(String classType) {
return TYPE.equals(classType);
}
@Override
public ClassInfoBO getClassInfo(String classId) {
final SmallClass small = classClient.getSmall();
return ClassInfoBO.builder()
.type("2")
.classId(classId)
.lessonNo(small.getLessonNo()).lessonName(small.getLessonName())
.build();
}
}
BigClassAdapter
/**
* 大班課介面卡
*/
@Component
@RequiredArgsConstructor
public class BigClassAdapter implements ClassService {
private static final String TYPE = "3";
private final RemoteClassClient classClient;
@Override
public boolean match(String classType) {
return TYPE.equals(classType);
}
@Override
public ClassInfoBO getClassInfo(String classId) {
final BigClass big = classClient.getBig();
return ClassInfoBO.builder()
.type("3")
.classId(classId)
.lessonNo(big.getLessonNo()).lessonName(big.getLessonName())
.build();
}
}
至此,介面卡完成。可以根據具體場景選擇不同的介面卡,去適配當前的場景。再來個介面卡工廠類。
ClassAdapterFactory
/**
* 課程資訊介面卡工廠
*/
@Service
@RequiredArgsConstructor
public class ClassAdapterFactory {
private final List<ClassService> classServiceList;
ClassService getAdapter(String classType) {
return classServiceList.stream()
.filter(cs -> cs.match(classType)).findFirst()
.orElse(null);
}
}
1.2 類介面卡
僅以其一舉例。這種介面卡不如物件介面卡靈活。
/**
* 小班課介面卡(類介面卡)
*/
@Component
public class SmallClassAdapter2 extends RemoteClassClientImpl implements ClassService {
private static final String TYPE = "2";
@Override
public boolean match(String classType) {
return TYPE.equals(classType);
}
@Override
public ClassInfoBO getClassInfo(String classId) {
final SmallClass small = super.getSmall();
return ClassInfoBO.builder()
.type("2")
.classId(classId)
.lessonNo(small.getLessonNo()).lessonName(small.getLessonName())
.build();
}
}
可以看到,我們透過繼承
的方式,使SmallClassAdapter2
具備了RemoteClassClientImpl
查詢課程資訊的能力。跟SmallClassAdapter
也沒有啥太大區別。
1.3 單測
@SpringBootTest
class ClassServiceTest {
@Autowired
private ClassAdapterFactory adapterFactory;
@Test
void testOne() {
String classType = "1";
String classId = "11111111";
Optional.ofNullable(adapterFactory.getAdapter(classType)).ifPresent(ad -> {
final ClassInfoBO classInfo = ad.getClassInfo(classId);
assertEquals("one", classInfo.getLessonNo());
});
}
@Test
void testSmall() {
String classType = "2";
String classId = "22222222";
Optional.ofNullable(adapterFactory.getAdapter(classType)).ifPresent(ad -> {
final ClassInfoBO classInfo = ad.getClassInfo(classId);
assertEquals("small", classInfo.getLessonNo());
});
}
@Test
void testBig() {
String classType = "3";
String classId = "33333333";
Optional.ofNullable(adapterFactory.getAdapter(classType)).ifPresent(ad -> {
final ClassInfoBO classInfo = ad.getClassInfo(classId);
assertEquals("big", classInfo.getLessonNo());
});
}
}
2 思考
介面卡模式能帶來什麼?
- 感覺最主要的還是帶了一種解決方案(?當然設計模式本來就是如此)。其次是對某些場景提供了一種規範,加入沒有這個設計模式,我們也能有解決方案,但具體實現可能千奇百怪(?當然設計模式本來就是從千奇百怪中提煉出來的)。
- 設計模式不是銀彈。避免濫用。
跟策略模式有啥區別?
- 單從實現方式(套路)來說。不能說毫無區別,簡直就是一模一樣。
- 細想一下,
Adaptee
在策略模式中,並不是必然存在的。它的目的是不同策略的具體實現和呼叫分開。而介面卡模式的重點在於Adaptee -> Target
的適配。