04. 인스턴스화를 막으려거든 private 생성자를 사용하라

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

  • 객체 지향적 방식과는 거리가 멀지만, 나름의 쓰임새가 존재합니다.
    • ex) java.lang.Math, java.util.Arrays, java.util.Collections
  • 특정 메서드들을 모아놓은 유틸성 클래스들이 그러합니다.
public class Item4App { 
    public static void main(String[] args) {
      Math.abs(5);
      Arrays.sort(new int[]{5, 3});
    }
}

하지만 생성자를 명시하지 않으면 컴파일러가 자동으로 기본 생성자를 생성

  • public 생성자가 만들어지며, 사용자가 의도한 것인지 자동생성된 것인지 알 수 없습니다.
  • 실제로 공개된 API 에서도 의도치 않게 인스턴스화할 수 있는 클래스가 종종 목격됩니다.
  • 추상 클래스로 만들어도 상속을 하게 되면 소용이 없습니다.

생성자 인스턴스화를 막는 방법

private으로 생성자를 선언하면 인스턴스화가 불가능합니다
또한, private을 사용하 상속이 불가능하여 원치 않는 인스턴스화를 막을 수 있습니다.
public class Item4 {

  private Item4() {
     throw new AssertionError(); 
  }

  public static void play() {
      System.out.println(“just play”);
  }
}

public class Item4Main {  
    public static void main(String[] args) {  
        // 인스턴스화 불가능  
        //Item4 item4 = new Item4();  
        Item4.play();  
    }  
}

주의하면 좋은것

클래스 바깥에서는 private 생성자에 접근할  없으니 Error를  던질 필요는 없습니다.
다만 에러 설정을 해둔다면 의도를 명확히 드러낼  있습니다.

 

참조

effective java 3rd

블로그 이미지

yhmane

댓글을 달아 주세요

Item2

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

  • 점층적 생성자 패턴도 쓸 수 있지만, 매개변수 개수가 많아지면 클라이언트 코드를 작성하거나 읽기 어렵습니다.
public class Reservation {
    private String name;
    private String phone;
    private String reservingTime;
    private String startTime;
    private String endTime;
    
    public Reservation(String name, String phone) {
        this.name = name;
        this.phone = phone;
    }

    public Reservation(String name, String phone, String reservingTime) {
        this.name = name;
        this.phone = phone;
        this.reservingTime = reservingTime;
    }

    public Reservation(String name, String phone, String reservingTime, String startTime) {
        this.name = name;
        this.phone = phone;
        this.reservingTime = reservingTime;
        this.startTime = startTime;
    }

    public Reservation(String name, String phone, String reservingTime, String startTime, String endTime) {
        this.name = name;
        this.phone = phone;
        this.reservingTime = reservingTime;
        this.startTime = startTime;
        this.endTime = endTime;
    }
}
  • 자바빈즈 패턴(setter)를 이용해 이러한 단점을 보완할 수 있지만, 객체의 일관성이 무너진 상태에 놓이게 됩니다.
public static void main() {
    Reservation reservation = new Reservation();
    reservation.setName("황윤호");
    reservation.setPhone("010-1234-5678");
    reservation.setReservingTime("2020-11-01");
    reservation.setStartTime("2020-11-18");
    reservation.setEndTime("2020-11-20");
}
  • 점층적 생성자 패턴의 안전성과 자바빈즈 패턴의 가독성을 겸비한 빌더 패턴이 있습니다.
public class Reservation {
    private String name;
    private String phone;
    private String reservingTime;
    private String startTime;
    private String endTime;
    
    public static class Builder {
        // 필수 매개변수
        private String name;
        private String phone;
        
        // 선택 매개변수
        private String reservingTime = "2020-01-01";
        private String startTime     = "2020-01-02";
        private String endTime       = "2020-01-03";
        
        public Builder(String name, String phone) {
            this.name = name;
            this.phone = phone;
        }

        public Builder reservingTime(String val) {
            reservingTime = val;
            return this;
        }

        public Builder startTime(String val) {
            startTime = val;
            return this;
        }

