오늘은 Enum 자료형에 대해 포스팅 한다.

자바 1.5가 추가 됨으로써, 새로운 참조자료형이 추가 됬다.

그중 하나는, 열거자료형(Enum Type)이라 불리는

새로운 종류의 클래스이다.

보통 자바에서 상수의 열거는 아래와 같이 작성한다.


public static final int AMERICANO = 0;

public static final int CAFE_MOCHA = 1;

public static final int CAFE_LATTE = 2;


public static final int APPLE_JUICE = 0;

public static final int ORANGE_JUICE = 1;

public static final int GRAPE_JUICE = 2;


책에서 말하길, 이 기법은 int enum 기법이라 한다.

하지만, 이 기법은 치명적인 단점이 있다. AMERICANO 와 APPLE_JUICE는 우리가 보고있는 이름은 틀리지만,

값은 0으로 같은 상황이다. 즉, 아래와 같은 메서드가 있다고 가정할 시,


public static void makeCoffee(int coffeeType){

  //..........

}


해당 메서드는 APPLE_JUICE를 넣더라도 컴파일 오류 없이, 아메리카노를 넣은 것과 똑같이 잘 실행이 된다. 

이유는 위에서도 적었다 시피, 같은 0값이기 때문이다.

또한, 우리는 값 0으로 구분하는 것이 아닌 이름, 즉 변수명으로 구분한다. 변수명을 보고

이것이 아메리카노, 오렌지 쥬스등을 파악하기 때문이다. 하지만, int enum기법은 문자열로 변환하기가 녹록치 않다.

원하는 문자열로 변환해주는 메서드 if ~ else 조합으로 만들 수는 있겠지만, 해서는 안될 짓이다.

왜냐하면 아이템이 추가 될때마다, 단적으로 else를 추가해 줘야하고,  

그 메서드로 인해 영향을 받는 모든 코드들을 수정해야 하기때문에 유지보수에 큰 난항을 겪기 때문이다.


그렇다면, 문자열로 변환하기 쉽게 toString() 메서드를 제공하는, String 클래스를 이용하여 아래와 같이 열거형을 만들면 어떨까? 

public static final String AMERICANO = "0";

위와 같이 열거형을 구성하면, AMERICANO.toString()을 실행하면 원하는 문자열인 "AMERICANO"를 얻을 수 있지만,

String enum 패턴이라 불리는 이 패턴은 해서는 안 될 패턴이다. 

문자열 비교는 성능을 떨어 트릴 수 있고, 클라이언트 입장에서

인자를 넣기를 AMERICANO변수를 이용하지 않고, "0"으로 메서드를 실행 할 수 있는 가능성이 있기 때문에, 좋지 않다.

이렇게 됬었을 시, 설계자가 AMERICANO를 바꿨을시, 클라이언트의 코드는 원하느 결과가 나오지 않을 뿐더러,

컴파일 오류가 나지 않는 경우는 버그를 찾기 힘들기 때문이다.


이러한 문제점을 해결하기 위한 대안이 바로 열거형 자료형인 Enum 이다.

위에서 예로 적은 상수들을 아래와 같이 Enum 열거형으로 바꿔보았다.

public enum Coffee { AMERICANO, CAFE_MOCHA, CAFE_LATTE }

public enum JUICE { APPLE_JUICE, ORANGE_JUICE, GRAPE_JUICE }

위 enum클래스는 클라이언트가 접근 할 수 있는 생성자가 없다. 또한 final 이 내부적으로 선언되 있으므로,

객체생성, 상속이 되지 않는다. 


enum의 장점 중 하나는 컴파일 시점 시 형 안정성을 제공해준다. 

Coffee자료형은 반드시 Coffee안에 존재하는 열거 상수를 받아야 하지,

JUICE의 열거 상수가 들어가면 컴파일 오류가 발생한다. 

위에서 말한 int enum의 문제점인 makeCoffee안에 APPLE_JUICE를 넣더라도 실행되는걸 막을 수 있단 얘기이다.

또한 enum은 같은 값을 가진 상수들을 평화롭게 공존 시킬 수 있는데,

