Effective Java 1 ~ 9

jj·2022년 8월 11일
0

CS

목록 보기
9/9

1. 생성자 대신 정적 팩토리 메소드를 고려하라


정적 팩토리 메소드: 객체 생성의 역할을 하는 클래스 메서드.

일단 여기서 정적(static)이란?

static 키워드로 선언된 필드, 메소드는 객체에 소속된 멤버가 아니라 클래스에 고정된 멤버이다. 따라서 클래스 로더의 클래스 로딩이 끝나면 객체 생성없이 바로 쓸 수 있다. 또한 그림에서 처럼 객체들은 메모리의 Heap 영역에 저장되는 반면 static 멤버들은 static영역에 저장된다. 모든 객체가 공유하여 하나의 멤버를 어디서든 참조할 수 있는 장점이 있지만 GC의 관리 영역 밖에 존재하므로 Static영역의 멤버들은 프로그램 종료시까지 메모리가 할당된 채로 존재한다. 따라서 Static을 남발하면 메모리가 낭비되고 시스템 성능에 악영향을 줄 수 있다.


정적 팩토리 메서드(Static Factory Method)는 왜 사용할까?

간단히만 정리한다. 자세한 내용은 위의 블로그를 참조하자.


장점

  • 이름을 가질 수 있다. 메소드니까.

  • 호출될 때마다 인스턴스를 새로 생성하지 않아도 된다. 메소드에 new 연산이 없고 걍 싱글톤으로 선언된 메소드이면 해당.

  • 반환 타입의 하위 타입 객체를 반환할 수 있다. 메소드니까 원하는 객체 반환가능.

  • 입력 매개변수에 따라 매번 다른 클래스의 객체를 반활할 수 있다.


단점

  • 상속을 하려면 public이나 protected 생성자가 필요한데 정적 팩토리 메소드만 제공하는 class이면 하위 클래스를 만들 수 없다. -> 상속대신에 컴포지션을 사용하여 해결.

  • 정적 팩토리 메소드는 개발자가 찾기 어렵다. -> API문서를 잘 쓰고 메소드 이름을 알려진 규약에 따라 짓는 식으로 완화시켜야 함.


대부분의 상황에 정적 팩토리 메소드가 유리하다고 한다. public 생성자만 생성하는 버릇이 있다면 고쳐라.





2. 생성자에 매개변수가 많다면 빌더를 고려하라


정적 팩토리 메소드와 생성자에게는 똑같은 제약이 있다. 선택적 매개변수가 많을 때 적절하게 대응하기가 어려운 것이다.

만약에 생성자를 필요한만큼 생성하여 쓰는 경우를 생각해보자. 20개의 필드메소드가 있고 생성자를 경우에 따라 생성한다면 class파일의 가독성이 현저히 떨어질 것이다.

다음으로 자바빈즈 패턴이다. 매개변수가 없는 생성자로 객체를 만든 후에 setter를 사용하여 필드를 초기화해주는 방식이다. 코드는 다음과 같다.


public class NutritionFacts {
    private int servingSize = -1;
    private int servings = -1;
    private int calories = 0;
    private int fat = 0;
    private int sodium = 0;
    private int carbohydrate = 0;

    public NutritionFacts() { }

    public void setServingSize(int val) { ... }
    public void setServings(int servings) { ... }
    public void setCalories(int calories) { ... }
    public void setFat(int fat) { ... }
    public void setSodium(int sodium) { ... }
    public void setCarbohydrate(int carbohydrate) { ... }

}

생성자를 여러개 만드는 것보다는 가독성이 좋아졌지만 객체 하나를 만들기 위해서 메소드를 여러개 호출해야하며 객체가 완전히 완성되기 전까지 일관성이 무너지는 문제가 생긴다.

이러한 단점들을 보완하기위해 나온 것이 builder 패턴이다.


class NutritionFacts{
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    public static class Builder{
        // 필수 매개변수
        private final int servingSize;
        private final int servings;

        // 선택 매개변수
        private int calories     = 0;
        private int fat          = 0;
        private int sodium       = 0;
        private int carbohydrate = 0;

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        }

        public Builder calories(int calories){
            this.calories = calories;
            return this;
        }

        public Builder fat(int fat){
            this.fat = fat;
            return this;
        }

        public Builder sodium(int sodium){
            this.sodium = sodium;
            return this;
        }

        public Builder carbohydrate(int carbohydrate){
            this.carbohydrate = carbohydrate;
            return this;
        }

        public NutritionFacts build(){
            return new NutritionFacts(this);
        }
    }
    
    private NutritionFacts(Builder builder){
        servingSize  = builder.servingSize;
        servings     = builder.servings;
        calories     = builder.calories;
        fat          = builder.fat;
        sodium       = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }
}

class 안에 builder class를 따로 만든다. 그리고 필수 매개변수와 선택 매개 변수를 구분한다. 필수 매개변수만으로 biulder 생성자(혹은 정적 팩토리 메소드)를 호출하고 필요에 따라 setter 메소드를 호출하여 선택 매개변수를 초기화한다. 그리고 마지막으로 build 메소드를 호출하여 builder객체를 통해서 root class 객체를 생성하여 return한다. 이렇게하면 가독성도 가져가면서 일관성이 깨지는 문제도 해결할 수 있다.



NutritionFacts cocaCola = new NutritionFacts.Builder(240,8).calories(100).sodium(35).build();

식으로 호출하여 사용할 수 있다.




enum 이 뭔가?

-> Java: enum의 뿌리를 찾아서...

profile
끊임없이 공부하는 개발자

0개의 댓글