2011.04.18 20:58
  항목 16에서 상속을 위한 설계와 문서화를 하지 않은 클래스로부터 상속을 받을 때의 위험성을 경고하였다. 그렇다면 상속을 위한 설계와 문서화가 된 클래스란 무슨 의미일까?

 우선, 그런 클래스에서는 메소드 오버라이딩으로 인한 파급 효과를 분명하게 문서화해야 한다. 달리 말해 오버라이드 가능한 메소드들의 자체사용(self-use) 즉, 그 메소드들이 같은 클래스의 다른 메소드를 호출 하는지에 대해 반드시 문서화해야 한다.

 이와는 반대로, 각각의 public이나 protected 메소드 및 생성자가 어떤 오버라이드 가능한 메소드를 호출하는지, 어떤 순서로 하는지, 호출한 결과가 다음 처리에 어떤 영향을 주는지에 대해서도 반드시 문서화해야한다. 더 일반적으로 말하자면, 오버라이드 가능한 메소드를 호출 할 수 잇는 어떤 상황에 대해서도 문서화해야 한다는 것이다. 
  
 관례적으로 오버라이드 가능한 메소드를 호출하는 메소드에는 문서화 주석의 제일 끝에 그런 호출에 대한 설명을 추가한다. 그리고 설명의 시작은 "이번 구현 버전(This implementation)"이라는 구문으로 한다. 배포판이 달라지면서 메소드의 동작이 변경될 수 있다는 것을 나타내기 위해서만 그런 구문을 넣어서는 안된다. 그 구문에서는 메소드 내부에서 처리하는 작업에 관련되는 설명을 담고 있어야 한다. 

 책에서는 java.util.AbstractCollection의 명세를 예로 들고 있다. 참고하기를 바란다.

 그러나 잘된 API 문서에는 메소드가 무슨(what) 일을 하는지 기술해야 하고,   어떻게(how) 하는지를 설명하면 안된다는 통념을 어기는 것이다.
  이것은 결국 상속이 캡슐화를 위반함으로써  초래된 불행인 것이다. 서브 클래스가 안전할 수 있게끔 클래스를 문서화하려면, 클래스의 상세 구현 내역을 기술해야 한다(보통때는 하지 않지만).

 오버라이드 가능한 메소드들이 같은 클래스의 다른 메소드를 호출하는 자체 사용을 문서화 하는것 이외에도 상속을 위한 클래스 설계에는 고려할 점이 많다. 그 중 하나로써 잘 선정된 protected 메소드(또는 드물지만 protected 필드)를 제공하여 클래스 내부의 다른 메소드와 연결되도록 해야 한다.
 또한 상속 관련해서 특별히 추가되는 내용과 일반적인 내용(평범하게 클래스 인스턴스를 생성하고 메소드를 호출하는 프로그래머를 위한)이 섞임으로 API 문서를 보기가 혼란스러워 질 수 있다는것에 유의하자.

 상속을 허용하기 위해 클래스가 준수해야 하는 제약이 몇가지 더 있다. 직접 또는 간접의 어떤 형태로든 생성자에서는 오버라이드 가능한 메소드를 호출하면 안된다.

 상속을 위한 클래스를 테스트하는 유일한 방법은 서브 클래스를 만들어 보는것이다.
 그리고 그 서브클래스는 수퍼클래스 작성자가 아닌 사람이 작성해야 한다.



 상속을 위한 클래스 설계에서 Cloneable과 Serializable 인터페이스 중 어느 하나를 구현하는 상속을 위한 클래스를 설계하는 것은좋은 생각이 아니다.(textbook 항목 11과 74 참조)
 꼭 구현해야겠다면 clone과 readObject 메소드가 생성자와 흡사하게 동작하므로 생성자와 유사한 규칙이 적용된다는 것에 유의해야 한다. 즉, 직접 또는 간접의 어떤 형태로든 clone이나 readObject 모두 오버라이딩 가능한 메소드를 호출하면 안된다.
 마지막으로, 상속을 위해 설계된 클래스가 Serializable 인터페이스를 구현하기로 했는데, 그 클래스가 readResolve나 writeReplace 메소드를 가지고 있다면, 그 메소드들을 private가 아닌 protected로 해야한다. 만일 그 메소드들이 private라면 서브 클래스에서 사용할 수 없기 때문이다. 상속을 허용하기 위해 클래스 내부의 구현 메소드가 그 클래스의 API가 되어야 하는 것이다.



 많은 논점이 있었지만 중요한 것은 상속을 위해 클래스를 설게하다 보면 그 클래스에 많은 제약이 생긴다. 라는것이다. 서브 클래스를 안전하게 만들수 있도록 설계나 문서화를 제공하지 않는 클래스의 상속을 금지하는 것이 좋은 방법이라고 할 수 있겠다.
 금지하는 방법은 클래스를 final로 선언하거나, 생성자를 private 혹은 default로 두고 public static 팩토리 메소드를 추가하는것이다.(항목15)

 하지만 실제 프로그래밍에서는 실체 클래스에서 상속을 사용하는 일이 많을 것이므로 이럴 경우에는 항목 16에서 설명한 래퍼 클래스를 만들거나 어떤 오버라이드 가능한 메소드의 자체 사용(self-use)를 완전히 없애는 방법도 있다. 또는 오버라이드 가능한 메소드의 몸체코드를 private "지원(helper)메소드"로 옮기고, 오버라이드 가능한 메소드에서는 그 지원 메소드를 호출하도록 하는 방법도 있다.




 
