Effective Java Ch2. 객체의 생성과 소멸 - Item 1

Donghyeok Jang·2021년 8월 18일
0

Effective Java

목록 보기
1/4

Item 1. 생성자 대신에 정적 팩토리 메소드를 고려해 볼 것.

인스턴스를 생성하기 위한 전통적인 방식은 생성자(constructor) 말고 다른 다른 방법이 있다. public static 팩토리 메소드(pulbic static factory method)를 사용해서 클래스 내에서 인스턴스를 반환하는 방법이다.


장점 1. 이름을 가질 수 있다.

만약 생성자가 제공하는 파라미터들이 반환되는 인스턴스에 대해 설명이 충분하지 않을 때, 잘 지어진 이름으로 static 팩토리 메소드를 사용해 더 쉽게 사용하고 가독성이 좋은 코드를 만들 수 있다.
예시로 BigInteger(int, int, Random)이 static 팩토리 메소드를 사용하고있다.

추가로 생성자는 시그니처에 제약이 있다. 똑같은 자료형의 파라미터로 받는 생성자를 중복되도록 만들 수 없다. 이 경우에도 static 팩토리 메소드를 사용한다.

예제
public class Foo {
  
	String name;
	String address;
	
	public Foo() {}
	public Foo(String name) {
		this.name = name;
 	}
	public Foo(String address) {
		this.address = address;
 	}
    
	// 불가능 
	// public Foo(String address) {
	// 	this.address = address;
 	// }
    
	// example: public static factory method
	public static Foo writeName(String name) {
		return new Foo(name);
	}
    
	public static Foo writeName(String name) {
		return new Foo(name);
	}
}

장점 2. 반드시 새로운 객체를 만들 필요가 없다.

불변(Immutable) 클래스나 매번 새로운 객체를 만들 필요가 없는 경우 미리 만들어둔 인스턴스 또는 캐시해둔 인스턴스를 반환해줄 수 있다. Boolean.valueOf(boolean) 메소드가 그 경우에 해당된다. (Flyweight Pattern에 기반함 - 어떤 클래스의 인스턴스 한 개만 가지고 여러 개의 "가상 인스턴스"를 제공하고 싶을 때 사용하는 패턴)

예제
public class Foo {
	private static final IMFOO = new Foo();

 	public static Foo getFoo() {
 		return IMFOO;
 	}
	
}

장점 3. 리턴 타입의 하위 타입 인스턴스를 만들 수 있다.

클래스에서 만들어줄 반환 객체를 선택하는데 매우 뛰어난 유연함을 준다. 리턴 타입의 하위 타입의 인스턴스를 만들어줄 수 있어서 리턴 타입은 인터페이스로 지정하고 그 인터페이스의 구현체는 API로 노출 시키지 않지만 그 구현체의 인스턴스를 만들어 줄 수 있다.이 기술은 인터페이스 기반 프레임워크에 적합하다. java.util.Collection이 그 예에 해당한다.

java.util.Collection은 45개에 달하는 인터페이스의 구현체 인스턴스를 제공하지만 그 구현체들은 모두 non-public이다. 즉 인터페이스 뒤에 숨겨져 있고 그럼으로서 public으로 제공해야 할 API를 줄였을 뿐만 아니라 개념적인 무게(conceptual weight)까지 줄일 수 있었다.

여기서 개념적 무게란, 프로그래머가 어떤 인터페이스가 제공하는 API를 사용할 때 알아야할 개념의 개수와 난이도를 말한다.

개발자는 반환된 객체가 그 인터페이스의 API에 의해 정확하게 구체화 되었다는 것을 알고 있으므로, 추가적인 구현 docs를 읽지 않아도 된다.
게다가, static 팩토리 메소드를 사용하려면 사용자가 인터페이스에서 반환 객체를 참조해야한다. 이것은 클래스 구현을 참조하는 것보다 실용적이다.

예제
public class Foo {
	// 인터페이스 선에서 해결 가능
 	public static Foo getGoo() {
 		return new Goo();
 	}
}
public class Goo extends Foo {
	// 굳이 알아볼 필요 없음
	public Goo() {
  		// ... 
  	}
}

장점 4. 리턴하는 객체의 클래스가 입력 매개변수에 따라 매변 다를 수 있다.

장점 3과 같은 이유로 객체의 타입은 다를 수 있다. EnumSet클래스는 생성자 없이 public static 메소드, allof(), of()등을 제공한다. 그 안에서 리턴하는 객체의 타입은 enum 타입의 개수에 따라 RegularEnumSet 또는 JumboEnumSet으로 달라진다.

그런 객체 타입은 노출하지 않고 숨겨져 있기 때문에 추후 JDK의 변화에 따라 새로운 타입을 만들거나 기존 타입을 없애도 문제가 되지 않는다.

장점 5. 리턴하는 객체의 클래스가 public static 팩토리 메소드를 작성할 시점에 반드시 존재하지 않아도 된다.

static 팩토리 메소드는 서비스 프로바이더 프레임워크의 근본이다. 예시로 JDBC가 있다.

서비스 프로바이더 프레임워크는 서비스의 구현체를 대표하는 서비스 인터페이스와 구현체를 등록하는데 사용하는 프로바이더 등록 API 그리고 클라이언트가 해당 서비스의 인스턴스를 가져갈 때 사용하는 서비스 엑세스 API가 필수로 필요하다. 추가로, 서비스 인터페이스의 인스턴스를 제공하는 서비스 프로바이더 인터페이스(SPI)를 만들 수 있는데, 그게 없는 경우 리플렉션을 사용해 구현체를 만들어 준다.

JDBC의 경우 DriverManager.registerDriver()가 프로바이더 등록 API, DriverManager.getConnection()이 서비스 엑세스 API, 그리고 Driver가 서비스 프로바이더 인터페이스 역할을 한다.

자바 6부터는 java.util.ServiceLoader라는 일반적인 용도의 서비스 프로바이더를 제공하지만, JDBC가 그 보다 이전에 만들어졌기 때문에 JDBC는 ServiceLoader를 사용하진 않는다.

Service Provider Pattern이란?(작성중)

단점 1. public 또는 protected 생성자 없이 static public 메소드만 제공하는 클래스는 상속할 수 없다.

예를들어, Collections 프레임워크에 있는 편의를 위한 구현체(java.util.Collections)는 상속할 수 없다. 불행중 다행인게 이건 단점을 가장한 장점이다. 개발자들이 상속대신 composition을 사용하게 장려하고 불변 타입을 요하기 때문에 장점이라 할 수 있다.

단점 2. 개발자가 static 팩토리 메소드를 찾는것이 어렵다.

static 팩토리 메소드들은 API docs에 명시되어 있지 않는다.(보통 constructor는 리스트가 모두 있다.)

Ref.

profile
Feedback is a gift

0개의 댓글