반응형

정적 메서드와 정적 필드만을 담은 클래스

1) java.lang,Math, java.util.Arrays과 같이 기본 타입 값이나 배열 관련 메서드들을 모아놓음
2) java.util.Collections처럼 특정 인터페이스를 구현하는 객체를 생성해주는 정적 메서드를 모아놓을 수 있음
3) final 클래스와 관련된 메서드들

추상 클래스로 만드는 것으로는 인스턴스화를 막을 수 없다.

private 생성자를 추가하면 클래스의 인스턴스활르 막을 수 있음

인스턴스를 만들 수 없는 유틸리티 클래스

public class UtilityClass {

    //기본 생성자가 만들어지는 것을 막는다(인스턴스화 방지용)
    private UtilityClass(){
        throw new AssertionError();
    }
}
  • 위 코드는 어떤 환경에서도 클래스가 인스턴스화되는 것을 막아줌
  • 상속을 불가능하게 하는 효과도 존재함
  • 하위 클래스가 상위 클래스의 생성자에 접근할 길이 막힘
728x90
반응형

싱글턴이란 인스턴스를 오직 하나만 생성할 수 있는 클래스를 말함

클래스를 싱글턴으로 만들면 이를 사용하는 클라이언튼를 테스트하기가 어려워 질 수 있음
타입이 인터페이스이면 그 인터페이스를 구현해서 만든 싱글턴이 아니라면 싱글턴 인스턴스를 가자 구현으로 대체할 수 없음

public static final 필드 방식의 싱글턴

public class Elvis{
    public static final Elvis INSTANCE = new Elvis();
    private Elvis() { ... }

    public void leaveTheBuilding() { ... }
}

위와 같은 방식으 싱글턴 생성은 리플렉션 API를 사용할 경우 싱글톤임을 보장하지 못함
이러한 공격(?)을 방어하려면 생성자를 수정하여 두 번째 객체가 생성되려 할 때 예외를 던지게 하면됨

장점

1) 해당 클래스가 싱글텀임이 API에 명백히 드러남, public static 필드가 final이니 절대로 다른 객체를 참조할 수 없음
2) 간결함

정적 팩터리 방식의 싱글턴

public class Elvis {
    private static final Elvis INSTANCE = new Elvis();
    private Elvis() { ... }
    public static Elvis getInstance() {return INSTANCE;}

    public void leaveTheBuilding() { ... }
}

Elvis.getInstance는 리플렉션을 통한 예외에서도 제2의 Elvis 인스턴스를 만들지 않음

장점

1) API를 바꾸지 않고도 싱글턴이 아니게 변경 할 수 있다. -> 스레드별 다른 인스턴스
2) 정적 팩터리를 제네릭 싱글턴 팩터리로 만들 수 있음
3) 원한다면 정적 팩터리의 메서드 참조를 공급자로 사용할 수 있음

싱글톤 클래스를 직렬화하는 경우

  • 모든 인스턴스 필드를 일시적이라고 선언하고, readResolve 메서드를 제공해야함
  • 이렇게 하지 않으면 인스턴스를 역직렬화할 때마다 새로운 인스턴스가 만들어짐
  • 싱글턴임을 보장해주는 readResolve 메서드
  • private Object readResolve(){ return INSTANCE; }

열거 타입 방식의 싱글턴 - 바람직한 방법

public enum Elvis {
    INSTANCE; 

    public void leaveTheBuilding(){ ... }
}
  • 복잡한 직렬화 상황이나 리플렉션 공경에서도 제2의 인스턴스가 생기는 일을 완벽히 막아줌
  • 대부분 상황에서 원소가 하나뿐인 열거 타입이 싱글턴을 만드는 가장 좋은 방법임
728x90
반응형

1) 점층적 생서자 패턴도 쓸 수는 있지만, 매개변수 개수가 많아지면 클라이언트 코드를 작성하거나 읽기 어렵다.