enum의 이름으로 각 상수들을 구분 할 수 있기 때문이다.

enum은 각 상수마다 toString 메서드를 사용 할 수 있어, 문자열로 쉽게 변환이 가능하다.

enum역시 클래스므로, 변수와 메서드를 추가 할 수 있다. 

위에서 작성한 열거 상수들은 변수가 아닌 상수임에 유의 하자.

변수와 메서드를 추가 하는 이유는 간단하다. 상수를 좀더 멋지게 사용하고 싶은 생각이 있는 것이다.


해당 내용은 다음 포스팅에서 계속한다.


포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee

오늘 포스팅 할 내용은 Builder 패턴이다.

Class 를 설계하다 보면,

필수로 받아야 할 인자들이 있고,

선택적으로 받아야 할 인자들이 있는 경우가 있다.

필수적으로 받아야 할 인자를 받기 위해

생성자의 매개변수를 넣는다.

또한 선택적 인자를 받기 위해 추가적인

생성자를 만든다.


public class PersonInfo{

  private String name;         //필수적으로 받야할 정보

  private int age;                //선택적으로 받아도 되는 정보

  private int phonNumber;   //선택적으로 받아도 되는 정보

  public PersonInfo(String name){

     this.name = name;

  }

  public PersonInfo(String name, int age){

    this.name = name;

    this.age = age;

  }

  public PersonInfo(String name, int age, int phonNumber){

    this.name = name;

    this.age = age;

    this.phonNumber = phonNumber;

  }

}

이러한 상황을 Effective Java 2/E 에선 점층적 생성자 패턴이라 말한다.

하지만, 이러한 패턴은 잘 동작하지만, 몇가지 단점들이 존재한다.

첫번째로, 인자들이 많아 질 수록 생성자의 숫자 역시 많아진다.

두번째로, 매개변수의 이름으로 원하는 정보를 설명하지만,

같은 자료형은 실수로 바꿔 넣는 실수가 생길 수 있다.

예를 들어, 위의 예시에서 age와 phonNumber를 서로 바꿔 넣는 경우이다.

세번째로, 읽기 어려운 코드가 된다.

PersonInfo personInfo = new PersonInfo("Mommoo", 12, 119);

위의 코드는 매개변수의 정보를 설명할 수 없으므로,

personInfo 객체가 어떤 인자들이 들어갔는지 알기 위해선

PersonInfo 클래스의 매개변수 이름이나, 주석을 참고해야 한다.


이러한 단점들을 보완하기 위해, 자바 빈즈(beans) 패턴이 대안점으로 나온다.

해당 패턴은 웹프로그래밍을 할 때, 자주 쓰는 패턴이다.

public class PersonInfo{

  private String name;         //필수적으로 받야할 정보

  private int age;                //선택적으로 받아도 되는 정보

  private int phonNumber;   //선택적으로 받아도 되는 정보

  public PersonInfo(){

  }

  public void setName(String name){

    this.name = name;

  }

  public void setAge(int age){

    this.age = age;

  }

  public void setPhonNumber(int phonNumber){

    this.phonNumber = phonNumber;

  }

}

생성자의 단점으로 꼽혔던 가독성이 해당 패턴을 사용하는 경우, 어느정도 해결이 된다.

PersonInfo personInfo = new PersonInfo( );

personInfo.setName("Mommoo");         // 이름을 넣는 메서드

personInfo.setAge(12);                       // 나이를 넣는 메서드

personInfo.setPhonNumber(119);          // 전화번호를 넣는 메서드

자바빈즈 패턴은 코드량이 늘어나는 단점이 존재한다. 

하지만 그것보다 더욱 문제가 되는 것은 객체 일관성이 깨진다.

객체 일관성이 깨진다는 것이란, 한번 객체를 생성할때, 그 객체가 변할 여지가 존재한다는 뜻이다.

즉, set메서드는 언제 어디서든 사용 할 수 있다는 장점이 있지만, 객체의 불변성이 깨진다.

