Adapter

DongHeon·2022년 11월 10일
1

디자인 패턴

목록 보기
5/12

GoF 디자인 패턴 중 구조에 속해있는 Adapter 패턴에 대해서 다뤄보겠습니다.

구조 패턴이란 클래스나 객체들을 조합해 더 큰 구조로 만들 수 있게 해주는 패턴입니다.

Adapter?

기존 코드를 클라이언트가 사용하는 인터페이스의 구현체로 바꿔주는 패턴입니다.

Client는 인터페이스(프로토콜)를 따르는 코드를 작성했다면 Adaptee에 있는 메서드를 사용하기 위해서는 Client에 있는 코드를 수정해야 합니다. Apdater 패턴을 사용하면 Adaptee를 인터페이스의 구현체로 변경해 사용할 수 있습니다.

코드

패턴 적용 전

  • Target
protocol LoginService {
    func loadUser(userID: String) -> UserInfo
}

protocol UserInfo {
    func getUserName() -> String
    
    func getUserPassword() -> String
}
  • Client
class LoginHandler {
    let service: LoginService
    
    init(service: LoginService) {
        self.service = service
    }
    
    func login(id: String, password: String) throws -> String {
        let userDetail = service.loadUser(userID: id)
        
        if userDetail.getUserPassword() == password {
            return userDetail.getUserName()
        } else {
            throw LoginError.defaultError
        }
    }
}

Client에는 LoginService를 따르는 코드가 작성되어 있습니다.

  • Adaptee
class Account {
    private var id: String = ""
    private var password: String = ""
    
    func getID() -> String {
        return id
    }
    
    func getPassword() -> String {
        return password
    }
    
    func setPassword(password: String) {
        self.password = password
    }
    
    func setID(id: String) {
        self.id = id
    }
}

struct AccountService {
    func findAccountUser(userID: String, password: String) -> Account {
        let account = Account()
        account.setID(id: userID)
        account.setPassword(password: password)
        return account
    }
}

AccountAccountServiceClient에서 사용하기 위해서는 기존의 코드를 수정해야 하는 불편함이 존재합니다.

패턴 적용

  • Adapter
class AccountLoginService: LoginService {
    private let service: AccountService
    
    init(service: AccountService) {
        self.service = service
    }
    
    func loadUser(userID: String) -> UserInfo {
        let account = service.findAccountUser(userID: userID, password: "1111")
        return AccountInfo(account: account)
    }
}

class AccountInfo: UserInfo {
    private let account: Account
    
    init(account: Account) {
        self.account = account
    }
    
    func getUserName() -> String {
        return account.getID()
    }
    
    func getUserPassword() -> String {
        return account.getPassword()
    }
}

Target 프로토콜을 준수하는 Adapter 코드를 작성하면 Client는 기존의 코드를 수정하지 않고 AccountAccountService와 호환해서 사용할 수 있습니다.

AccountAccountService에서 UserInfoLoginService를 직접 채택해서 사용하면 Client의 코드를 수정할 필요 없지 않을까라는 생각을 할 수 있습니다.

  • AdapteeTarget 프로토콜의 코드를 수정할 수 없다면 두 타입을 호환시킬 수 있는 방법은 Adapter 패턴을 적용하는 것입니다.
  • 수정이 가능하다면 관리해야 하는 타입이 줄어들지만 기존의 코드를 변경해야 하기 때문에 객체 지향 원칙 중 OCP를 위반하고 과연 해당 타입이 SRP를 준수하는지 고민할 수 있을 것 같습니다.

장단점

  • 장점
  1. 기존 코드를 변경하지 않고 사용할 수 있습니다.
  2. 비즈니스 로직과 특정 인터페이스 구현체로 변환하는 작업을 분리할 수 있습니다.
  • 단점
  1. 관리해야 하는 타입이 증가하기 때문에 복잡도가 증가할 수 있습니다.

해당 글은 인프런의 코딩으로 학습하는 GoF 디자인 패턴 강의와 블로그를 참고해 작성했습니다.

참고 자료
Refactoring.Guru

⭐️ 부족하거나 잘못된 부분이 있다면 댓글은 언제나 환영입니다!! ⭐️

0개의 댓글