안녕하세요 돼지왕 왕돼지입니다.
오늘은 "상속을 위한 설계와 문서화를 하자. 그렇지 않다면 상속의 사용을 금지시킨다" 라는 내용을 다뤄보고자 합니다.
이 글은 "Effective Java" 의 내용을 요약 정리한 글입니다.
이 글을 읽기 전에 이 글을 먼저 읽으시길 권장해드립니다.
2012/03/29 - [프로그래밍 놀이터/디자인 패턴] - [Design Pattern/Java] 가급적 상속(Inheritance) 보다는 컴포지션(composition)을 사용하자.
무엇을 문서화해야 하는가?
- 메소드 오버라이딩으로 인한 파급 효과를 분명하게 문서화해야 합니다.
일반적으로 말하면, 오버라이드 가능한 메소드를 호출할 수 있는 어떤 상황에 대해서도 문서화해야 한다.
- 오버라이딩 가능한 메소드들의 자체 사용(self-use) 즉, 그 메소드들이 같은 클래스의 다른 메소드를 호출하는지에 대해 반드시 문서화해야합니다.
- 각각의 public 이나 protected 메소드 및 생성자가 어떤 오버라이드 가능한 메소드를 호출하는지, 어떤 순서로 하는지, 호출한 결과가 다음 처리에 어떤 영향을 주는지에 대해서도 반드시 문서화해야 한다. ( 잘된 API 문서는 메소드가 무슨 일을 하는지(What)를 기술해야 하고, 어떻게 하는지(how)를 설명해서는 안 된다는 통념을 어기는 것이 됩니다. 이것은 상속이 캡슐화를 위반함으로서 초래된 현상입니다. How를 설명해주는 것은 좋지 않지만, 서브 클래스가 안전하게 사용할 수 있게끔 문서화를 통해 상세 구현 내역을 기술해주어야만 합니다. )
어떤 식으로 기술해주어야 하는가?
- 문서화 주석의 제일 끝에 정리를 해줍니다. 설명의 시작은 "This implementation" 으로 시작하고, 배포판이 달라지면서 메소드의 동작이 변경될 수 있다는 것 뿐만 아니라, 내부 구현에 관한 설명도 넣어 주어야 합니다.
- 상속을 위한 문서화와 일반 API Document 가 프로그래머를 혼란스럽게 할 수 있다는 점도 고려하여, 이 둘을 확실히 구분하여 기술해주어야 합니다.
상속을 위한 설계는 무엇을 말하는가?
- 프로그래머가 어려움 없이 효율적인 서브 클래스를 작성할 수 있도록, 잘 선정된 protected 메소드와 protected 필드를 제공해야 합니다. protected 메소드와 필드는 서브 클래스에게 내부 구현을 그렇게 하겠다는 약속과 같은 것이기 때문에 가능한 최소화해야 합니다. 그렇다고 protected 맴버가 하나도 없다면 사실상 상속을 사용할 수 없는 클래스가 될수 있습니다.
- 상속을 위한 클래스를 테스트하기 위한 서브 클래스를 만들어 보고, protected 함수들을 조율해야 합니다. 중요한 protected 맴버를 빼먹었을 경우는 추가시켜주고, 사용하지 않는 protected 맴버는 private 으로 바꿔줄 수 있습니다. 이는 직접 상속을 하는 서브클래스를 제작함으로서 쉽게 알 수 있고, 통상 3개의 서브 클래스로 테스트하면 충분하다고 합니다. 그리고 그 3개 중 적어도 하나는 반드시 수퍼 클래스 제작자가 아닌, 다른 사람이 제작해봐야 합니다.
- 상속을 위한 설계는 지금, 그리고 앞으로도 지속적으로 self-use 패턴을 문서화한다는 약속을 하는 것입니다. 그리고 protected 맴버들에 내포된 구현 코드의 지속적인 유지 관리도 약속을 하는 것입니다. 이런 약속으로 인해 향후 배포판에서 클래스의 성능이나 기능을 향상시키기가 어렵거나 불가능 할 수 있다는 점을 염두해놓고 하는 것을 상속을 위한 설계라고 볼 수 있습니다. 그래서 "반드시" 서브클래스를 작성하여 테스트해야 합니다.
- 직접 또는 간접의 어떤 형태로든 생성자에서는 오버라이드 가능한 메소드를 호출해서는 안 됩니다. 이 규칙을 위반하면 프로그램이 잘 동작하지 않을 가능성이 높습니다. 슈퍼 클래스의 생성자는 서브 클래스의 생성자에 앞서 실행됩니다. 따라서 오버라이드 가능한 메소드를 수퍼 클래스 생성자에서 호출하면, 서브 클래스 생성자가 실행되지 전에 서브 클래스에서 오버라이딩한 메소드가 호출됩니다. 그럼 문제가 생길 소지가 많아지겠지요.
상속을 위한 클래스를 설계할 때 추가적으로 주의해야 할 사항들은 무엇인가?
- 수퍼클래스가 Cloneable 이나 Serializable 인터페이스를 구현하는 것은 좋은 생각이 아닙니다. 그 인터페이스들은 클래스를 상속받는 프로그래머에게 많은 부담을 줄 수 있지요. 서브 클래스가 이 인터페이스들을 구현할 수 있게 해줘야 합니다.
- 어쩔 수 없이 Cloneable 이나 Serializabe 인터페이스를 수퍼 클래스에서 구현해야만 한다면, clone 함수와 readObject 함수는 생성자와 흡사하게 동작하므로 생성자와 유사한 규칙이 적용된다는 것에 유의해야만 합니다. 직접 또는 간접의 어떤 형태로든 clone 이나 readObject 모두 오버라이딩 가능한 메소드를 호출하면 안 된다는 것이지요. Serializable 을 구현한 경우에 readResolve 나 writeReplace 메소드를 갖고 있다면, 이 두 함수들을 protected 로 해주어야 합니다. 서브 클래스에서 사용할 수 있도록 해야 하기 때문이죠.
[왜 생성자와 유사한 규칙이 적용된다는 걸까요?]
그럼 상속이 고려되지 않은 클래스들의 상속을 쓰는 것은 어떠한가?
이에 대해서는 먼저 아래 글을 읽어보시길 권장드립니다.
2012/03/29 - [프로그래밍 놀이터/디자인 패턴] - [Design Pattern/Java] 가급적 상속(Inheritance) 보다는 컴포지션(composition)을 사용하자.
위 글을 다시 한번 정리하자면 이런 내용입니다.
클래스에 변경이 생길 때마다 클라이언트의 서브 클래스가 제대로 동작하지 않을 가능성이 생깁니다. 이론에만 그치는 문제가 아니라, 상속을 위한 설계가 되지 않거나, 문서화되지 않은 final 이 아닌 클래스를 상속해서 사용하는 경우 겨함이 보고되는 경우가 많다고 합니다. 이런 문제를 해결하는 가장 좋은 방법은, 서브 클래스를 안전하게 만들 수 있도록 설계나 문서화하지 않은 클래스의 상속을 금지시키는 것이지요. 상속을 금지시키는 2가지 방법은, class 를 final 로 만들거나 생성자를 private 으로 만들고 public static factory method 를 제공하는 것입니다.
상속을 위한 설계와 문서화가 너무 어려운 것은 아닌가?
사실 어렵습니다. 그래서 많은 고급 프로그래머들이 가급적 상속(Inheritance)보다는 컴포지션(Composition)을 사용하길 권하고 있습니다. 상속을 위한 설계와 문서화가 너무 어렵다면, 다음과 같은 사항만을 지켜서 위험성을 낮추는 방법이 있습니다.
1. 클래스 내에서 오버라이드 가능한 메소드의 자체 사용(self-use)를 완전히 없앱니다.
2. 오버라이드 가능한 메소드의 자체 사용을 없애기 힘들다면, 그녀석을 private helper 메소드로 옮기고, 오버라이드 가능한 메소드에서는 그 지원 메소드들을 호출하여 사용하도록 하는 것입니다.
'프로그래밍 놀이터 > 디자인 패턴, 리펙토링' 카테고리의 다른 글
[용어] CQRS 란? ( Command Query Responsibility Segregation ) (0) | 2012.10.20 |
---|---|
[Design Pattern/Java] 추상 클래스보다는 인터페이스를 사용하자. (0) | 2012.03.29 |
[Design Pattern/Java] 가급적 상속(Inheritance) 보다는 컴포지션(composition)을 사용하자. (0) | 2012.03.29 |
[Design Pattern/Java] 가변성을 최소화 하자. (0) | 2012.03.27 |
[Design Pattern/Java] public 클래스에서는 public 필드가 아닌 접근자 ( accessor ) 메소드를 사용한다. (0) | 2012.03.14 |
댓글