본문 바로가기
Effective JAVA 2판

Effective JAVA Item16 계승하는 대신 구성하라

by BroBroBro 2015. 6. 5.

------------------------------------------------

본글은 Effective JAVA 2판의 책의 내용을 토대로 작성하였으며
지극히 주관적인 사항들이 많이 있으므로 가려서 읽으시기바랍니다.
잘못된 내용이 있거나 의견이 있으시다면 언제든 댓글로^^~!

------------------------------------------------

CHAPTER 4 클래스와 인터페이스

4장에서는 클래스와 인터페이스를 설계할때 이용할 수 있는 강력한 요소들을 많이 갖추고 있다고 한다. 

[Item 13] 클래스와 그 멤버의 접근 권한을 최소화하자 부터 보도록 하자 

Item14 public 클래스 안에는 public 필드를 두지말고 접근자(accessor) 메소드를 사용한라
Item15 변경 기능성을 최소화 하라 
Item16 계승하는 대신 구성하라
Item17 계승을 위한 설계와 문서를 갖추거나, 그럴 수 없다면 계승을 금지하라
Item18 추상 클래스 대신 인터페이스를 사용하라
Item19 인터페이스는 자료형을 정의할 때만 사용하라
Item20 태그 달린 클래스 대신 클래스 계층을 활용하라
Item21 전략을 표현하고 싶을 때는 함수 객체를 사용하라
Item22 멤버 클래스는 가능하면 static으로 선언하라.

--page01

용어에 우선 충실해보자 계승은 무엇이고 구성은 무엇인가? 보통은 부모 자식관계라고 말을 한다.

그렇게 알고있어도 된다. ^^ 하지만 extends 키워드를 사용하지 않는가? 바로 확장이라는 의미이다.

단순히 클래스를 확장 할수도있지만 abstract class를 확장해서 구체화된(concrete class)만드는것이라고 생각한다.

구성은 말그대로 내안에 구성하고 있다 가지고있다의 의미이다. 나자신의 부품정도로 생각하면될듯하다.

사람이 있다면 나의 왼팔 , 오른팔 정도를 나의 composition이라고 받아들이자. 아래그림으로 한번 표현해보았다.



계승을 사용한 적절하지 못한예제를 보도록 하자
“ addCount에는 몇이 찍히는가? “ 라고 책에서 질문을 던지면서 시작함… 글을 읽으시는분도 한번 count를 맞춰보세요^^?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class InstrumentedSet<E> extends ForwardingSet<E> {
    private int addCount = 0;
 
    public InstrumentedSet(Set<E> s) {
        super(s);
    }
 
    @Override
    public boolean add(E e) {
        addCount++;
        return super.add(e);
    }
 
    @Override
    public boolean addAll(Collection<extends E> c) {
        addCount += c.size();
        return super.addAll(c);
    }
 
    public int getAddCount() {
        return addCount;
    }
 
    public static void main(String[] args) {
        InstrumentedSet<String> s = new InstrumentedSet<String>(new HashSet<String>());
        s.addAll(Arrays.asList("Snap""Crackle""Pop"));
        System.out.println(s.getAddCount());
    }
}
cs

적절하지 못한 계승은 소프트웨어가 깨지기 쉽다. (릴리즈가 거듭되면서 망가진다.)

    - 메서드 호출과 달리 계승은 캡슐화 원칙을 위반한다.


깨지는 이유를 보려면 addAll의 내부를 보면 이해가 간다. 아래와 같다.

1
2
3
4
5
6
7
 public boolean addAll(Collection<extends E> c) {
        boolean modified = false;
        for (E e : c)
            if (add(e))
                modified = true;
        return modified;
    }
cs

이런한 클래스를 깨지기 쉬운(fragile)클래스라고 한다.


가령 어떤 프로그램의 보안이 어떤 컬렉션에 추가되는 모든 원소가 특정 술어(predicate)를 만족한다는 사실에 근거한다고 해보자. 이 술어를 계속

만족시키려면 컬렉션의 하위 클래스에서는 원소를 추가할 수 있는 모든 메서드를 재정의해서….(….ㅠㅠ)


실제로 java 만드는 과정에서 HashtableVectorCollection framework넣는과정에서 문제가 생겨 수정했다고 함.


*참고 : 자바는 복잡한언어다.

          (주부)  (술부)


*참고 : @SuppressWarning

 : 부적절한 컴파일러의 경고를 제거하기 위해 사용된다.


*참고 : @SafeVarargs 애노테이션 자바 7부터 새로 추가되었는데인자의 수가 일정하지 않은 메소드나 생성자가 안전하다고 지정할 때 사용된다.


*참고 : @SafeVarargs 대신 @SuppressWarning("unchecked") 애노테이션을 사용해도 되지만, 경우에 따라 경고 메시지가 생성될 수도 있기 때문에 @SafeVarargs을 사용하면 경고를 확실히 제거할 수 있다 