점층적 생성자 패턴 - 확장하기 어렵다!

public 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 NutritionFacts(int servingSize, int servings){
        this(servingSize, servings, 0);
    }

    public NutritionFacts(int servingSize, int servings, inst calories){
        this(servingSize, servings, calories, 0);
    }
}

점층적 생성자 패턴도 쓸 수 있지만, 매개변수 개수가 많아지면 클라이언트 코드를 작성하거나 읽기 어렵다.

자바빈즈 패턴 - 일관성이 깨지고 불변으로 만들 수 없다.

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

    public NutritionFacts(){}

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

}

자바빈즈 패턴에서는 객체 하나를 만들려면 메서드를 여러 개 호출해야 하고, 객체가 완전히 생성되기 전까지는 일관성이 무너진 상태에 놓이게 됨

자바빈즈 패턴에서는 클래스를 불변으로 만들 수 없음(아이템 17_변경 가능성을 최소화하라)

final 등 불변을 보장, 스레드 안정하도록 구현이 필요함

위 두가지를 아우리는게 세번째 대안인 빌터 패턴이다

빌더 패턴 - 점층적 생성자 패턴과 자바빈즈 패턴의 장점만 취함

public 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 sodium = 0; 
        private int carbohydrate = 0;

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

        public Builder calories(int val){
            calories = val; return this;
        }
        public Builder fat(int val){
            fat = val; return this;
        }
        public Builder sodium(int val){
            sodium = val; return this;
        }
        public Builder carbohydrate(int val){
            carbohydrate = val; return this;
        }

        public NutritionFacts build(){
            return new NutritionFacts(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;
    }

}

//클라이언트 후출시
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
        .calories(100).sodium(35).carbohydrate(27).build();

메서드 호출이 흐르듯이 연결된다는 뜻으로 플루언트 API 혹은 메서드 연쇄라 한다.

계층적으로 설계된 클래스와 잘 어울리는 빌더 패턴

public abstract class Pizza{
    public enum Topping{HAM, MUSHROOM, ONION, PEPPER, SAUSAGE}
    final Set<Topping> toppings;

    abstract static class Builder<T extends Builder<T>> {
        EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);
        public T addTopping(Topping topping){
            toppings.add(Objects.requiredNonNull(topping));
            return self();
        }

        abstract Pizza build();

        protected abstract T self();
    }

    Pizza(Builder<?> builder){
        toppings = builder.toppings.clone();
    }
}

재귀적 타입 하전을 이용하는 제네릭 타입
추상 메서드인 self를 더해 하위 클래스에서 형변환하지 않고도 메서드 연쇄를 지원

뉴욕피자

public class NyPizza extends Pizza {
    public enum Size {SMALL, MEDIUM, LARGE}
    private final Size size; 

    public static class Builder extends NyPizza.Builder<Builder>{
        private final Size size; 

        public Builder(Size size){
            this.size = Objects.requiredNonNull(size);
        }

        @Override public NyPizza build() {
            return new NyPizza(this);
        }

        @Override protected Builder self(){return this;}

    }

    private NyPizza(Builder builder){
        super(builder);
        size = builder.size;
    }

}

@Override public NyPizza build() {
return new NyPizza(this);
}
다음 구체 하위 클래스는 하위 클래스 메서드가 상위 클래스의 메서드가 정의한 반환 타입이 아닌, 그 하위 타입을 반환하는 기능을 공병환 타이핑이라 한다.
클라이언트가 형변환에 신경 쓰지 않고도 빌더를 사용할 수 있음

NyPizza pizza = new NyPizza.Builder(SMALL)
        .addTopping(SAUSAGE).addTopping(ONION).build();

생성자나 정적 팩터리가 처리해야 할 매개변수가 많다면 빌더 패턴을 선택하는 게 더 낫다.

실무에서는 롬복에서 제공하는 Builder 어노테이션을 잘 활용하면 될 듯하다.

728x90

+ Recent posts