본문 바로가기
Effective JAVA 2판

Effective JAVA Item15 변경 기능성을 최소화 하라 - 실습 및 동료클래스

by BroBroBro 2015. 5. 17.

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

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

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

CHAPTER 4 클래스와 인터페이스

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

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

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

--page01

이번시간에는 과연 15장에서 설명한 Companion Class가 무엇인지 알아보도록 하자.

배운것을 써먹을때가 왔다. 우리 한번 클래스를 만들어보자. 15장의 변경가능성을 최소화하여서.


이런걸 만들어보려고 한다. 본사 커피가 있고 이를 MainCoffee라고 해보자. 그리고 ChainCoffee가 있다. 

본사에서 Basic(기본)커피를 만들어준다면 이를 ChainCoffee에서 변경해서 이용할것이다. 

본사커피는 변경가능성을최소화실킬것이다. 즉 Immutable로 만들것이다. 

우선적으로 생각나는 데로 그려보면 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
class MainCoffee {
  final private String basicCoffee;
  final private int basicShot;
  
  private MainCoffee(String basicCoffee, int basicShot){
    this.basicCoffee = basicCoffee;
    this.basicShot = basicShot;
  }
  
  public static MainCoffee mackMainCoffee(){
    return new MainCoffee("9floorCoffee",1);
  }
}
cs

1번 객체 상태를 변경하는 메서드(수정자 mutator메서드)를 제공하지 않는다.를 적용해봤다...
사실 적용한게 아니라 안만든거다 그래도 좀 미안하니까 read메소드정도는 만들어주자.

1
2
3
4
5
6
  public String kindOfCoffee(){
    return this.basicCoffee;
  }
  public int countOfShot(){
    return this.basicShot;
  }
cs

이정도가 될듯하다.

그럼 다음 은 2번 계승할 수 없도록 한다. 이다. 아래처럼 바꿔주도록 하자.

1
final class MainCoffee {
cs

3번 모든 필드를 final로 선언한다.
4번 모든 필드를 private로 선언한다. 이다. 그냥 붙여주면 되죠? 쉽죠? 이미 작성시에 붙여버렸다. ^^;;

5번 변경 가능 컴포넌트에 대한 독점적 접근권을 보장한다. 을 적용해볼까? 아..이게 뭐였더라?
말이 많지만 결론은 클래스 내부에있는 변경가능객체에 대한 참조를 클라이언트가 획득하지 못하게하는것이다. 본예제에서는 적절하지 못하지만 그냥 방어적 복사본 기법정도만 적용하는것으로 만족 하면 아래와 같은 그림이 나오게된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
final class MainCoffee {
  final private String basicCoffee;
  final private int basicShot;
  
  private MainCoffee(String basicCoffee, int basicShot){
    this.basicCoffee = basicCoffee;
    this.basicShot = basicShot;
  }
  public String kindOfCoffee(){
    return this.basicCoffee;
  }
  public int countOfShot(){
    return this.basicShot;
  }
  public static MainCoffee makeMainCoffee(){
    return new MainCoffee("9floorCoffee",1);
  }
  public MainCoffee plusOneShot(){
    return new MainCoffee(this.basicCoffee,this.basicShot+1);
  }
}
cs

규칙 5를 적용한 plusOneShot 메소드이다. basicShot의 값을 변경만하는것이 아닌 새로운객체를 생성해서 return해주고있다. 이 MainCoffee를 사용하는 사람은 나를 소유할수가 없다. ㅋㅋ
이게 바로 Immutable Class다.


그런데...이걸 본사(Main)에서 만들어서 지점(Chain)에서 사용하게 할것이다.
그런데 본사에서 생성한것은 변경이 불가능하다. 지점에서는 이를 수정막막해서 자신의 지점만의 특화된것을 하고싶었다. 우리는 이제 동료클래스를 만들어볼것이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ChainCoffee {
  final private String basicCoffee;
  private int basicShot;
  
  public ChainCoffee(String basicCoffee, int basicShot){
    this.basicCoffee = basicCoffee;
    this.basicShot = basicShot;
  }
  
  public ChainCoffee plusOneShot(){
    this.basicShot +=1
    return this;
  }
  
  //support Immutable class
  public MainCoffee makeMainCoffee(){
    return new MainCoffee(this.basicCoffee,this.basicShot);
  }
}
cs

기본적으로 비슷하지만 makeMainCoffee가 보인다? 동료클래스를 만들었다면 mainClass에 아래의 메소드를 추가해보자.


1
2
3
4
5
6
7
8
  public ChainCoffee makeChainCoffee() {
    return new ChainCoffee(this.basicCoffee,this.basicShot);
  }
  public static MainCoffee modifiableMainCoffee(MainCoffee mc){
    ChainCoffee cc = mc.makeChainCoffee();
    cc.plusOneShot();
    return cc.makeMainCoffee();
  }
cs


Test를 만들어서 실행해보면 아래와 같다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class CompanionClassTest {
  public static void main(String args[]){
    MainCoffee mc1 = MainCoffee.makeMainCoffee(); 
    MainCoffee mc2 = mc1.plusOneShot();
    System.out.println(mc1);
    System.out.println(mc2);
    
    MainCoffee mc3 = MainCoffee.modifiablePlusOneShot(mc1);
    System.out.println(mc3);
    /**
     *  [ 9floorCoffee, 1 ]
     *  [ 9floorCoffee, 2 ]
     *  [ 9floorCoffee, 2 ]
     */
  }
}
cs

허무한가? 음....위의코드를 자세히볼부분은 mc2완 mc3다. mc2는 샷추가시에 새로운 객체를 생성해서 리턴한다.
mc3는 샷추가시에 새로운객체를 리턴하지 않는다.(물론 내부에서 mutable객체를 생성하는 비용은 들고있다.)

다시 말해서 shot를 10번추가한다면 Main 객체는 내부적으로 new를 10번한다는것이다.
Chain객체는 내부적으로 new를 2번과 수정자 메소드 10번의 호출이면 된다는것이다.

끝 ^^;;~~~ 이아니고...
            String 클래스    , StringBuilder 클래스는
          = Main클래스와     , Chain 클래스이다.
 다시 말해서 Immutable클래스와, Mutable클래스이다. ^^;

헐 지금 생각해보니...String 클래스 이용해서 문자 한개정도 더할거면 궂이 Builder안사용해도 되는구나^^;
참고: StringBuilder 클래스는 Thread에서 안전하지 않다... 이유를 알겠나? ㅋ 위에서 봤듯이 수정이 가능하니까^^ 대신 StringBuffer클래스는 Thread에서 안전하다^^

동료들한테 설명했는데 의문점만 던져주고 말았다 ㅠㅠ
좀 이해하기 쉬운소스로 다시 가져왔다. 사진이다 ..이거 보면 이해 갈듯~!