신고
Posted by JAVA_HOME

댓글을 달아 주세요

2011.04.18 20:20
 상속은 캡슐화를 위배하기 떄문에 부실한 소프트웨어를 초래한다.(textbook에서 다루는 상속의 문제점은 구현 상속(implementation inheritance, 클래스간의 상속)에서의 문제점을 의미한다. 인터페이스 상속(interface inheritance, 클래스에서 인터페이스를 구현하거나, 인터페이스가 다른 인터페이스로 확장되는 것)에는 적용되지 않는다.)

 textbook에서 거론한 잘못된 상속 사용법은 우리가 쉽게 저지를 수 있는, 그리고 이미 저지르고 있는 방법이므로 꼭 읽어보기를 바란다.


 그렇다면 대안은 무엇이 있을까?

 상속을 하는 대신 기존 클래스(상속에서 수퍼 클래스에 해당되는)의 인스턴스를 참조하는 private 필드를 새로운 클래스(상속에서 서브 클래스에 해당되는)에 둔다. 이런 식의 설계를 컴포지션(composition)이라 하는데, 그 이유는 기존 클래스가 새 클래스의 컴포넌트로 포함되기 때문이다.

  새 클래스의 각 인스턴스 메소드에서는 포함된 기존 클래스 인스턴스의 대응되는 메소드를 호출하여 결과를 반환할 수 있다. 이것을 포워딩(forwarding)이라 하고 새 클래스의 메소드를 포워딩 메소드라 한다. 이렇게 하면, 새 클래스는 기존 클래스의 내부 구현에 종속되지 않고 강건하게 될 것이다. 기존 클래스에 새로운 매소드를 추가하더라도 새 클래스에는 영향을 주지 않는다.

 다음의 예시 코드를 보면 이해하기 쉬울 것이다. 다음 예시 코드는 두부분으로 나누어져있다. InstrumentedHashSet 클래스 코드 및 재사용 가능한 포워딩 클래스(포워딩 메소드만 갖고 있는) 코드이다.

 이러한 InstrumentedSet 클래스를 래퍼(Wrapper) 클래스라고 하는데, 그 이유는 각 InstrumentedSet 인스턴스가 다른 Set 인스턴스를 포장하듯 포함하기 때문이다. 
 이것을 데코레이터 패턴(Decorator Pattern)이라고도 하는데, InstrumentedSet 클래스가 자신의 기능을 다른 Set에 덧붙여 치장("decorates")하기 때문이다. 컴포지션과 포워딩을 결합한 것을 막연하게 위임(delegation)이라고 하는 경우도 있지만, 래퍼 객체 참조가 자신이 포함된 객체에 전달되지 않는다면 기술적으로 위임이라고 할 수 없다.
 
 래퍼 클래스의 단점은 거의 없지만, 차후에 호출("콜백")이 될 수 있도록 객체 자신의 참조를 다른 객체에게 전달하는 콜백 프레임워크(Callback framework)에서의 사용은 부적합하다. 래퍼 객체에 포함된 객체는 자신의 래퍼 객체를 알지 못하므로, 콜백을 할 경우 자신의 참조(this)만을 다른 객체에게 전달하기 때문이다. 이것은 이미 SELF 문제라고 알려져있다.

 포워딩 메소드는 작성하기 번거롭지만, 각 인터페이스에 대해 포워딩 클래스는 한번만 작성하면 되며, 관련 인터페이스가 포함된 곳과 같은 패키지에 둘 수도 있을 것이다. 

 
 컴포지션 대신 상속을 사용하기로 결정할 때는 API의 결함이 없는 super 클래스와 진정한 서브 타입 관계 즉 "is-a"관계일 때만 사용하여야 하며, 그렇지 않다면 컴포지션을 사용해야 한다.
