기존 코드를 클라이언트가 사용하는 인터페이스의 구현체로 바꿔주는 패턴
클라이언트가 사용하는 인터페이스를 따르지 않는 기존 코드를 재사용할 수 있게 해준다.
Account
객체로 LoginHelper
를 통해 해당 유저가 로그인이 가능한 상태인지 확인합니다.
// 사용자의 데이터를 저장하는 객체
data class Account(
var name: String,
var password: String,
var email: String
)
// 저장된 사용자의 정보를 핸들링 할 수 있는 class
// 예시를 위해 findAccountByUsername 할 때,
// Account 를 생성하도록 구현하였습니다.
class AccountService {
fun findAccountByUsername(username: String): Account {
val account = Account(
name = username,
password = username,
email = username
)
return account
}
fun createNewAccount(account: Account) {}
fun updateAccount(account: Account) {}
}
Adaptee 에 해당하는 클래스를 만들었습니다.
Account
객체에는 회원가입된 사용자의 정보를 담을 수 있는 정보를 가지고 있으며,
AccountService
는 Account
의 생성, 변경, 찾기 등 여러 동작을 지원하는 클래스입니다.
우리가 구현하고자하는 목표는 Account
를 활용하여 LoginHelper
를 통해 로그인이 가능한지 확인하는 것입니다.
Account
를 LoginHeldper
에서 사용할 수 있도록 하는 어댑터가 필요합니다.
// Account 의 정보를 가지고 올 수 있는 target
interface UserDetails {
fun getUsername(): String
fun getPassword(): String
}
// 로그인 하고자 하는 username 을 통해 UserDetail 를 return
interface UserDetailService {
fun loadUser(username: String): UserDetails
}
2가지 Target Interface 를 구현합니다.
UserDetailService
: username 을 이용하여 즉, 사용자의 이름을 이용하여 UserDeatils
을 반환합니다. 이렇게 만들면 Account
객체를 직접 참조하지 않고 UserDetails
클래스에 정의된 함수를 통해 Account 의 데이터에 접근할 수 있습니다.UserDetails
: Account
객체를 직접 참조 하지 않고 구현체에 동작을 위임 할 수 있습니다. 현재는 간단하게 getUsername()
, getPassword()
만 구현 하였지만 Adapter 클래스에서 해당 함수를 구현할 때, validate 처리, 잘못 된 값 처리 등 다양한 동작을 추가할 수 있습니다.class AccountUserDetails(
private val account: Account
) : UserDetails {
override fun getUsername(): String {
return this.account.name
}
override fun getPassword(): String {
return this.account.password
}
}
class AccountUSerDetailService(
private val accountService: AccountService
) : UserDetailService {
override fun loadUser(username: String): UserDetails {
val account = accountService.findAccountByUsername(username)
return AccountUserDetails(account)
}
}
AccountUserDetailService
AccountService
를 생성자로 받아와서 사용자의 정보(Account) 를 찾아서 AccountDetails
Adapter를 반환합니다.Account
는 AccountUserDetails
Adapter 에 캡슐화 되어 직접 참조가 불가능한 상태가 되어 집니다.AccountUserDetails
Account
를 생성자로 받아 Account
의 데이터를 UserDetails Target Interface 에 정의된 함수로 값을 return 합니다.Account
객체에 접근할 수 있어 보다 안전하게 객체를 관리할 수 있습니다.class LoginHandler(private var userDetailsService: UserDetailService) {
fun login(username: String, password: String): String {
val userDetails = userDetailsService.loadUser(username)
return if (userDetails.getPassword() == password) {
userDetails.getUsername()
} else {
throw IllegalArgumentException()
}
}
}
// App.kt
private fun main() {
val accountService = AccountService()
val userDetailService = AccountUSerDetailService(accountService)
val loginHandler = LoginHandler(userDetailService)
val login = loginHandler.login("Jack", "Jack")
println(login)
}
모든 구현이 끝났습니다.
LoginHelper 에 login() 매소드를 통해 해당 유저가 로그인 가능한지 확인하면 됩니다.
LoginHelper 는 UserDetailService 생성자를 통해 로그인한 사용를 조회 합니다.
Account
객체 대신 UserDetails
객체를 반환하고 userDetails.getPassword() == password
조회된 사용자의 비밀번호와 입력한 비밀번호가 같다면 로그인
다르다면 Exception
을 던지도록 구현합니다.
쉽게 생각하면 A 생성자로 B 라는 새로운 객체를 만든다고 생각하시면 쉽게 이해할 수 있습니다.
모든 어탭터 패턴이 A → B → C
이렇게 변하지는 않습니다.