        public Builder endTime(String val) {
            endTime = val;
            return this;
        }
        
        public Reservation build() {
            return new Reservation(this);
        }
    }
    
    private Reservation(Builder builder) {
        name = builder.name;
        phone = builder.phone;
        reservingTime = builder.reservingTime;
        startTime = builder.startTime;
        endTime = builder.endTime;
    }
}

public class Item2 {
    public static void main() {
        Reservation reservation = new Reservation
                .Builder("황윤호","010-1234-5678")
                .reservingTime("2020-11-13")
                .startTime("2020-11-20")
                .endTime("2020-11-22")
                .build();
    }
}

정리

  • 코드가 읽고 쓰기 쉬워집니다.
  • 객체의 일관성을 부여할 수 있습니다.
  • 빌더 패턴은 빌더를 만들어 주어야 하기 때문에 매개변수가 많지 않다면 필수는 아닙니다.
  • 점층적 생성자 패턴이나 자바빈즈 패턴의 장점을 모아두었고, 생성자는 시간이 지날수록 매개변수가 늘어나니 빌더패턴을 적용하는 것이 좋습니다.



참고

- effective java 3/e [조슈아 블로크]


블로그 이미지

yhmane

댓글을 달아 주세요

생성자 대신 정적 팩터리 메서드를 고려하라

클래스의 인스터스를 얻는 전통적인 수단은 public 생성자를 이용하는 것입니다.
하지만, 또 알아두어야 할 기법이 있으니 정적 팩토리 메서드(static factory method)를 이용해서도 제공할 수 있습니다.

public static Boolean valueOf(boolean b) {
    return b ? Boolean.TRUE : Boolean.FALSE;
}

정적 팩토리 메서드가 생성자보다 좋은 장점

  • 이름을 가질 수 있습니다.
class Student() {
    private String name;
    private int studentNum;

    // 어떤 역할인지 명시적 표현.
    public static Student studentWithName(String name) {
         Student student = new Student();
         student.name = name;
         return student;
    }
}

public class Item1 {
    Student student2 = Student.studentWithName("홍길동");
}
  • 호출될 때마다 인스턴스를 새로 생성하지 않아도 됩니다.
class Activity {

    public static final Activity DISCOUNT_THREE_ACTIVITY = new Activity(10000.0, 3);

    private double price;
    private int activityCount;

    public static Activity setPriceWithActivity(int activityCount) {
        // 생성되어 있는 객체를 할당.
        if (activityCount == 3) {
            return DISCOUNT_THREE_ACTIVITY;
        }

        Activity activity = new Activity();
        activity.price = 20000.0;
        activity.activityCount = activityCount;
        return activity;
    }
}

public class Item1 {
    Activity activity = Activity.setPriceWithActivity(3);
}
  • 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있습니다.
    생성자는 리턴값이 없지만 정적 팩토리 메소드는 반환값을 유연하게 사용할 수 있습니다.
class Student {
    private String name;
    private int studentNum;
    
    public static MiddleSchool middleSchoolStudnent() {
        return new MiddleSchool();
    }
}
class MiddleSchool extends Student {}
public class Item1 {
    MiddleSchool middleSchool = Student.middleSchoolStudnent();
}
  • 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있습니다.
class Fruit {
    public static Fruit getFruit(String name) {
        if ("Apple".equals(name)) {
           return new Apple();
        } else if ("Banana".equals(name)) {
            return new Banana();
        } else {
            return new Strawberry();
        }
    }
}
class Apple extends Fruit { }
class Banana extends Fruit { }
class Strawberry extends Fruit { }
public class Item1 {
    // 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있습니다.
    Fruit fruit = Fruit.getFruit("Banana");
    System.out.println(fruit.getClass().getName());
}

정적 팩토리 메서드의 단점

  • 상속을 하려면 public, protected의 생성자가 필요합니다.
    정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없습니다.
  • API를 따로 제공하지 않기에 프로그래머가 찾기 어렵습니다.



참고

- effective java 3/e [조슈아 블로크]


블로그 이미지

yhmane

댓글을 달아 주세요