어댑터, 퍼사드 패턴

Adapter Pattern

  • 특정 클래스 인터페이스를 클라이언트에서 요구하는 다른 인터페이스로 변환합니다. 인터페이스가 호환되지 않아 같이 쓸 수 없었던 클래스를 사용할 수 있게 도와줍니다.

Facade Pattern

  • 서브시스템에 있는 일련의 인터페이스를 모아서 사용하기 쉽게 통합 인터페이스로 묶어줍니다.

  • facade: 겉모양, 외관

1. 오리와 칠면조 어댑터 구현하기

  • 외주넣은 업체에서 제공한 클래스 라이브러리를 사용해야 합니다. 근데 그 인터페이스가 기존에 사용하던 인터페이스와 다른 상황이 생깁니다.

  • 하지만, 기존에 사용하던 코드는 바꿀 수가 없는 상황이에요. 괜히 건드렸다가 무슨 사이드이펙트가 발생할 지 모르는 것이죠.

  • 이럴 때, 외주 받은 클래스 라이브러리를 기존 인터페이스에 적응시켜주는 클래스를 만드는 것이 어댑터 패턴입니다.

  • 아래 코드를 보면, MallardDuck은 Duck 프로토콜을 준수하는 클래스이고 WildTurkey는 Turkey를 준수하는 전혀 다른 인터페이스 입니다.

  • 하지만, Duck을 준수하는 TurkeyAdapter 클래스에서 turkey 인스턴스를 구성(Composition)으로 가지고 있고, 그 인스턴스를 통해 메소드를 실행하고 있죠. 물론 그 반대도 가능합니다.

2. 어댑터 패턴

  • 어댑터(TurkeyAdapter)에서 타깃 인터페이스(Duck)를 구현합니다.

  • 어댑터(TurkeyAdapter)는 어댑티(Turkey) 인터페이스의 인스턴스를 구성(Composition)으로 가지고 있지요. 이런 접근법은 어댑티(Turkey) 인터페이스의 모든 서브 클래스에 어댑터(TurkeyAdapter)를 쓸 수 있다는 장점이 있습니다.

  • 그 말은, 나중에 어댑티(Turkey) 인터페이스를 준수하는 서브 클래스를 추가해도 호환된다는 뜻입니다.

  • 어댑터 패턴은 타깃 인터페이스가 크다면 그에 비례해서 복잡해집니다. 코드를 엄청나게 많이 고쳐야 된다는 소리죠. 그냥 모든 변경 사항을 캡슐화할 클래스 하나만 제공하는 방법도 고려해보면 좋습니다.

  • 대부분은 어댑터는 하나의 클래스만 감싸지만, 그렇지 않은 경우도 있습니다.

  • 코드에 오래된 인터페이스와 새로만든 인터페이스가 있어서 한쪽만 어댑터를 사용해야 되나 고민된다면, 두 인터페이스를 모두 지원하는 다중 어댑터(Two Way Adapter)를 만들면 됩니다.

3. 객체 어댑터와 클래스 어댑터

  • 클래스 어댑터

    • 다중 상속(Multiple Inheritance)을 사용합니다. 다중 상속을 지원하는 언어에서만 사용 가능합니다.

    • 인터페이스를 준수하는 어댑터 클래스를 만드는 객체 어댑터와는 다르게 인터페이스가 아닌 MallardDuck과 WildTurkey 클래스 2개를 다중 상속해서 어댑터 클래스를 만듭니다.

    • 그렇기 때문에, 특정 어댑티 클래스에만 적용할 수 있다는 단점이 있습니다. 하지만 서브클래스이기 때문에 어댑티의 행동을 오버라이드 할 수 있는 장점이 있습니다.

    • 상속을 사용하므로 코드 분량은 상대적으로 적습니다.

  • 객체 어댑터

    • 구성(Composition)을 사용합니다.

    • 구성으로 어댑티에 요청을 전달합니다.

    • 구성을 사용하므로 어댑터 클래스와 그 서브클래스들에 대해서도 어댑터 역할이 가능합니다.

    • 어댑터 코드에 어떤 행동을 추가하면 어댑티와 모든 서브클래스에 그대로 적용되기 때문에 상대적으로 유연성이 뛰어납니다.

