如果依照存在即合理的說法來看,Switch的存在確實是有解決一些問題,比如說一些要依照不同情況來回傳不同結果的Function,相較於用冗長的if-else,選擇用Switch確實是個不錯的解法。但當今天的Switch Case會增長的情況,在選擇使用它就會違反了OCP(開放擴充、封閉修改)的原則,亦即每次有新的情況出現,我們就得回頭去改Switch語法,新增不同的case,一來一往增加了維護的負擔,這邊分享一個我把switch語法拆解的方式,以供紀錄這樣子。
情境
我在設計一款可以連結不同SQL Engine的程式,當用戶選擇了不同的SQL Engine,我能夠執行不同資料庫的語法來返回結果,圖示如下
修改前
這是我的設計
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| @RestController
public class DatasourceController {
DatabaseService databaseService;
public DatasourceController(DatabaseService databaseService) {
this.databaseService = databaseService;
}
@GetMapping("/query")
public String query(@RequestBody QueryRequest queryRequest) throws SQLException, JsonProcessingException, ClassNotFoundException {
return databaseService.query(queryRequest);
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| @Service
public class DatabaseServiceImp implements DatabaseService{
DatabaseDao databaseDao;
public DatabaseServiceImp(DatabaseDao databaseDao) {
this.databaseDao = databaseDao;
}
@Override
public String query(QueryRequest queryRequest) throws SQLException, JsonProcessingException, ClassNotFoundException {
String databaseName = queryRequest.getDatabaseEngine();
databaseDao = getDatabaseDao(databaseName);
return databaseDao.connect().query(queryRequest.getQuery());
}
private DatabaseDao getDatabaseDao(String databaseName) {
switch (databaseName){
case "Postgres":
return new PostgresDaoImpl();
case "MsSql":
return new MySQLDaoImpl();
default:
throw DatabaseNotFoundException.createDatabaseNotFoundException("Not this Database");
}
}
}
|
1
2
3
4
5
6
7
8
9
10
11
| @Repository
public interface DatabaseDao {
DatabaseDao connect() throws SQLException, ClassNotFoundException;
void close() throws SQLException;
String query(String query) throws SQLException, JsonProcessingException;
}
|
我在Service層的地方會有一個methodgetDatabaseDao
,來依照輸入的databaseName來將不同的Dao賦值進filed中,首先這有兩個問題
- 當資料庫在擴充時,必須要回頭修改
getDatabaseDao
,增加修改的成本 - 無法利用SpringBean的IoC,每次有新的Request近來,都會創造一個Dao的Object存在於記憶體中,造成記憶體空間的浪費
修改後
因此後來的修改必須得改善上面兩點,必須要符合OCP的規則,我只要新增新的Dao,而毋需修改就有的程式碼,並且還要使用SpringBean,以下是修改後的版本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| @Service
public class DatabaseServiceImp implements DatabaseService {
DatabaseEngineDao databaseEngineDao;
List<DatabaseEngineDao> databaseDaoList;
public DatabaseServiceImp(List<DatabaseEngineDao> databaseDaoList) {
this.databaseDaoList = databaseDaoList;
}
@Override
public String query(QueryRequest queryRequest) throws SQLException, JsonProcessingException, ClassNotFoundException {
String databaseName = queryRequest.getDatabaseEngine();
databaseEngineDao = getDatabaseDao(databaseName);
return databaseEngineDao.connect().query(queryRequest.getQuery());
}
private DatabaseEngineDao getDatabaseDao(String databaseEngine) {
for (DatabaseEngineDao databaseDao : databaseDaoList) {
if (databaseEngine.equals(databaseDao.getDatabaseEngineName())) {
return databaseDao;
}
}
throw DatabaseNotFoundException.createDatabaseNotFoundException("Not Found This DatabaseEngine,請檢查是否有相應的databaseEngineName在DAO中");
}
}
|
1
2
3
4
5
6
7
8
| @Repository
public interface DatabaseEngineDao {
DatabaseEngineDao connect() throws SQLException;
void close() throws SQLException;
String query(String query) throws SQLException, JsonProcessingException;
public String getDatabaseEngineName();
}
|
將原本的DatabaseDao databaseDao;
修改為,List<DatabaseEngineDao> databaseDaoList;
,在SpringBoot啟動時,將所有的DatabaseEngineDao類配置進databaseDaoList中,讓springBoot控制object的創建,並且將getDatabaseDao
改寫,改成去for-each databaseDaoList,尋找List中是否有名稱符合的SQL Engine。並在DatabaseEngineDao Interface處新增getDatabaseEngineName,讓每個實作它的Impl都必須去完成這個method,完成了OCP的原則。