디자인 패턴의 필요성2 Design Patterns

"디자인 패턴의 필요성1" 을 통해서 봤듯이 초기 디자인이 변화와 유연한지를 좌우하는 중요한 이슈가 된다.

이번에는 이를 해결하기 위한 생각을 해보도록 하겠다.

인터페이스는 어떨까?

홍길동은 상속을 활용하는 것이 옳은 해법이 아닐지도 모른다는 것을 깨달았다. 앞으로 변화는 계속될거라는 요구가
들어왔기 때문이다. 규격이 계속 바뀔 것이라는 점은 불 보듯 뻔하고, 그렇게 되면 매번 젠에 프로그램에 추가 했던
Duck의 서브클래스의 fly(), quack() 메소드를 일일히 살펴봐야 하고, 상황에 따라 오버라이드 해야 할 수도 있고...
무척 고민 스럽니다.

문제를 명확하게 파악해 보자.

서브클래스 마다 오리의 행동이 바꿀 수 있는데도 모든 서브클래스에서 한 행동을 하도록 하는것은 올바르지 못하다.
Duck 슈퍼클래스에서 fly(), quack() 메소드를 빼고 Flyable과 Quackable 인터페이스를 만들 수도 있다. 이렇게 하면 날 수 있는 오리들에 대해서만 그 인터페이스를 구현해서 fly() 메소드를 집어넣을 수 있다.

하지만 이것도 문제가 있다.  Flyable과 Quackable 인터페이스에는 구현된 코드가 전혀 들어가지 않기 때문에 코드 재사용을 할 수 없다는 문제점이 있다. 즉 한 행동을 바꿀때 마다 그 행동의 정의되어 있는 서로 다른 서브클래스들을 전부 찾아서 코드를 일일히 고쳐야 하고, 그 과정에서 새로운 버그가 생길 가능성도 있다.

애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분으로부터 분리시킨다.

즉, 코드에 새로운 요구사항이 있을 때마다 바뀌는 부분이 있다면, 그 행동을 바뀌지 않는 다른 부분으로 부터 골라내서 분리해야 한다는 것이다.

"바뀌는 부분은 따로 뽑아서 캡술화시킨다. 그렇게 하면 나중에 바뀌지 않는 부분에는 영향을 미치지 않은 채로 그 부분만 고치거나 확장할 수 있다."

이 개념은 매우 간단하지만 모든 디자인 패턴의 기반을 이루는 원칙이다. 그러면 코드를 변경하는 과정에서 의도하지 않은 일이 일어나는 것을 줄이면서 시스템의 유연성을 향상시킬 수 있다.

바뀌는 부분과 그렇지 않은 부분 분리하기

fly()와 quack()은 Duck 클래스에서 오리마다 달라지는 부분.

이러한 행동을 Duck 클래스로부터 갈라내기 위해서 그 두 메소드를 모두 Duck 클래스로부터 끄집어내서 각 행동을 나타낼 클래스 집합을 새로 만든다.

오리의 행동 디자인

우선 최대한 유연하게 만든다. 애초에 이런 문제에 말려든게 오리의 행동과 관련된 유연성 때문이다. 그리고 Duck의 인스턴스에 행동을 할당할 수 있어야 한다.

목표

1. MallardDuck 인서턴스를 새로 만들고 특정 형식의 나는 행동으로 최기화
2. 오리의 행동을 동적으로 바꿀 수 있도록 한다. 즉 Duck클래스에 행동과 관련된 세터(setter) 메소드를 포함시켜서
프로그램 실행중에도 MallardDuck 의 나는 행동을 바꿀 수 있도록 하면 더욱 좋을 것이다.

디자인 원칙

구현이 아닌 인터페이스에 맞춰서 프로그래밍 한다.

각 행동은 인터페이스(FlyBehavior, QuackBehavior)로 표현하고 행동을 구현할 때 이런 인터페이스를 구현한다.
나는 행동과 소리내는 행동은 이제 Duck클래스에서 구현하지 않는다. 대신 특정 행동만을 목적으로 하는 클래스의
집합을 만든다.

정리하자면 Duck의 행동은 (특정 행동 인터페이스를 구현한) 별도의 클래스 안에 들어있게 된다.
그렇게 하면 Duck 클래스에서는 그 행동을 구체적으로 구현하는 방법에 대해서는 더 이상 알고 있을 필요가 없다.

이 방법은 지금까지 썻던, 행동을 Duck 클래스에서 구체적으로 구현하거나 서브클래스 자체에서 별도로 구현하는 방법하고는 상반되는 방법이다. 전에 썼던 두 방법에서는 항상 특정 구현에 의존했다. 특정 구현을 써야만 했기 때문에 (코드를 더 작성하는 것 외에는) 행동을 변경할 여지가 없다.

새로운 디자인을 사용하면 Duck의 서브클래스에서는 인터페이스(FlyBehavior, QuackBehavior)로 표현되는 행동을 사용하게 된다.

---------------------------
     <<인터페이스>>
        FlyBehavior
---------------------------
fly()
---------------------------
            |          | 
            |          --------------------------------------
            |                                                                  |
---------------------------                          ----------------------
      FlyWithWings                                               FlyNoWay
---------------------------                          ----------------------
fly() {                                                           fly() {
//나는 방법을 구현                                         // 아무것도 하지 않음
}                                                                  }
---------------------------                          -----------------------

이런식으로 디자인하면 다른 형식의 객체에서도 나는 행동과 꽥꽥거리는 행동을 재사용 할 수 있다.
그런 행동이 더이상 Duck클래스 안에 숨겨져 있지 않기 때문이다.
그리고 기존의 행동 클래스를 수정하거나 날아다니는 행동을 사용하는 Duck클래스를 전혀 건드리지 않고도
새로운 행동을 추가 할 수 있다.

따라서 상속을 쓸 때  떠안게 되는 부담을 전부 떨쳐 버리고도 재사용의 장점을 그래도 누릴 수 있다.



- 이 종 화 (ingenuity.egloos.com) -

덧글

댓글 입력 영역