스레드 작업에 큰 단점이 될 수 있을 뿐더러, 컴파일러 오류는 아니지만, 우리가 원하지 않는

결과물이 만들어 질 수 있는것이다.


점층적 생성자패턴과 자바빈즈 패턴의 장점을 섞은것이 빌더패턴이다.

정보들은 자바 빈즈 패턴처럼 받되, 데이터 일관성을 위해 정보들을 다 받은후에 객체생성을 해준다.


public class PersonInfo{

  private String name;         //필수적으로 받야할 정보

  private int age;                //선택적으로 받아도 되는 정보

  private int phonNumber;   //선택적으로 받아도 되는 정보

  private PersonInfo(){

  }

  public static class Builder{

    private String name;

    private int age;

    private int phonNumber;

   

   public Builder(String name){

      this.name = name;

    }

   public Builder setAge(int age){

 this.age = age;

 return this;

   }

   public Builder setPhonNumber(int phonNumber){

 this.phonNumber = phonNumber;

 return this;

   }

   public PersonInfo build(){

   PersonInfo personInfo = new PersonInfo( );

   personInfo.name = name;

   personInfo.age = age;

   personInfo.phonNumber = phonNumber;

   return personInfo;

 }

}

이러한 빌더 패턴은 아래와 같이 사용한다.

PersonInfo personInfo = new Builder("Mommoo").setAge(12).setPhonNumber(119).build( );

setter에 리턴자료형을 Builder 객체를 지정함으로써, 메서드 체이닝 기법을 적용했고,

정보를 다 넣은 경우 build( ) 메서드로 객체생성을 한다.

build( ) 메서드를 쓴 이후 에는 PersonInfo 클래스의 멤버변수를 변경 할 수 있는 방법은

리플렉션 기법빼곤 존재하지 않는다. 따라서, 데이터 일관성, 객체불변성 등을 만족시킨다.

또한 코드 가독성 역시 올라간다.


하지만 이러한 빌드 패턴의 단점은 많은 코드량이 필요할 뿐더러,

Builder라는 객체를 하나더 만드는 것이므로, 때에 따라선 성능이 낮아질 수 있다.


Class 설계할때, 받아야할 인자들이 많거나, 선택적 인자들이 많거나 또한, 추가될 인자들이 많은 경우

에 Builder 패턴을 고려한다면, 좀 더 나은 설계를 할 수 있을 것이다.


포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee
  1. 지나가는 행인1 2017.12.05 16:05

    public static Builder{
    }
    class 키워드가 바진거 같네요 :)

  2. JAMINS 2020.01.23 01:22 신고

    setAge, setPhoneNumber에 return this가 빠졌네요!

오늘 포스팅 할 내용은 정적 팩토리 메서드에 관한 내용이다.


내용 의 상당수는 (Effective Javaa 2/E - 2판)의 도움을 받았고,


개인적인 의견을 덧 붙여 해당 내용을 풀어 설명할 것 이다.


인스턴스를 생성하기 위해서는 Class 원칙에 따라, 생성자를 호출해야 한다.


Person p = new Person( );


Class의 이름은 캡슐화를 위해 Class의 내용을 아우를 수 있는 이름을 짓기 마련이다.


따라서, 생성자의 이름만을 봐서는 감이 안올때가 있다.


또한, 여러가지 매개변수를 받는 생성자를 만들 때도, 생성자의 매개변수를


설명할 방법이 딱히 없다. 하지만, 정적 팩토리 메서드의 활용 여부에 따라 


코드의 가독성을 높일 수 있다. (여기서 정적은 static을 가리킨다.)


그로인해 아래와 같은 장점들이 존재한다.


정적 팩토리 메서드는 해당 클래스 인스턴스에 이름을 지어줄 수 있다.


public class Person{

  private String name;

  private Person( ){

  }

  

  public static Person newInstance(){

    return new Person( );

  }

  public static Person newInstance(String name){

    this.name = name;

    return new Person( );

  }


  public static void main(String[] args){

    Person p  = Person.newInstance(); // 평범한 인스턴스 생성

    Person p2 = Person.newInstanceByName("Mommoo"); // name속성을 가지는 인스턴스 생성 

  }

}


