Item4

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

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

  • 객체 지향적 방식과는 거리가 멀지만, 나름의 쓰임새가 존재합니다.

  • ex) java.lang.Math, java.util.Arrays, java.util.Collections

  • 특정 메서드들을 모아놓은 유틸성 클래스들이 그러합니다.

 

public class Item4Main {

  public static void main(String[] args) {
      Math.abs(5);
      Arrays.sort(new int[]{5, 3});
  }
}

 

 

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

  • public 생성자가 만들어지며, 사용자가 의도한 것인지 자동생성된 것인지 알 수 없습니다.

  • 실제로 공개된 API 에서도 의도치 않게 인스턴스화할 수 있는 클래스가 종종 목격됩니다.

  • 추상 클래스로 만드는 것도 하위 클래스를 만들면 인스턴스화를 할 수 있습니다.

  • private으로 생성자를 선언하면 인스턴스화가 불가능합니다. 마찬가지로 상속이 불가능하여 원치 않는 인스턴스화를 막을 수 있습니다.

 

public class Item4 {

  private Item4() {
  }

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

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

 

참고

블로그 이미지

사용자 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

댓글을 달아 주세요