어댑터, 퍼사드 패턴
Last updated
Last updated
특정 클래스 인터페이스를 클라이언트에서 요구하는 다른 인터페이스로 변환합니다. 인터페이스가 호환되지 않아 같이 쓸 수 없었던 클래스를 사용할 수 있게 도와줍니다.
서브시스템에 있는 일련의 인터페이스를 모아서 사용하기 쉽게 통합 인터페이스로 묶어줍니다.
facade: 겉모양, 외관
외주넣은 업체에서 제공한 클래스 라이브러리를 사용해야 합니다. 근데 그 인터페이스가 기존에 사용하던 인터페이스와 다른 상황이 생깁니다.
하지만, 기존에 사용하던 코드는 바꿀 수가 없는 상황이에요. 괜히 건드렸다가 무슨 사이드이펙트가 발생할 지 모르는 것이죠.
이럴 때, 외주 받은 클래스 라이브러리를 기존 인터페이스에 적응시켜주는 클래스를 만드는 것이 어댑터 패턴입니다.
아래 코드를 보면, MallardDuck은 Duck 프로토콜을 준수하는 클래스이고 WildTurkey는 Turkey를 준수하는 전혀 다른 인터페이스 입니다.
하지만, Duck을 준수하는 TurkeyAdapter 클래스에서 turkey 인스턴스를 구성(Composition)으로 가지고 있고, 그 인스턴스를 통해 메소드를 실행하고 있죠. 물론 그 반대도 가능합니다.
어댑터(TurkeyAdapter)에서 타깃 인터페이스(Duck)를 구현합니다.
어댑터(TurkeyAdapter)는 어댑티(Turkey) 인터페이스의 인스턴스를 구성(Composition)으로 가지고 있지요. 이런 접근법은 어댑티(Turkey) 인터페이스의 모든 서브 클래스에 어댑터(TurkeyAdapter)를 쓸 수 있다는 장점이 있습니다.
그 말은, 나중에 어댑티(Turkey) 인터페이스를 준수하는 서브 클래스를 추가해도 호환된다는 뜻입니다.
어댑터 패턴은 타깃 인터페이스가 크다면 그에 비례해서 복잡해집니다. 코드를 엄청나게 많이 고쳐야 된다는 소리죠. 그냥 모든 변경 사항을 캡슐화할 클래스 하나만 제공하는 방법도 고려해보면 좋습니다.
대부분은 어댑터는 하나의 클래스만 감싸지만, 그렇지 않은 경우도 있습니다.
코드에 오래된 인터페이스와 새로만든 인터페이스가 있어서 한쪽만 어댑터를 사용해야 되나 고민된다면, 두 인터페이스를 모두 지원하는 다중 어댑터(Two Way Adapter)를 만들면 됩니다.
클래스 어댑터
다중 상속(Multiple Inheritance)을 사용합니다. 다중 상속을 지원하는 언어에서만 사용 가능합니다.
인터페이스를 준수하는 어댑터 클래스를 만드는 객체 어댑터와는 다르게 인터페이스가 아닌 MallardDuck과 WildTurkey 클래스 2개를 다중 상속해서 어댑터 클래스를 만듭니다.
그렇기 때문에, 특정 어댑티 클래스에만 적용할 수 있다는 단점이 있습니다. 하지만 서브클래스이기 때문에 어댑티의 행동을 오버라이드 할 수 있는 장점이 있습니다.
상속을 사용하므로 코드 분량은 상대적으로 적습니다.
객체 어댑터
구성(Composition)을 사용합니다.
구성으로 어댑티에 요청을 전달합니다.
구성을 사용하므로 어댑터 클래스와 그 서브클래스들에 대해서도 어댑터 역할이 가능합니다.
어댑터 코드에 어떤 행동을 추가하면 어댑티와 모든 서브클래스에 그대로 적용되기 때문에 상대적으로 유연성이 뛰어납니다.
둘 다 자바의 인터페이스인데, Enumeration이 구형 Iterator가 신형이랍니다. iOS도 objc와 swift가 있으니 대충 그런가보다 하고 이해하면 되겠네요.
hasNext()는 hasMoreElements()로, next()는 nextElement()로 대응됩니다.
하지만 Iterator의 remove()는 Enumeration에 해당하는 기능을 제공하지 않습니다. 이처럼 메소드가 일대일로 대응되지 않는 상황에서는 어댑터 차원에서 완벽한 구현 방법은 없습니다. 그나마 가장 좋은 방법은 런타임 예외를 던지는 것입니다. 자바에서는 UnsupportedOperationException이라는 게 있다고 합니다.
데코레이터 패턴
여러개의 클래스를 감쌀 수 있습니다.
인터페이스는 바꾸지 않고 데코레이터로 감싸질 때 마다 기능/책임을 추가하는 용도로 사용된다.
어댑터 패턴
여러개의 클래스를 감쌀 수 있습니다.
인터페이스를 다른 인터페이스로 용도로 사용된다.
퍼사드 패턴
여러개의 클래스를 감쌀 수 있습니다.
복잡한 인터페이스를 단순하게 만드는 용도로 사용된다.
복잡한 인터페이스를 단순하게 바꾸려고 인터페이스를 변경합니다.
하나 이상의 클래스 인터페이스를 깔끔하면서도 효과적인 퍼사드(겉모양, 외관)으로 덮어줍니다.
퍼사드 클래스는 서브 시스템 클래스(Light, Screen, Popper...)를 캡슐화 하지 않습니다. 그냥 더 쉽게 사용하기 위한 인터페이스만 제공할 뿐이죠. 이게 대표적인 장점인데, 캡슐화 하지 않기 때문에 원한다면 얼마든지 원래 서브 시스템 클래스 인스턴스를 생성한다던지 해서 계속 사용할 수 있게 합니다.
최소 지식 원칙(Principle of Least Knowledge, LoD, Law of Demeter)을 준수하는데 도움이 됩니다.
공통 코드
퍼사드 패턴 적용 전의 코드
영화를 보기 위해서는 할 일이 아주 많습니다.
팝콘 기계를 켜고, 튀기고, 조명을 조절하고, 스크린을 내리고, 프로젝트를 켜고...
영화를 끌 때도 마찬가지로 위의 과정을 역순으로 해야 합니다. 힘들어요.
퍼사드 패턴 적용 후의 코드
팝콘, 조명, 스크린, 프로젝트 같은 서브 시스템을 합쳐서 통합 인터페이스를 만들어버립니다.
최소 지식 원칙(Principle of Least Knowledge)은 디미터/데메테르 법칙(Law of Demeter)이나 축약해서 LoD 라고도 불린다.
한 마디로 정의하면 객체 사이의 상호작용은 될 수 있으면 아주 가까운 '친구'사이에만 허용하는 것이 좋다는 말이다.
즉, 시스템을 디자인 할 때, 어떤 객체든 그 객체와 상호작용을 하는 클래스의 개수와 상호작용 방식에 주의를 기울여야 한다는 뜻이다.
이 원칙을 잘 따르면 여러 클래스가 복잡하게 얽혀 있어서 시스템의 어느 한 부분을 변경했을 때, 다른 부분까지 줄줄이 고쳐야 하는 상황을 방지할 수 있습니다.
이 원칙을 잘 따르지 않는 코드는 여러 클래스가 서로 복잡하게 의존하고 있어 관리하기도 힘들고, 남들이 이해하기 어려운 코드가 됩니다.
장점이 있는만큼 단점도 있습니다. 원칙을 적용하다보면 '래퍼'클래스를 더 만들어야 할 수도 있습니다. 그러면 시스템이 복잡해지고, 개발 시간도 늘어나고, 성능도 떨어집니다.
친구를 만들지 않는 4개의 가이드라인
객체 자체는 호출 (O)
메소드에 매개변수로 전달된 객체는 호출 (O)
메소드를 생성하거나 인스턴스를 만든 객체는 호출 (O)
객체에 속하는 구성 요소는 호출 (O)
아래 클라이언트의 친구는 퍼사드 하나 뿐이죠. 친구는 찐친구 하나만 있어도 됩니다.
https://www.hanbit.co.kr/store/books/look.php?p_code=B6113501223