인스턴스의 갯수를 제어 할 수 있다. 이를 응용한 것이 싱글톤 패턴 이다.


public class Person{

  private final Person p = new Person( );


  private Person( ){

  }


  public static Person getInstance(){

    return p;

  }


  public static void main(String[] args){

    Person p = Person.getInstance( ); // 이미 존재하고 있는 객체 리턴

  }

}


생성자엔 반환값이 없지만, 필요시에 적절한 반환값을 만들어 줄 수 있다.


public class Person{

  private Person( ){ 

  }


  public static class Doctor{

    private Doctor(){

    }

  }


  public static Doctor getDoctor( ){

    return new Doctor( );

  }


  public static void main(String[] args){

    Doctor doctor = Person.getDoctor( );

  }

}


위의 예시는 반환값을 특정 Class로 했지만, 설계에 따라 어떠한 반환값이든 가능하다.


생성자를 감싸는 구조이므로, 유지보수와 성능 개선에 좋다.


위의 예시중 getDoctor 메서드가 new Doctor( )란 새로운 인스턴스를 리턴하지만,


개발자가 싱글톤으로 리턴하는 코드로 변경한다고 가정해보자.


public class Client{


  public static void main(String[] args){

    /** 클라이언트는 변경사항이 없다. 

      *  getDoctor( ) 메서드가 내부적으로 어떻게 변경되는지, 어떻게 쓰이는지 알 필요조차 없다.

      *  클라이언트는 단순히 Doctor라는 객체를 쓰는데만 집중한다. 

      */

    Doctor doctor = Person.getDoctor( );  

  }

}


public class Person{

  private Person( ){ 

  }


  public static class Doctor{

    

    private final Doctor d = new Doctor( );


    private Doctor(){

    }

  }


  public static Doctor getDoctor( ){

    return Doctor.d;   //new Doctor( ) 에서 Doctor.d 로 변경되었다.

  }


  public static void main(String[] args){

    Doctor doctor = Person.getDoctor( );

  }

}


이러한 장점들이 있는 반면, 단점들은 무엇이 있을까? 


먼저, 정적 팩토리 메서드의 사용을 권하기 위해서 private 제어자를 사용해 생성자를 막는다.


그로인해, 해당 클래스를 상속 할 수 없다. 왜냐하면 상속받을 생성자가 없기 때문이다.


또한, 다른 정적 팩토리 메서드는 다른 메서드와 크게 구분이 가지 않는다. 


만약 생성자를 이용한다고 가정해보자.


클래스를 설계 할때는 클래스 이름과 동일하고 반환값 자리가 아예 존재 하지 않는


메서드라는 특정 표식이 있고, 클래스를 쓸때도, new 생성자( ); 처럼


new 연산자를 통해 생성하므로, 생성자의 느낌을 물씬 주지만


정적 팩토리 메서드는 다른 정적 메서들과 차이점이 존재하지 않는다.


Class를 설계한 개발자 조차도 주석을 달아 놓지 않으면, 후에 다시 자신이 설계한 클래스를


봤을 때, 잊어먹 을 수 있다. 


결국 정적 팩토리 메서드를 활용한 클래스를 사용할때는, API나 사용법을 들어야한다.


(물론 생성자를 사용할 때도 어느정도 API나 사용법등을 참고해야할 때가 있다.)




'Java [Effective Java 2_E]' 카테고리의 다른 글

JAVA - 열거자료형 Enum 사용하기(1)  (0) 2016.11.11
Java - Builder 패턴 적용하기.  (3) 2016.10.08

포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee
  1. Lucas 2017.01.06 13:41

    정적 팩토리 메서드는 해당 인스턴스에 이름을 지어 줄 수 있다. 라는 예시에서 2번째 메서드 newInstanceByName(String name)로 이름을 수정해야 해야 하지 않나요???

    • Mommoo 2017.01.30 14:23 신고

      맞습니다. 오타네요 찾아주셔서 감사합니다^^

+ Recent posts