Head First Design Pattern이란 책을 소프트웨어 디자인 패턴 수업에서 읽으라고 해서 읽었는데 내용이 좋은 것 같아서 정리해야겠단 생각이 들었다. 내용을 다 적기엔 너무 귀찮으니까 나중에 보면 기억날 정도로만 정리하려고 한다.
디자인 패턴을 배워야하는 이유는 추후 서비스의 기능이 추가되거나 새로운 구조가 생겨도 기존 코드의 변화를 최소화하면서 간단한 유지보수가 되어야 한다고 생각한다. Java와 같은 객체지향 언어는 이러한 점들을 구현하기 위해 추상화, 캡슐화, 다형성, 상속 등의 개념을 가지고 있다.
패턴을 바로 설명하기 전에 기초적인 내용을 간단하게 정리하고 시작하자.
먼저 추상클래스가 무엇인지 알아보자. 추상클래스는 하나 이상의 추상 메소드를 가지고 있는 클래스를 말한다. 해당 메소드는 그 클래스 안에서 정의되지 않고 추상클래스를 상속받은 클래스에서 반드시 정의되어야한다. 그 외엔 기존 클래스와 다른 점은 없다. (아마도?)
인터페이스는 추상 클래스와 다르게 추상 메소드와 상수만을 가진 집합이다. 인터페이스도 마찬가지로 이를 상속받은 클래스는 추상 메소드를 전부 정의해야한다.
추상 클래스와 인터페이스 모두 그 이름으로 인스턴스를 만드는 것은 불가능하고 이를 상속받은 클래스를 통해 인스턴스화 될 수 있다. 다형성을 깔고 간다는 얘기이다.
인터페이스와 추상 클래스의 차이점은 우선 다중상속의 여부이다. 자바는 기본적으로 다중 상속을 지원하지 않지만 인터페이스만은 다중 상속이 가능하다. 또한 인터페이스는 생성자나, 변수, 일반 메소드는 가질 수 없다는 것도 차이점이다.
일단 Strategy Pattern과 Observer Pattern까지 읽었을 때 배운 객체지향 원칙은 아래와 같다.
1. 바뀌는 부분은 캡슐화한다.
2. 상속보다는 구성을 활용한다.
3. 구현이 아닌 인터페이스에 맞춰서 프로그래밍한다.
4. 서로 상호작용하는 객체 사이에서는 가능하면 느슨하게 결합하는 디자인을 사용해야한다.
1은 내가 흔히아는 private로 만들고 getter, setter를 만든다는 개념보단 인터페이스를 만들고 그것들을 구현하는 다양한 클래스를 묶어서 말하는 것 같다.
2는 말 그대로 상속보단 사용하고 싶은 기능이 있는 클래스나 인터페이스를 멤버변수로 가지는 것을 말한다. 이렇게하는 것이 결국 4의 느슨한 결합에 더 가까워지는 길이다.
3은 기능을 바로 클래스안에서 구현하지 말고 변경의 여지가 많거나 다양한 종류로 나뉠 수 있는 것들은 인터페이스로 빼고 그것을 가지고 있는 것을 말한다.
4는 어느 한 쪽이 변해도 다른 한쪽에 영향이 거의 가지 않는 것을 말한다.
이 패턴이 제일 처음 나온 패턴인데 내가 지금까지 했던 고민들을 다 담고 있어서 신기했다. 일단 위의 3가지 개념을 합치면 Strategy Pattern이 된다. 이 패턴은 알고리즘군을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있도록 만드는 패턴이다. 알고리즘 군은 어떤 인터페이스를 상속받아 구현된 클래스 그룹을 의미한다. 책에서 스토리를 만들어 친절하게 설명하고 있는데 이걸 다 적기엔 너무 많아서 최종 클래스 구조만 올려놓겠다.
이 이미지를 보면 확장의 가능성이 다분한 소리를 내는 기능과 나는 기능은 따로 인터페이스로 빼고 최상위 클래스는 이 두 인터페이스를 멤버로 가지고 있는 것을 볼 수 있다. 자식 클래스는 생성자에서 이 두 인터페이스를 사용하고 싶은 기능에 맞게 정의해서 사용하면 된다.
이 패턴은 Subject와 Observer란 2개 역할이 있을 때 사용하는 패턴이다. 신문 구독이 좋은 예시인데 Subject가 신문회사면 Observer는 구독자가 되어 신문을 구독하면 매달 신문이 나오면 자신을 구독한 사람들에게 신문을 보내주고 구독을 해지하면 더 이상 보내지 않는 방식을 그대로 사용한다. Subject는 Observer를 등록하고 해지하는 2개의 함수를 필수적으로 가지고 있어야하고 Observer들을 담고 있는 리스트를 멤버 변수로 가지고 있다. 이 상태에서 Subject의 상태가 변하면 자기의 데이터를 Observer들에게 자신의 데이터를 보내주는 구조이다.
모든 Observer들이 Observer 인터페이스를 상속받고 있는 것에 유의하자 이렇게 해야 Subject에서 다형성을 이용해 깔끔하게 update함수를 불러 Observer들에게 데이터를 전달해줄 수 있다.
Subject의 상태가 변했을 때 Subject가 자신의 데이터를 능동적으로 전달해주는 push 방식이 있고 Observer들이 Subject에게서 원하는 데이터를 뽑아가는 pull 방식이 있다.
Observer observer = (Observer)observers.get(i)
observer.update(a,b,c)
만약 코드가 이렇게 작성되어 있으면 추후 Subject 클래스에 변수가 추가되면 Subject와 Observer 모두 일일히 수정해야한다. 그래서 이를 방지하기 위해 update(Subject o, Object arg) 이런식의 방식을 사용한다. pull 방식을 사용하면 arg는 NULL이 되고 Observer들이 o의 getter와 setter를 사용해 일일히 데이터를 가져간다. push 방식일 땐 arg에 보내주고 싶은 데이터를 담아서 보내준다.