예제의 설명으로 다시 돌아가서...

 앞서 본문제는 메소드의 재정의 때문에 발생한것이다.

재정의가 아닌 추가 메소드로 하였지만 이후 릴리즈에서 재수없게도 시그니처(signature)가 같을수도 있을것이다. (더 이상 컴파일 되지 않을것이다.)

    *  파라미터 변수의 수, 타입, 순서 <--시그니처라 함


*참고 : 시그니처 error sample


1
2
3
4
5
6
7
8
9
10
11
12
//before
public class Mather {
  public String matherTest(int a, int b){
    return null;
  }
}
//after
public class Child extends Mather{
  public int matherTest(int a, int b){
    return null;
  }
}
cs


이제 해결을 해보자 .


이모든 방법을 기존 객체를 참조하는 private 필드를 하나 두는것으로 해결 가능하며 이런 설계 기법을 구성(composition)(일부)이라함


*참고 : composition class
method에 의해 필요한 것을 호출하는것을 전달 메서드(forwarding method)라고함


해결된 예제 sample

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class InstrumentedHashSet<E> extends HashSet<E> {
    // The number of attempted element insertions
    private int addCount = 0;
 
    public InstrumentedHashSet() {
    }
 
    public InstrumentedHashSet(int initCap, float loadFactor) {
        super(initCap, loadFactor);
    }
 
    @Override
    public boolean add(E e) {
        addCount++;
        return super.add(e);
    }
 
    @Override
    public boolean addAll(Collection<extends E> c) {
        addCount += c.size();
        return super.addAll(c);
    }
 
    public int getAddCount() {
        return addCount;
    }
 
    public static void main(String[] args) {
        InstrumentedHashSet<String> s = new InstrumentedHashSet<String>();
        s.addAll(Arrays.asList("Snap""Crackle""Pop"));
        System.out.println(s.getAddCount());
    }
}
cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
public class ForwardingSet<E> implements Set<E> {
    private final Set<E> s;
 
    public ForwardingSet(Set<E> s) {
        this.s = s;
    }
 
    public void clear() {
        s.clear();
    }
 
    public boolean contains(Object o) {
        return s.contains(o);
    }
 
    public boolean isEmpty() {
        return s.isEmpty();
    }
 
    public int size() {
        return s.size();
    }
 
    public Iterator<E> iterator() {
        return s.iterator();
    }
 
    public boolean add(E e) {
        return s.add(e);
    }
 
    public boolean remove(Object o) {
        return s.remove(o);
    }
 
    public boolean containsAll(Collection<?> c) {
        return s.containsAll(c);
    }
 
    public boolean addAll(Collection<extends E> c) {
        return s.addAll(c);
    }
 
    public boolean removeAll(Collection<?> c) {
        return s.removeAll(c);
    }
 
    public boolean retainAll(Collection<?> c) {
        return s.retainAll(c);
    }
 
    public Object[] toArray() {
        return s.toArray();
    }
 
    public <T> T[] toArray(T[] a) {
        return s.toArray(a);
    }
 
    @Override
    public boolean equals(Object o) {
        return s.equals(o);
    }
 
    @Override
    public int hashCode() {
        return s.hashCode();
    }
 
    @Override
    public String toString() {
        return s.toString();
    }
}
 
cs

*좀길지만 그냥 다 붙여버렸다...

위의 InstrumentedSet과 같은 클래스를 포장 클래스라고 부릅니다.(Set객체를감고 있기 때문에) 또한 이런 구현 기법은 장식자(decorator)패턴이라고 부름.

 때로는 구성과 전달 기법을 아울러서 막연하게 위임(delegation)이라고 부르기도 하는데 기술적으로 포장객체가 자기 자신을 포장된(wrapped)객체에 전달하지 않으면 위임이라고 부를 수 없다고 함.


Wrapper Class의 장점

어떤 SET구현을 원하는 대로 수정, 이미 있는 생성자도 그대로 사용

1
2
Set<Date> s = new InstrumentedSet<Sate>(new TreeSet<Date>(cmp));
Set<E> s2 = new InstrumentedSet<E>(new HashSet<E>(capacity));
cs

 

심지어 이미 사용중인 객체에 일시적으로 원하는 기능을 넣는 데도 사용할수있다.

1
2
3
Static void walk(Set<Dog> dogs){InstrumentedSet<Dog>
iDogs = new InstrumentedSet<Dog>(dogs);...
//이 메서드 안에서는 dogs대신 iDogs를 사용
cs


갑자기 참고 : <> 이거는
In Java SE 7 and later, you can replace the type arguments required to invoke the constructor of a generic class with an empty set of type arguments (<>) as long as the compiler can determine,
이라고 나온다 유추해준다는 의미이다. 

Box<Integer> integerBox = new Box<>();

// Integer또안써도 된다. 단, 1.7이상에서만


여기까지가 구성의 장점 설명이였습니다. 너무 난잡하다 내가 썼지만..ㅋㅋ

흠...데코레이터를 한번 정리 해야겠다.