디자인패턴이란 반복되는 문제의 패턴을 발견하고, 유지보수 작업 시간을 줄이는 노력이다.
여러 종류의 오리 만들기
- 객체지향 기법을 사용하여 Duck 슈퍼클래스를 만든 다음, 그 클래스를 확장하여 다양한 종류의 오리를 만들 수 있다.
- Duck 슈퍼클래스는 꽥꽥소리지르기 - quack(), 수영하기 - swim(), 모양보여주기 - display() 메서드를 가지고 있다.
오리를 날개 하려면 어떻게 해야 하나
- 슈퍼클래스에 fly() 메서드를 추가하면 된다.
RubberDock(고무오리)는 날 수 없다
- 코드의 일부만 변경했지만 프로그램 전체에 부작용이 발생할 수 있다.
- Duck의 모든 서브클래스가 fly()하는 것은 아니다.
- RubberDuck(고무오리)은 날수 없기 때문에 적합하지 않은 행동이 추가된 것이다.
fly()를 오버라이드 하면 되지 않을까
- RubberDuck과 같은 서브클래스가 만약 100개라면, 100개의 fly() 메서드를 재정의 해줘야 하며, 유지보수도 어려워질 수 있다.
- 코드 재사용성을 높이기 위해 사용한 상속의 의미가 옅어지게 된다.
인터페이스를 활용하면 어떨까
- Duck 슈퍼클래스의 fly(), quack() 메서드를 Flayable, Quackable 인터페이스로 만들어보자.
- 이렇게 되면 날 수 있는 오리와, 꽥꽥 소리지를 수 있는 오리들만 Flyable, Quackable 인터페이스를 구현하게 해서 fly(), quack() 메소드를 집어넣을 수 있게 된다.
- 그러나 코드 재사용성이 떨어질 수 있다.
- 오버라이드와 마찬가지로 50개의 서브클래스 중 날아다니는 동작을 조금 바꾸려면 48개의 코드를 전부 고쳐야 하는 문제가 발생할 수 있다.
첫 번째 디자인 원칙
애플리케이션에서 달라지는 부분을 찾아 내고, 달라지지 않는 부분으로부터 분리시킨다.
- 위 내용을 통해 상속은 서브클래스들의 구체 행위의 특성에 따라 코드 재사용을 할 수 없는 문제가 발생할 수 있음을 보여준다.
- 따라서 바뀌는 부분은 따로 뽑아서 캡슐화 시켜 나중에 바뀌지 않는 부분에는 영향을 미치지 않은 채로 그 부분만 고치거나 확장할 수 있도록 해야 한다.
- 이 개념은 모든 디자인패턴의 기반을 이루는 원칙이다.
두 번째 디자인 원칙
구현이 아닌 인터페이스에 맞춰서 프로그래밍 한다.
- 달라지는 부분을 분리하여 인터페이스로 추상화시킨다. 그리고 인터페이스를 서브클래스에서 구현하는 것이 아니라, 구현하는 클래스들을 따로 생성하여 캡슐화 한다.
- Duck 클래스로부터 fly()와 quack()을 분리하자.
-
각 행동은 인터페이스 (FlyBehavior, QuackBehavior)로 표현하고 행동을 구현할 때 이 인터페이스를 구현하는 클래스를 만들도록 한다.
-
날수 있는 오리, 날수 없는 오리가 있으므로 각각의 오리의 특성에 맞는 fly()를 FlyBehavior 인터페이스를 구현하는 클래스로 만들자.(QuackBehavior도 마찬가지)
- Duck의 서브클래스에서는 위 인터페이스로 표현되는 행동(클래스)을 사용하게 된다.
- 따라서 행동을 실제로 구현한 것(FlyWithWings, FlyNoWay)은 Duck 서브클래스에 국한되지 않는다.
- 정리하자면 인터페이스에 맞춰서 프로그래밍 하는 것은 '상위 형식에 맞춰서 프로그래밍한다'는 것을 의미한다. 이것은 어떤 상위 형식(supertype)에 맞춰서 프로그래밍함으로써
다형성
을 활용한다는 것이다. 즉, 변수를 선언할 때 보통 추상 클래스나 인터페이스 같은 상위 형식으로 선언해야 하는데, 객체를 변수에 대입할 때 상위 형식을 구체적으로 구현한 형식이라면 어떤 객체든 집어넣을 수 있게 된다. → 변수를 선언하는 (Duck)클래스에서는 fly 및 quack 변수의 실제 객체의 형식을 몰라도 된다. Duck 클래스에 fly변수의 타입으로 FlyBehavior를 선언하게 되면, 서브클래스에서 FlyBehavior를 구현하는 FlyWithWings 및 FlyNoWay 클래스로 초기화 시켜주면 된다. Duck 클래스는 쉽게 바뀔 수 있는 fly 메서드로부터 자유로워질 수 있고, 서브클래스에서는 fly코드를 재정의할 필요가 없어지기 때문에 코드 재사용성을 높일 수 있게 된다. fly 코드 수정이 필요하다면 fly행위를 정의한 클래스만을 수정하면 되기 때문이다.
세 번째 디자인 원칙
상속보다는 구성을 활용한다.
- 구성(composition)이란 'A에는 B가 있다 관계'를 의미한다.
- Duck 슈퍼 클래스에는 FlyBehavior / QuackBehavior 인터페이스가 있으며, (선언만 되어 있고, 아직 생성되지는 않은 상태) 서브클래스에서 각각의 구체적 행동을 다른 클래스로부터 위임받아 생성한다.
- 구성을 이용하면 시스템의 유연성을 향상시킬 수 있다.
스트래티지 패턴(Strategy Pattern)
알고리즘군을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있도록 만든다.
스트래티지를 활용하면 알고리즘을 사용하는 클라이언트와는 독립적으로 알고리즘을 변경할 수 있다.