4. Enumeration을 Iterator에 적응시키기

  • 둘 다 자바의 인터페이스인데, Enumeration이 구형 Iterator가 신형이랍니다. iOS도 objc와 swift가 있으니 대충 그런가보다 하고 이해하면 되겠네요.

  • hasNext()는 hasMoreElements()로, next()는 nextElement()로 대응됩니다.

  • 하지만 Iterator의 remove()는 Enumeration에 해당하는 기능을 제공하지 않습니다. 이처럼 메소드가 일대일로 대응되지 않는 상황에서는 어댑터 차원에서 완벽한 구현 방법은 없습니다. 그나마 가장 좋은 방법은 런타임 예외를 던지는 것입니다. 자바에서는 UnsupportedOperationException이라는 게 있다고 합니다.

5. 데코레이터, 어댑터, 퍼사드 패턴

  • 데코레이터 패턴

    • 여러개의 클래스를 감쌀 수 있습니다.

    • 인터페이스는 바꾸지 않고 데코레이터로 감싸질 때 마다 기능/책임을 추가하는 용도로 사용된다.

  • 어댑터 패턴

    • 여러개의 클래스를 감쌀 수 있습니다.

    • 인터페이스를 다른 인터페이스로 용도로 사용된다.

  • 퍼사드 패턴

    • 여러개의 클래스를 감쌀 수 있습니다.

    • 복잡한 인터페이스를 단순하게 만드는 용도로 사용된다.

6. 퍼사드 패턴

  • 복잡한 인터페이스를 단순하게 바꾸려고 인터페이스를 변경합니다.

  • 하나 이상의 클래스 인터페이스를 깔끔하면서도 효과적인 퍼사드(겉모양, 외관)으로 덮어줍니다.

  • 퍼사드 클래스는 서브 시스템 클래스(Light, Screen, Popper...)를 캡슐화 하지 않습니다. 그냥 더 쉽게 사용하기 위한 인터페이스만 제공할 뿐이죠. 이게 대표적인 장점인데, 캡슐화 하지 않기 때문에 원한다면 얼마든지 원래 서브 시스템 클래스 인스턴스를 생성한다던지 해서 계속 사용할 수 있게 합니다.

  • 최소 지식 원칙(Principle of Least Knowledge, LoD, Law of Demeter)을 준수하는데 도움이 됩니다.

7. 홈시어터 퍼사드 구현하기

  • 공통 코드

  • 퍼사드 패턴 적용 전의 코드

    • 영화를 보기 위해서는 할 일이 아주 많습니다.

    • 팝콘 기계를 켜고, 튀기고, 조명을 조절하고, 스크린을 내리고, 프로젝트를 켜고...

    • 영화를 끌 때도 마찬가지로 위의 과정을 역순으로 해야 합니다. 힘들어요.

  • 퍼사드 패턴 적용 후의 코드

    • 팝콘, 조명, 스크린, 프로젝트 같은 서브 시스템을 합쳐서 통합 인터페이스를 만들어버립니다.

8. 최소 지식 원칙(PoLK, LoD)

  • 최소 지식 원칙(Principle of Least Knowledge)은 디미터/데메테르 법칙(Law of Demeter)이나 축약해서 LoD 라고도 불린다.

  • 한 마디로 정의하면 객체 사이의 상호작용은 될 수 있으면 아주 가까운 '친구'사이에만 허용하는 것이 좋다는 말이다.

  • 즉, 시스템을 디자인 할 때, 어떤 객체든 그 객체와 상호작용을 하는 클래스의 개수와 상호작용 방식에 주의를 기울여야 한다는 뜻이다.

  • 이 원칙을 잘 따르면 여러 클래스가 복잡하게 얽혀 있어서 시스템의 어느 한 부분을 변경했을 때, 다른 부분까지 줄줄이 고쳐야 하는 상황을 방지할 수 있습니다.

  • 이 원칙을 잘 따르지 않는 코드는 여러 클래스가 서로 복잡하게 의존하고 있어 관리하기도 힘들고, 남들이 이해하기 어려운 코드가 됩니다.

  • 장점이 있는만큼 단점도 있습니다. 원칙을 적용하다보면 '래퍼'클래스를 더 만들어야 할 수도 있습니다. 그러면 시스템이 복잡해지고, 개발 시간도 늘어나고, 성능도 떨어집니다.

  • 친구를 만들지 않는 4개의 가이드라인

    • 객체 자체는 호출 (O)

    • 메소드에 매개변수로 전달된 객체는 호출 (O)

    • 메소드를 생성하거나 인스턴스를 만든 객체는 호출 (O)

    • 객체에 속하는 구성 요소는 호출 (O)

9. 퍼사드 패턴과 최소 지식 원칙

  • 아래 클라이언트의 친구는 퍼사드 하나 뿐이죠. 친구는 찐친구 하나만 있어도 됩니다.

Reference

https://www.hanbit.co.kr/store/books/look.php?p_code=B6113501223

Last updated