본문 바로가기
iOS/Design Pattern

Design Pattern - Adapter Pattern

by HaningYa 2020. 8. 30.
728x90

Adapter Pattern

Adapter pattern은 행위 패턴으로 상호 호환이 안되는 타입을 연결해 주는 패턴이다. 4개의 부분이 있다.

출처: design pattern by tutorials (raywenderlich)

  • Adapter를 사용하는 객체: 새로운 프로토콜에 의존하는 객체
  • 새로운 프로토콜: 사용을 위해 필요한 프로토콜 
  • legacy Object: 프로토콜이 만들어 지기 전에 원래 있던 객체, 수정될 수 없고 직접적으로 integrate 할 수 없는 객체
  • Adapter: 프로토콜을 준수하면서 legacy object 와 상호작용 하기 위해 만들어진 객체

실물의 어댑터의 가장 좋은 예시는 아이폰이다. 3.5mm 헤드폰 단자가 없어서 어댑터를 통해 라이트닝 단자에 연결해야 한다.

두개의 요소를 알맞게 연결해 주는 것이 Adapter Pattern 의 핵심이다.

안드로이드에서는 RecyclerView 같은 종류를 만들때 맨날 Adapter 썼었는데 iOS 로 넘어오면서 delegate, datasource 프로토콜로 너무 편했다.


Adapter Pattern 예제

3rd party authentication service 가 legacy object 의 역할을 맡을 것 이다.

import UIKit

public class GoogleAuthenticator {
	public func login(
    	email: String,
        password: String,
        completion: @escaping (GoogleUser?, Error?) -> Void ) {
        let token = "speical-token-value"
        let user = GoogleUser(email: email,
        					  password: password,
                              token: token)
        completion(user, nil)
       }
   }
   public struct GoogleUser {
   	public var email: String
    public var password: String
    public var token: String
}
  • 위 코드는 써드파티 클래스로 수정될 수 없다. (legacy object 역할이다)
  • 실제 Google Authentication 은 더 복잡하지만 예시로 간단하게 작성했다.
  • 로그인 함수는 GoogleUser 라는 string property 로 이루어진 token 을 준다. 

public protocol AuthenticationService {
	func login(email: String,
    		   password: String,
               success: @escaping (User, Token) -> Void,
               failture: @escaping (Error?) -> Void)
}

public struct User {
	public let email: String
    public let password: String
}

public struct token {
	public let value: String
}
  • 위 authentication protocol은 새로운 프로토콜 역할을 맡는다. 
  • email 과 password 가 필수이고 로그인에 성공할 경우 success 를 호출해 User와 Token 를 넘긴다.
  • 앱단에서는 GoogleAuthenticator를 쓰지않고 방금 만든 프로토콜을 통해 Authenticate 를 할 것이다.
  • 이렇게 하면 좋은 점이 다양한 Authentication 로직, 예를 들어 Google, Facebook 과 같은 걸 하나의 protocol 을 통해 단순화 할 수 있다는 것이다.
  • Google Authenticator 를 방금 만든 AuthenticationService 프로토콜을 준수하게 만든다. (이게 어댑터 패턴이다)

public class GoogleAuthenticatorAdapter : AuthenticationService {
	private var authenticator = GoogleAuthenticator()
    public func login(email: String,
    				  password: String,
                      success: @escaping (User, Token) -> Void.
                      failture: @escaping (Error?) -> Void) {
         authenticator.login(email: email, password: password) {
         	(googleUser, error) in 
            
            guard let googleUser = googleUser else {
            	failure(error)
                return
            }
            
            let user = Usre(email: googleUser.email,
            				password: googleUser.password)
            let token = Token(value: googleUser.token)
            success(user, token)
        }
    }
}
  • 위 GoogleAuthenticatorAdapter 가 AuthenticationService 와 GoogleAuthenticator 사이 어댑터 역할을 담당한다.
  • GoogleAuthenticator 에 대한 private reference를 선언한다.
  • 프로토콜에 따라 AuthenticationService 의 로그인 함수를 구현한다. 함수 내에서 GoogleUser를 얻기위해 Google 의 로그인 매소드를 호출한다.
  • failure 와 success 처리한다.
  • GoogleAuthenticator 를 wrapping 함으로 써 소비자가 Google API 를 직접적으로 알 필요가 없는 장점이 있다.
  • 이런 식으로 하면 나중에 수정사항이 생겨도 adapter 만 수정하면 되서 편리하다.

public class LoginViewController : UIViewController {
	public var authService : AuthenticationService!
    
    var emailTF = UITextField()
    var passwordTF = UITextField()
    
    public class func instance(
    	with authService: AuthenticationService)
        -> LoginViewController {
        let vc = LoginViewController()
        vc.authService = authService
        return vc
    }
    
    public func login() {
    	guard let email = emailTF.text,
        	let password = passwordTF.text else{
            print("Email and password are required to inputs!")
            return
        }
        authService.login(
        	email: email,
            password, password,
            success: {user token in 
            	print("Auth succeeded: \(user.email), \(token.value)")
            },
            failure: { error in
            	print("Auth failed with error: no error provided")
            })
        }
    }
}


let viewController = LoginViewController.instance(
	with: GoogleAuthenticatorAdapter())
viewController.emailTF.text = "userexample.com"
viewController.passwordTF.text = "password"
viewController.login()
  • LoginViewController 에서 어댑터, 프로토콜, 레거시 객체를 사용해 본 코드이다.
  • authService.login을 호출하는 로그인 메소드를 만든다.
  • 마지막에 textfield 에 이메일 비밀번호 적고 login() 해본다.

Adapter Pattern 사용시 주의할 점

  • Adapter Pattern는 새로운 프로토콜만 준수하면 된다. 이 건 결과적으로 프로토콜 뒤의 타입에 관해 (또는 미래 수정) 에 대해 신경 쓸 필요가 없다는 뜻이다. 하지만 구현이 더 복잡해지고 가독성이 떨어지며 유지보수가 힘들어 진다.
  • 실제 수정에 대한 가능성이 있다면 adapter pattern을 사용하고 수정할 가능성이 없다면 adapter 를 빼고 직접적으로 연결 하는 것이 바람직하다.

 

Adapter Pattern 정리

  • adapter pattern 은 써드파티 라이브러리처럼 수정할 수 없는 class를 작업하는데 유용하다. 프로토콜을 사용한다.
  • adapter 를 사용하기 위해 legacy object를 extend 하거나 새로운 adapter class 를 만들어야 한다.
  • adapter pattern은 필요로 하는 객체가 incompatible 한 경우에도 class 를 재사용 할 수 있게 해준다.
  • 미래에 수정이 될만한 코드라면 Adapter Pattern을 쓰는 것이 좋다.

 

 

728x90

'iOS > Design Pattern' 카테고리의 다른 글

복합체 패턴 발표자료  (0) 2021.06.08
Design Pattern - Factory Pattern  (0) 2020.08.25
Design Pattern - Model View ViewModel Pattern  (2) 2020.08.24
Design Pattern - Builder Pattern  (0) 2020.08.09
Design Pattern - Observer Pattern  (0) 2020.08.09

댓글