신고
Posted by JAVA_HOME

댓글을 달아 주세요

2011.04.18 16:30
 인스턴스가 가변적이어야 할 타당한 이유가 없다면, 그 클래스는 불변 클래스가 되어야 한다.

 불변 클래스는 가변 클래스에 비해 설계와 구현 및 사용이 더 쉽고, 에러 발생이 적으며 보안이나 사용 측면에서 더 안전한 장점이 있다. 


 불변 클래스를 만들 때는 다음 다섯가지 규칙을 따르자.

1. 객체의 상태를 변경하는 그 어떤 메소드(변경자라고 하는)도 제공하지 않는다.
2. 상속(inheritance)을 할 수 없도록 하자.

더보기

3. 모든 필드를 final로 지정한다. 
4. 모든 필드를 private로 지정한다.
5. 가변 컴포넌트의 직접적인 외부 접근을 막자. 

더보기

 불변 클래스의 유일한 단점은 객체가 가질 수 있는 각 값마다 별개의 객체가 필요하다는 것이다. 

 현실적으로 불변 클래스가 되기 어려운 클래스가 있는데 이런 경우에는 가능한 가변성을 제한하자. 그리고 필드를 final로 하지 않아야 될 이유가 없는 한 모든 필드를 final로 만든다.

 책에서는 불변 클래스의 성능상 단점 및 극복방안에 대한 내용이 있는데 한번 읽어보면 도움이 될 것이다.
  
신고
Posted by JAVA_HOME

댓글을 달아 주세요

2011.04.18 16:01
 흔히 게터(getter)와 세터(setter)로 알고 있는 접근자 메소드(accessor) 메소드와 변경자(mutator)메소드를 사용한 캡슐화(encapsulation)는 모든 자바 개발자라면 하고 있을 것이다. 

 public 클래스와 같은 경우 위와 같은 방법으로 하는 것이 옳지만, 패키지 전용 클래스거나, private 중첩 클래스라면 데이터 필드를 노출해도 별로 문제 되지 않으므로 접근자 메소드를 사용하는 것보다 코드를 알아보기 쉽게 하기 위해서 노출하는 편이 좋을때도 있다. 

 한줄로 요약하면 public 클래스는 모든 가변 필드를 절대로 외부에 노출시키지 말아야 한다. 정도가 되겠다.
신고
Posted by JAVA_HOME

댓글을 달아 주세요

