既然是實現(xiàn)類干活兒,為何不直接寫實現(xiàn)類,還要多此一舉去寫接口?
一、接口與實現(xiàn)類代碼
package com.xxx; import xxx; public interface IOrderService { public default Map<String, Object> receiveOrderV1(OrderV1 orderV1) { return null; } }
package com.xxx; import xxx; @Service public class OrderService implements IOrderService { private static final Logger logger = LoggerFactory.getLogger(OrderService.class); @Autowired private Xxx xxx; public Map<String, Object> receiveOrderV1(OrderV1 orderV1){ ExOrder exOrder = orderV1.getObject(); return receiveExOrderV1(exOrder); } public Map<String, Object> receiveExOrderV1(ExOrder exOrder) { ... } }
二、分析
核心疑問可以總結(jié)為一句話:
"既然所有事都是OrderService實現(xiàn)的,為什么還要多繞一層接口IOrderService?直接用類不香嗎?"
? 首先,從Java語言角度講,這個接口不是必須的。
沒錯 —— 如果你只是想寫一個OrderService干活,然后Controller調(diào)用它,其實你完全可以不寫接口,直接@Autowired OrderService,完全work。
那問題來了:
?? 為什么要寫這個接口?
答案是:為了架構(gòu)的"靈活性"和"擴(kuò)展性"。
這是"面向接口編程"的設(shè)計思想。我們來看看幾個實際用途:
1?? 方便未來有多個實現(xiàn)類
假設(shè)以后OrderService有不同版本,比如:
- OrderServiceV1
- OrderServiceV2
- MockOrderServiceForTest
只要你用的是接口IOrderService,你就可以在Spring中輕松切換實現(xiàn):
@Autowired private IOrderService orderService;
想換一個實現(xiàn)?只要換個@Primary,或者在配置文件里指定即可,不用改任何業(yè)務(wù)代碼。
2?? 方便做單元測試
寫單元測試時,如果你用了接口,你可以輕松寫一個假的Mock實現(xiàn)類:
public class MockOrderService implements IOrderService { @Override public Map<String, Object> receiveOrderV1(OrderV1 orderV1) { return Map.of("msg", "mock success"); } }
然后在測試中注入MockOrderService,你就能繞開實際的業(yè)務(wù)邏輯、數(shù)據(jù)庫等,專心測接口邏輯。
3?? 團(tuán)隊協(xié)作/規(guī)范統(tǒng)一
很多大公司或者平臺架構(gòu)都規(guī)定:所有服務(wù)必須有接口,即使現(xiàn)在只有一個實現(xiàn),也要按規(guī)范來。
這樣未來好維護(hù)、好替換、好接入框架。
這就像你知道家里電視插的是三孔插頭,但你可能一輩子沒換過插座 —— 可是只要你哪天真要換,插頭統(tǒng)一就顯得太香了。
4?? 框架/中間件/動態(tài)代理兼容性
有時候框架(比如 Dubbo、Feign、MyBatis)都要求你傳的是接口而不是實現(xiàn)類。比如:
@FeignClient("service") public interface IOrderService { @PostMapping("/receiveV1") Map<String, Object> receiveOrderV1(@RequestBody OrderV1 orderV1); }
接口讓它更容易被 Spring AOP、代理等機(jī)制管理。
那你又會問:
你說這些我懂了,但這段代碼里的IOrderService,就寫了個default return null,這不是擺爛嗎?
是的,在你現(xiàn)在看到的這段代碼中,這個接口的確沒有“實質(zhì)性功能”。
但你可以把它理解為一種"設(shè)計上的預(yù)埋",為將來做準(zhǔn)備。
總結(jié):一句話打底
?接口就是一種"對未來變化的預(yù)留"?,F(xiàn)在看著沒用,不代表以后不會有用。?
當(dāng)然,如果你在做一些小項目、小團(tuán)隊、只追求效率,不搞那么多規(guī)范,那也完全可以不寫接口,直接上類。
三、沒有接口vs有接口 —— 對比示例
那我們就來對比一下——用不用接口,在測試、維護(hù)、擴(kuò)展性這三方面到底有啥區(qū)別。
我們用剛才說的OrderService為例,演示一下:
? 場景設(shè)定
我們有一個控制器:
@RestController public class OrderInController { @Autowired private IOrderService orderService; @PostMapping("/xxx/order") public Map<String, Object> receive(@RequestBody OrderV1 orderV1) { return orderService.receiveOrderV1(orderV1); } }
方案一:用了接口IOrderService
1?? 正常開發(fā)時
你實現(xiàn)這個接口:
@Service public class OrderService implements IOrderService { @Override public Map<String, Object> receiveOrderV1(OrderV1 orderV1) { // 真實業(yè)務(wù)邏輯 } }
2?? 測試時:寫個假的Mock實現(xiàn)!
@Component @Primary // 指定默認(rèn)注入這個mock public class MockOrderService implements IOrderService { @Override public Map<String, Object> receiveOrderV1(OrderV1 orderV1) { return Map.of("msg", "mocked", "code", 200); } }
??好處是:不依賴真實數(shù)據(jù)庫、服務(wù)、網(wǎng)絡(luò),單元測試超級快,還能隨便模擬返回數(shù)據(jù)。
3?? 未來擴(kuò)展時:新增一個實現(xiàn)類
@Service public class OrderServiceV2 implements IOrderService { @Override public Map<String, Object> receiveOrderV1(OrderV1 orderV1) { // 全新邏輯,比如AI自動驗單 } }
通過配置文件或注解,快速切換實現(xiàn)類。
方案二:沒用接口,直接寫死類
@Service public class OrderService { public Map<String, Object> receiveOrderV1(OrderV1 orderV1) { // 真實業(yè)務(wù)邏輯 } }
?? 問題來了:
? 1.測試難了
你想mock它,就只能用工具如Mockito:
when(orderService.receiveOrderV1(...)).thenReturn(...);
而且你要加@MockBean、@SpringBootTest,起一個大工程,性能慢,復(fù)雜度高。
? 2.擴(kuò)展難了
以后你要加V2版本,沒法讓Spring根據(jù)接口切換,只能自己手動在Controller判斷邏輯:
if (version.equals("v2")) { new OrderServiceV2().receive(...); } else { new OrderService().receive(...); }
丑爆了,還容易出錯。
?? 最終總結(jié)表格
對比點 | 用接口(IOrderService) | 不用接口(直接用類) |
測試 Mock | ? 很方便,隨便寫一個實現(xiàn)注入 | ? 比較難,要配合框架寫假類 |
多實現(xiàn)版本切換 | ? 很靈活,Spring 配置就行 | ? 要手動 if 分支判斷 |
面向框架兼容性 | ? 支持 Feign、Dubbo、AOP 等 | ? 有些框架不支持類注入 |
初期開發(fā)快慢 | ? 多寫一個接口略慢 | ? 快速開發(fā)直接寫類 |
項目代碼結(jié)構(gòu)清晰度 | ? 有規(guī)范,易讀可擴(kuò)展 | ? 類膨脹,維護(hù)成本高 |