網頁

2017/12/17

Java 為什麼在Service層要使用Interface

在Java Web應用程式中,SpringMVC的@Controller@Service@Repository的分層架構是經常被使用的架構。在實作Service層時,通常會先定義Service的介面,然後才撰寫具體類別實作該介面。我曾經認為為什麼要這麼麻煩呢? Service直接寫成類別不就好了,寫一個介面然後再來繼承不是多此一舉很麻煩嗎? 後來查了一下才知道這樣做的原因其實就是符合SOLID原則的設計,不直接依賴類別的好處如下。


在寫單元測試的時候比較容易

例如你要測試一個功能模組,例如查詢使用者,該模組使用到UserService,當撰寫測試用的程式碼時可能不想使用真正的UserService的來測試,而是用一個測試版的UserServiceMock來返回一些預先定義好的測試資料,若UserService有定義介面,則測試時可以傳入測試用的UserServiceMock類別。如果UserService定義成具體的類別,就無法輕易地傳入測試用的實作了,你可能要更改到原本被測試的模組程式碼。


在擴充上比較容易

UserService定義介面的話可以輕鬆地添加對UserService的實作。例如原本是從資料庫讀取使用者資訊,如果今天要增加可以從xml檔讀取使用者資訊,則只要繼承UserService介面並實作具體類別即可,原本的實作不用更動程式碼。

例如下面UserService是具體類別,目前分別可從xml或資料庫中查詢使用者。

UserController

public class UserController {

    @Autowired
    private UserService userService;

    public List<UserInfo> queryUser() {
        return this.queryFromDatabase();
    }
    
    /** 從xml查詢 */
    private List<UserInfo> queryFromXml() {
        return userService.queryFromXml();
    }
    
    /** 從資料庫查詢 */
    private List<UserInfo> queryFromDatabase() {
        return userService.queryFromDatabase();
    }
}

UserService

public class UserService {

    public List<UserInfo> queryFromXml() {
        // 從xml查詢
    }
    public List<UserInfo> queryFromDatabase() {
        // 從資料庫查詢
    }
}

如果多了一個可以從JSON資料查詢使用者,則必在Controller和Service添加新增的程式碼如下。

public class UserController {

    @Autowired
    private UserService userService;

    public List<UserInfo> queryUser() {
        List<UserInfo> userList = this.queryFromDatabase();
    }

    /** 從xml查詢 */
    private List<UserInfo> queryFromXml() {
        userService.queryFromXml();
    }

    /** 從資料庫查詢 */
    private List<UserInfo> queryFromDatabase() {
        userService.queryFromDatabase();
    }

    /** 從JSON查詢 */
    private List<UserInfo> queryFromJSON() {
        userService.queryFromJSON();
    }
}

public class UserService {
  public List<UserInfo> queryFromXml() {
      // 從xml查詢
  }
  public List<UserInfo> queryFromDatabase() {
      // 從資料庫查詢
  }
  // 新增從JSON查詢
  public List<UserInfo> queryFromJSON() {
      // 從JSON查詢
  }
}

從上可以看到Controller和Serivce的程式碼都會被更動到。若還有非常多其他的Controller有用到UerService,則每次UserService有新的方法被加入時,所有用到UserService的Controller也會被影響,造成Controller和Service高度耦合,使得在擴充和維護上變得困難。


若把UserService定義成介面如下。

UserService

public interface UserService {
  
    public List<UserInfo> query();
  
}

實作UserService的類別。

UserServiceXmlImpl

public class UserServiceXmlImpl implements UserService {
    @Override
    public List<UserInfo> query() {
        // 從xml查詢
    }
}

UserServiceDatabaseImpl

public class UserServiceDatabaseImpl implements UserService {
    @Override
    public List<UserInfo> query() {
        // 從資料庫查詢
    }
}

UserServiceJSONImpl

public class UserServiceJSONImpl implements UserService {
    @Override
    public List<UserInfo> query() {
        // 從JSON查詢
    }
}

可以看到在UserController中只有一種查詢方法,Controller只知道UserService的存在,透過注入不同實作類別來查詢不同的資料來源,以後若要新增新的查詢來源方法,只要撰寫一個實作UserService的類別並實作UserService.query()方法即可,Controller只要依需求注入對應的實作(或使用@Order來調整注入順序),使得在維護上變得容易。

UserController

public class UserController {

    @Qualifier("ouserServiceDatabaseImplne")
    @Autowired
    private UserService userService;

    public List<UserInfo> queryUser() {
        return userService.query();
    }
    
}

如果覺得文章有幫助的話還幫忙點個Google廣告,感恩。


參考:

沒有留言:

張貼留言