소프트웨어 개발 불변의 진리

이 책에 따르면 소프트웨어 개발 불변의 진리는 "변화" 라고 한다.

아무리 디자인을 잘한 애플리케이션이라도 시간이 지남에 따라 변화하고 성장해야 한다.

그렇지 않으면 그 애플리케이션은 죽고 만다.

 

전략 패턴

SimUDuck이라는 오리 시뮬레이션 게임이 있다고 가정하자.

이 게임에는 헤엄도 치고 꽥꽥 소리도 내는 매우 다양한 오리가 등장한다.

이 시스템을 처음 디자인한 사람은 표준 객체지향 기법을 사용하여 Duck이라는 베이스클래스를 만든 다음, 그 클래스를 확장해서 서로 다른 종류의 오리를 만들었다.

 

이 게임은 작년부터 다른 경쟁사로부터 심한 압박을 받고 있다.

회사 임원진은 회동한 뒤 다음 주주총회에서 보여주기 위해 대규모적인 혁신이 필요하다고 결론을 내렸다.

회사 임원진은 다른 오리 시뮬레이션 게임을 이기려면 모든 오리를 날게 만들어야 한다고 결론지었다.

 

고무오리를 제외한 모든 자식 클래스들을 날게 만들어야 한다.

고무오리만 어떻게 제외시킬까?

 

보통은 fly 멤버함수를 고무오리에 한정해서 오버라이드시킬 것이다.

그러나 이것은 옳지 못한 방법이다.

만약 살아있는 오리에 한정해서 눈을 깜빡이는 기능을 추가시킨다 가정해보자.

 

그러면 고무오리 클래스에 "이 클래스는 눈을 깜빡이지 않는다"고 오버라이드 시켜줘야 한다.

나무오리가 추가되면

"이 클래스는 소리를 내지 않는다"고 오버라이드 시켜줘야 하고

"이 클래스는 눈을 깜빡이지 않는다"고 오버라이드 시켜줘야 한다.

또 베이스클래스에 깃털을 붙인다면 고무오리와 나무오리는 깃털이 없으므로

"이 클래스는 깃털이 없다"고 일일히 명시해줘야 한다.

 

코드를 추가할 때마다 전혀 상관없어 보이는 클래스에 무언가를 명시해야 하는 구조의 프로그램은 프로그래머가 작업하기에 매우 헷갈린다.

인간이 컴퓨터가 아닌 이상은 버그가 생길 수밖에 없다.

 

이보다 훨씬 깔끔한 방법이 있다.

 

다중상속? 코드가 중복되니 피해야 한다.

또한 서브클래스마다 오리의 행동이 바뀔 수 있는데, 다중상속을 사용하면 행동이 고정된다.

게다가 한 가지 행동을 바꿀 때마다 그 행동이 정의되어 있는 서로 다른 자식클래스를 전부 찾아서 코드를 일일히 고쳐야 하고, 그 과정에서 새로운 버그가 생길 가능성도 있다.

 

이 상황에 적용할 가장 적절한 것은 (애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분과 분리한다)는 디자인 원칙이다.

다시 말해 코드에 새로운 요구 사항이 있을 때마다 바뀌는 부분이 있다면 분리해야 한다.

이 원칙은 다음과 같이 생각할 수도 있다.

 

"바뀌는 부분은 따로 뽑아서 캡슐화한다. 그러면 나중에 바뀌지 않는 부분에는 영향을 미치지 않고 그 부분만 고치거나 확장할 수 있다."

 

이 개념은 매우 간단하지만 다른 모든 디자인 패턴의 기반을 이루는 원칙이다.

모든 패턴은 "시스템의 일부분은 다른 부분과 독립적으로 변화시킬 수 있는 방법"을 제공하기 때문이다.

 

 

그렇다면 오리의 본체와 분리시켜 캡슐화시킨 오리의 행동을 어떻게 통합할까?

우선 Duck 클래스에 flyBehavior와 quackBehavior라는 인터페이스 형식의 인스턴스 변수를 추가한다(구상 클래스 형식으로 선언하지 않는다)

각 오리 객체에서는 실행시에 이 변수에 특정 행동 형식의 레퍼런스를 다형적으로 설정한다.

나는 행동과 꽥꽥거리는 행동은 FlyBehavior와 QuackBehavior 인터페이스로 옮겨놨으므로 Duck 클래스에 fly()와 quack() 메소드를 제거한다.

Duck 클래스에 fly()와 quack() 대신 performFly()와 performQuack()이라는 메소드를 넣는다.

 

구성

"A has B" 관계를 생각해보자. 각 오리에는 FlyBehavior와 QuackBehavior가 있으며, 각각 나는 행동과 꽥꽥거리는 행동을 위임받는다.

 

이런 식으로 두 클래스를 합치는 것을 (구성(composition)을 이용한다 고 부른다.

여기에 나와있는 오리 클래스에서는 행동을 상속받는 대신, 올바른 행동 객체로 구성되어 행동을 부여받는다.

구성은 매우 중요한 테크닉이자 세 번째 디자인 원칙이기도 하다.

 

구성을 활용해서 시스템을 만들면 유연성을 크게 향상시킬 수 있다.

단순한 알고리즘군을 별도의 클래스로 캡슐화할 수 있으며, 구성 요소로 사용하는 객체에서 올바른 행동 인터페이스를 구현하기만 하면 실행 시에 행동을 바꿀 수도 있다.

구성은 여러 디자인 패턴에서 쓰이며, 앞으로 이 책 전반에 걸쳐서 구성의 장단점을 배운다.

 

전략 패턴의 개념

알고리즘군을 정의하고 캡슐화해서 각각의 알고리즘군을 수정해서 쓸 수 있게 해주는 패턴

전략 패턴을 사용하면 클라이언트로부터 알고리즘을 분리해서 독립적으로 변경할 수 있다.

+ Recent posts