"헤드 퍼스트 디자인 패턴(개정판)"을 읽고 정리한 내용입니다.
7. 적응시키기
어탭터 패턴
어탭터 패턴(Adapter Pattern)은 클래스의 인터페이스를 클라이언트가 요구하는 다른 인터페이스로 변환한다. 어댑터 패턴을 사용하면 호환되지 않아서 함께 사용할 수 없었던 클래스를 작동할 수 있게 해준다.
어댑터 패턴 예시
public interface Duck {
public void quack();
public void fly();
}
public class MallardDuck implements Duck {
@Override
public void quack() {
System.out.println("꽥꽥");
}
@Override
public void fly() {
System.out.println("날고 있습니다.");
}
}
public interface Turkey {
public void gobble();
public void fly();
}
public class WildTurkey implements Turkey {
@Override
public void gobble() {
System.out.println("골골");
}
@Override
public void fly() {
System.out.println("짧은 거리를 날고 있습니다.");
}
}
public class TurkeyAdapter implements Duck {
Turkey turkey;
public TurkeyAdapter(Turkey turkey) {
this.turkey = turkey;
}
@Override
public void quack() {
turkey.gobble();
}
@Override
public void fly() {
turkey.fly();
}
}
public class DuckTestDrive {
public static void main(String[] args) {
Duck duck = new MallardDuck();
Turkey turkey = new WildTurkey();
Duck turkeyAdapter = new TurkeyAdapter(turkey);
testDuck(duck);
testDuck(turkeyAdapter);
}
static void testDuck(Duck duck) {
duck.quack();
duck.fly();
}
}
// Output
꽥꽥
날고 있습니다.
골골
짧은 거리를 날고 있습니다.
퍼사드 패턴
퍼사드 패턴(Facade Pattern)은 하위 시스템에 있는 인터페이스의 집합에 대해 하나의 통합된 인터페이스를 제공한다. 퍼사드 패턴은 하위 시스템을 더 쉽게 사용할 수 있도록 더 높은 수준의 인터페이스를 정의한다.
디자인 원칙
- 애플리케이션에서 달라지는 부분을 식별하고 불변으로부터 그것들을 분리해라. (Identify the aspects of your application that vary and separate them from what stays the same.)
- 달라지는 부분을 찾아서 나머지 코드에 영향을 주지 않도록 캡슐화(encapsulate)한다. 그러면 나중에 바뀌지 않는 부분에는 영향을 미치지 않고 그 부분만 고치거나 확장할 수 있다.
- 구현이 아니라 인터페이스에 프로그래밍해라. (Program to an interface, not an implementation.)
- 특정 행동만을 목적으로 하는 클래스의 집합을 만들고, 이 클래스에서 구현한다. 그러면 슈퍼 클래스에서 구체적으로 구현하거나 서브 클래스 자체에서 별도로 구현하지 않아서 특정 구현에 의존하지 않는다.
- 상속보다는 구성이 더 낫다. (Favor composition over inheritance.)
- 여기서 구성(composition)은 행동을 상속받는 것 대신에, 올바른 행동 객체로 구성되어 행동을 부여받는 것을 말한다.
- 구성을 사용하면 유연성을 향상 시킬 수 있다. 알고리즘군을 별도의 클래스로 캡슐화 할 수 있을 뿐만 아니라, 구성 요소로 사용하는 객체에서 올바르게 행동 인터페이스를 구현하기만 한다면 실행 중에(runtime) 동적으로 행동을 바꿀 수도 있다.
- 상호작용하는 객체들 사이에는 느슨한 결합을 사용하도록 노력해라. (Strive for loosely coupled designs between objects that interact.)
- 느슨한 결합(Loose Coupling)은 객체들 사이의 종속성이 적다는 뜻이다. 즉, 객체들 사이의 유연성이 좋아진다. 대표적인 예로 "옵저버 패턴"이 있다.
- 주제(Subject)는 옵저버가 특정 인터페이스(Observer 인터페이스)를 구현한다는 사실만 알고있다. 즉, 주제는 옵저버가 무엇을 하는지 알 필요가 없다.
- 옵저버는 언제든지 새로 추가할 수 있다. (제거도 마찬가지이다.)
- 새로운 타입의 옵저버를 추가할 때도 주제를 수정할 필요가 없다.
- 주제와 옵저버는 서로 독립적으로 재사용할 수 있다.
- 주제나 옵저버가 달라져도 서로에게 영향을 미치지 않는다.
- 느슨한 결합(Loose Coupling)은 객체들 사이의 종속성이 적다는 뜻이다. 즉, 객체들 사이의 유연성이 좋아진다. 대표적인 예로 "옵저버 패턴"이 있다.
- 클래스는 확장에는 열려있어야 하지만, 변경에는 닫혀 있어야 한다. (Classes should be open for extension, but closed for modification.)
- 이 원칙은 OCP(Open-Closed Principle, 개방-폐쇄 원칙)이다.
- 기존 코드를 변경하지 않고 확장으로 새로운 행동을 추가해야 한다는 말이다.
- 추상화에 의존해라. 구상 클래스에 의존하지 마라.(Depend upon abstractions. Do not depend upon concrete classes.)
- 이 원칙은 DIP(Dependency Inversion Principle, 의존성 뒤집기 원칙)이다.
- 이 원칙을 지키기 위한 가이드 라인은 아래와 같다.
- 변수에 구상 클래스의 레퍼런스를 저장하지 마라.
- 구상 클래스에서 유도된 클래스를 만들지 마라.
- 베이스 클래스에 이미 구현되어 있는 메소드를 오버라이드하지 마라.
- 가까운 친구에게만 말해라. (Talk only to your immediate friends.)
- 이 원칙은 디미터의 법칙(Law of Demeter), 최소 지식 원칙(Principle of Least Knowledge)이다.
- 객체 간의 상호작용을 최소화하여 결합도를 낮춰야 한다는 말로, 체인 호출(점(dot)으로 계속해서 호출하는 것)을 피하라는 것이다.
- 이 원칙을 지키기 위한 가이드 라인은 아래와 같다.
- 객체 자신에 대해서 상호작용.
- 메소드에 매개변수로 전달된 객체에 대해서 상호작용.
- 메소드에서 생성하거나 인스턴스를 만든 객체에 대해서 상호작용.
- 객체에 속하는 구성 요소에 대해서 상호작용.
- 이 원칙은 디미터의 법칙(Law of Demeter), 최소 지식 원칙(Principle of Least Knowledge)이다.
디미터의 법칙, 최소 지식 원칙 예시
public float getTemp() {
Thermometer thermometer = station.getThermometer();
return thermometer.getTemperature();
}
위는 원칙을 지키지 않은 경우이다. Thermometer 객체가 Station 으로부터 Thermometer 객체를 받은 다음에 그 객체의 getTemperature() 메소드를 직접 호출하기 때문이다.
public float getTemp() {
return station.getTemperature();
}
위는 원칙을 지킨 경우이다. Thermometer 객체에게 요청을 전달하는 메소드를 Station 클래스에 추가해서 의존해야 하는 클래스의 개수를 줄였다.