2011.04.18 13:28
OOP 소프트웨어 설계 기본원리 중 하나인 정보 은닉(Information hiding) 또는 캡슐화(Encapsulation)은 매우 중요하다.
 잘 설계된 모듈과 그렇지 않은 것을 구분 짓는 가장 중요한 잣대가 되며, 시스템을 구성하는 모듈들 간의 결합도를 낮추어 모듈 별로 개발, 테스트, 최적화, 사용 및 수정이 쉬워진다.

 자바에서는 정보 은닉을 지원하는 기능이 많은데, 그 중 하나인 접근 제어(access control) 메커니즘에서는 클래스와 인터페이스 및 멤버의 접근성(accessibility)을 지정하는데 그것들이 선언된 위치에 의해 접근성이 결정되며, 필요에 따라 접근 변경자(access modifier)인 private, protected, public을 선언문에 지정한다.

 멤버(필드,메소드,중첩클래스,중첩인터페이스)의 경우 위의 4가지 접근 수준이 가능하며, 접근 수준이 낮은 수준부터 높은 순으로 나열하면 다음과 같다.

- private : 멤버가 선언된 최상위 클래스 내에서만 접근 가능하다.
- (default) : 지정하지 않으면 default로 지정되는 수준이며, 멤버가 선언된 클래가 속한 패키지의 모든 클래스에서 접근 가능하다. 
- protected : default 접근 수준을 가지며, 추가적으로 멤버가 선언된 클래스의 서브 클래스로 부터 접근 가능하다.
- public : 어디에서든 접근 가능하다. 


 한가지만 기억하면된다. 각 클래스나 멤버의 접근 허용을 가능한 최소화 하는것이다. 
 
 클래스를 설계할 때는 우선적으로 public API를 신중하게 결정한 다음 그 외의 다른 멤버들은 private으로 해야 한다. 그리고 같은 패키지의 다른 클래스가 특정 멤버를 꼭 접근할 필요가 있을 떄만 그 멤버의 private 접근 지시자를 제거하여 패키지 전용이 되도록 한다. 
 접근 범위가 커질수록 지속적인 유지 보수가 필요하다는 것을 명심하자.

 메소드의 접근 수준을 줄일 수 없도록 제한 하는  규칙이 하나 있다. 서브 클래스의 메소드가 수퍼 클래스의 것을 오버라이드 하는 경우 수퍼 클래스에서 지정한 접근 수준보다 더 낮은 것을 지정할 수 없다. 그리고 이 규칙이 특별하게 적용되는 경우가 있는데, 인터페이스를 구현할 경우 반드시 public으로 선언되어야한다.


 인스턴스 필드는 절대로 public으로 하지말아야한다.
 또한 public 가변 필드를 갖는 클래스는 스레드에서 안정성을 보장할 수 없다.
 요소가 하나라도 있는 배열은 항상 가변적이라는 것에 주의하자. 따라서 클래스에서 public static final 배열 필드를 갖거나, 또는 그런 필드를 반환하는 접근자(accessor, 통칭 getter) 메소드를 갖는 것은 잘못된 것이다.

 그런 문제점을 해결하는 두 가지 방법이 있다. 하나는 public 배열을 private으로 하고, public의 불변 list를 추가하는 것이다.
 또 다른 방법으로는 배열을 private로 하고, 그 배열의 복사본을 반환하는 public 메소드를 추가하는 것이다.

신고
Posted by JAVA_HOME

댓글을 달아 주세요

2011.04.17 21:01
 클래스와 인터페이스는 자바 프로그래밍 언어의 핵심이며 추상화의 기본 단위이다. 자바에서는 클래스와 인터페이스를 설계하는데 사용할 수 있는 강력한 요소들을 제공한다. 우리가 만드는 클래스와 인터페이스가 쓸모 있고 강력하며 유연성이 있도록 하기 위해 이장에서는 그런 요소들을 최상으로 사용하는데 도움을 주는 지침을 설명한다. (textbook 인용)

  앞의 두 챕터에서는 성능이나 어떠한 메소드의 동작과 같은 국지적인 부분에 포커스를 맞추었지만(앞의 내용들이 중요하지 않다는 것은 아니다!), 이번 Chapter에서는 OOP 프로그래밍 설계를 JAVA 언어가 가진 특성을 활용하여 극대화 하는 방법 및 잘못되거나 비효율적인 방법으로 한 설계를 바로잡는 것을 다루는 Chapter 이므로 중요도가 매우 큰 Chapter라고 할 수 있겠다.
신고
Posted by JAVA_HOME

댓글을 달아 주세요


티스토리 툴바