생성자 vs 팩토리 메소드


181

클래스를 모델링 할 때 선호하는 초기화 방법은 무엇입니까?

  1. 생성자 또는
  2. 공장 방법

그리고 그것들 중 하나를 사용하기 위해 고려해야 할 것은 무엇입니까?

어떤 상황에서는 객체를 생성 할 수없는 경우 null을 반환하는 팩토리 메소드를 선호합니다. 이것은 코드를 깔끔하게 만듭니다. 생성자에서 예외를 던지는 것과는 달리 대체 조치를 수행하기 전에 리턴 된 값이 널이 아닌지 간단히 확인할 수 있습니다. (나는 개인적으로 예외를 좋아하지 않는다)

id 값을 기대하는 클래스에 생성자가 있다고 가정 해보십시오. 생성자는이 값을 사용하여 데이터베이스에서 클래스를 채 웁니다. 지정된 ID를 가진 레코드가 존재하지 않는 경우 생성자는 RecordNotFoundException을 발생시킵니다. 이 경우 try..catch 블록 내에 이러한 모든 클래스의 구성을 포함해야합니다.

이와 대조적으로 레코드를 찾을 수 없으면 null을 반환하는 클래스에 정적 팩토리 메서드를 사용할 수 있습니다.

이 경우 생성자 또는 팩토리 방법 중 어떤 방법이 더 낫습니까?

답변:


66

디자인 패턴 : 108 페이지의 Gamma, Helm, Johnson 및 Vlisside의 재사용 가능한 객체 지향 소프트웨어 요소.

다음과 같은 경우 팩토리 메소드 패턴을 사용하십시오.

  • 클래스는 생성해야하는 객체의 클래스를 예상 할 수 없습니다
  • 클래스는 서브 클래스가 생성하는 객체를 지정하기를 원합니다.
  • 클래스는 여러 헬퍼 서브 클래스 중 하나에 책임을 위임하며, 어떤 헬퍼 서브 클래스가 위임인지에 대한 지식을 현지화하려고합니다.

21
정적 팩토리 메소드는 GoF 디자인 패턴 (팩토리 메소드 패턴)과 다릅니다. stackoverflow.com/questions/929021/…
Sree Rama

GoF 디자인 패턴의 팩토리 메소드 패턴과 비교하지 마십시오.
Sree Rama

137
이것은 아무 것도 설명하지 않습니다
Sushant

@Sushant, 왜 그렇습니까?
PaulD

2
이 답변은 질문에 대한 대답이 아니라 개념을 이해 / 설명하기 위해 읽을 정보를 전달합니다 ... 이것은 더 많은 주석입니다.
Crt

202

자신에게 무엇이고 왜 그런지 물어보십시오. 둘 다 객체의 인스턴스를 만들기 위해 존재합니다.

ElementarySchool school = new ElementarySchool();
ElementarySchool school = SchoolFactory.Construct(); // new ElementarySchool() inside

지금까지 아무런 차이가 없습니다. 이제 다양한 학교 유형이 있으며 ElementarySchool을 HighSchool (ElementarySchool에서 파생되거나 ElementarySchool과 동일한 인터페이스 ISchool을 구현 함)로 전환하려고합니다. 코드 변경은 다음과 같습니다.

HighSchool school = new HighSchool();
HighSchool school = SchoolFactory.Construct(); // new HighSchool() inside

인터페이스의 경우 :

ISchool school = new HighSchool();
ISchool school = SchoolFactory.Construct(); // new HighSchool() inside

이제이 코드가 여러 곳에있는 경우 팩토리 메소드를 변경하면 완료되었으므로 팩토리 메소드를 사용하는 것이 상당히 저렴하다는 것을 알 수 있습니다 (두 번째 예제를 인터페이스와 함께 사용하는 경우).

이것이 주요 차이점과 장점입니다. 복잡한 클래스 계층을 다루기 시작하고 이러한 계층에서 클래스 인스턴스를 동적으로 만들려면 다음 코드를 얻습니다. 팩토리 메소드는 인스턴스화 할 구체적인 인스턴스를 메소드에 알려주는 매개 변수를 취할 수 있습니다. MyStudent 클래스가 있고 학생이 해당 학교의 구성원이되도록 해당 ISchool 개체를 인스턴스화해야한다고 가정 해 봅시다.

ISchool school = SchoolFactory.ConstructForStudent(myStudent);

이제 다른 IStudent 객체에 대해 인스턴스화 할 ISchool 객체를 결정하는 비즈니스 로직이 포함 된 앱의 한 곳이 있습니다.

따라서 간단한 클래스 (값 객체 등)의 경우 생성자는 훌륭하지만 (응용 프로그램을 너무 많이 엔지니어링하고 싶지는 않습니다) 복잡한 클래스 계층의 경우 팩토리 메소드가 선호되는 방법입니다.

이런 식으로 당신은 "구현이 아니라 인터페이스에 대한 프로그램"이라는 네 권의 책 에서 첫 번째 디자인 원칙을 따릅니다 .


2
간단한 수업이라고 생각할 때도 누군가 간단한 수업을 확장해야 할 가능성이 있으므로 팩토리 방법이 여전히 좋습니다. 예를 들어 ElementarySchool로 시작할 수 있지만 나중에 누군가 (자신 포함)는 PrivateElementarySchool 및 PublicElementarySchool로 확장 할 수 있습니다.
jack

10
이 허용 대답해야한다
am05mhz

2
@David, 좋은 대답이지만 각 인터페이스 구현에 구성에 다른 매개 변수가 필요할 수있는 예를 확장 할 수 있습니다. 다음은 바보 같은 예제 IFood sandwich = new Sandwich(Cheese chz, Meat meat);IFood soup = new Soup(Broth broth, Vegetable veg);어떻게 공장 및 또는 빌더 도움이 여기에?
Brian

1
방금 공장 사용을 목적으로 세 가지 다른 설명을 읽었습니다. 이것이 마침내 "클릭"한 것입니다. 감사합니다!
Daniel Peirano

이것이 왜 대답이되지 않습니까?
Tomas

74

효과적인 Java 2 항목 1 : 생성자 대신 정적 팩토리 메소드를 고려하십시오 ( 액세스 할 수있는 경우) .

정적 팩토리 메소드의 장점 :

  1. 그들은 이름이 있습니다.
  2. 호출 할 때마다 새 객체를 만들 필요는 없습니다.
  3. 반환 유형의 하위 유형의 객체를 반환 할 수 있습니다.
  4. 매개 변수화 된 유형 인스턴스 작성의 세부 사항을 줄입니다.

정적 팩토리 메소드 단점 :

  1. 정적 팩토리 메소드 만 제공하는 경우 공용 또는 보호 생성자가없는 클래스는 서브 클래스 화 될 수 없습니다.
  2. 그것들은 다른 정적 메소드와 쉽게 구별되지 않습니다.

4
이것은 Java의 심각한 버그, 일반적인 OOD 문제 인 것 같습니다. 도하지 않는 다수의 OO 언어가 있습니다 생성자를 아직 서브 클래스 작품 잘가.
Jörg W Mittag

1
@cherouvim 왜 대부분의 코드는 경우 생성자를 사용하여 작성( factory methods are better than Constructors. ( Item-1 ) ) Effective java
아시프 Mushtaq

좋은 지적입니다. 그래도 Java 전용입니다. 팩토리 메소드를 다른 정적 메소드와 구별 할 수있는 언어 기능의 경우를 작성할 수 있습니다.
OCDev

30

기본적으로 생성자와 이해하기가 더 간단하므로 생성자가 선호됩니다. 그러나 클라이언트 코드에서 이해할 수 있듯이 객체의 구성 기능을 의미 적 의미에서 분리해야하는 경우 팩토리를 사용하는 것이 좋습니다.

생성자와 팩토리의 차이점은 변수 및 변수에 대한 포인터와 유사합니다. 또 다른 수준의 간접 지향이 있는데, 이는 단점입니다. 그러나 또 다른 수준의 유연성이 있기 때문에 이점이 있습니다. 따라서 선택하는 동안이 비용 대 이익 분석을 수행하는 것이 좋습니다.


17
따라서 (TDD 스타일) 작업을 수행하는 가장 간단한 방법으로 생성자로 시작합니다. 그런 다음 코드 냄새가 나기 시작하면 팩토리로 리팩터링하십시오 (반복되는 조건부 논리가 호출 할 생성자를 결정하는 것과 같이)?
AndyM

1
매우 중요한 점. 공장 및 생성자를 비교하는 사용자의 연구는 공장 API의 유용성에 해로운 것을 나타내는 매우 의미있는 결과를 발견 : "사용자가 훨씬 더 많은 시간을 필요로 (P = 0.005) 생성자보다 공장을 가진 개체를 생성하기 위해" API 디자인 [팩토리 패턴 : 사용성 평가 ].
mdeff

12

생성자를 사용하여 수행 할 수없는 방식으로 객체 생성에 대한 추가 제어가 필요한 경우에만 팩토리를 사용하십시오.

예를 들어 공장은 캐싱 가능성이 있습니다.

팩토리를 사용하는 다른 방법은 구성하려는 유형을 모르는 시나리오입니다. 종종 플러그인 팩토리 시나리오에서 이러한 유형의 사용법을 볼 수 있는데, 여기서 각 플러그인은 기본 클래스에서 파생되거나 일종의 인터페이스를 구현해야합니다. 팩토리는 기본 클래스에서 파생되거나 인터페이스를 구현하는 클래스 인스턴스를 작성합니다.


11

"Effective Java"의 인용, 2nd ed., Item 1 : 생성자 대신 정적 팩토리 메소드를 고려하십시오, p. 5 :

" 정적 팩토리 방법은 디자인 패턴의 팩토리 방법 패턴 [Gamma95, p. 107]과 동일하지 않습니다.이 항목에서 설명하는 정적 팩토리 방법은 디자인 패턴과 직접적으로 동일하지 않습니다."


10

다른 고전 서적"유효한 java" (다른 답변에서 언급 한 것) 외에도 다음 과 같이 제안합니다.

오버로드 된 생성자보다 정적 팩토리 메소드 (인자를 설명하는 이름 포함)를 선호하십시오.

예 : 쓰지 마

Complex complex = new Complex(23.0);

대신에 쓰십시오

Complex complex = Complex.fromRealNumber(23.0);

이 책은 Complex(float)생성자를 비공개로 설정하여 사용자가 정적 팩토리 메소드를 강제로 호출 하도록 제안합니다 .


2
책의 일부를 읽으면 여기로 왔어요
Purple Haze

1
@ Bayrem : 나도 최근에 다시 읽고 있었고 대답에 추가해야한다고 생각했습니다.
blue_note

1
관련 메모에서, 당신은 몇 가지 도움이 될 명명 규칙은에 의해 일 java.time의 프레임 워크 의 이름에 대한 from…, to…, parse…, with…, 등. 마음에 계속 java.time 클래스가 불변 내장되어 있습니다,하지만이 명명 규칙의 일부도 변경 가능한 클래스와 함께 수행하는 것이 유용 할 수 있습니다.
Basil Bourque

7

CAD / CAM 애플리케이션의 구체적인 예입니다.

생성자를 사용하여 절단 경로를 만듭니다. 절단 경로를 정의하는 일련의 선과 호입니다. 일련의 선과 호는 다를 수 있고 다른 좌표를 가질 수 있지만 목록을 생성자에 전달하면 쉽게 처리 할 수 ​​있습니다.

팩토리를 사용하여 모양을 만듭니다. 셰이프 클래스가 있지만 각 셰이프는 셰이프 유형에 따라 다르게 설정됩니다. 사용자가 선택할 때까지 초기화 할 모양을 알 수 없습니다.


5

id 값을 기대하는 클래스에 생성자가 있다고 가정 해보십시오. 생성자는이 값을 사용하여 데이터베이스에서 클래스를 채 웁니다.

이 프로세스는 반드시 생성자 외부에 있어야합니다.

  1. 생성자는 데이터베이스에 액세스해서는 안됩니다.

  2. 생성자의 작업과 이유는 데이터 멤버초기화 하고 생성자로 전달 된 값을 사용하여 클래스 불변설정하는 것 입니다.

  3. 다른 모든 방법에는 정적 팩토리 메소드 를 사용 하거나 복잡한 경우 별도의 팩토리 또는 빌더 클래스 를 사용하는 것이 좋습니다 .

Microsoft의 일부 생성자 가이드 라인 :

생성자에서 최소한의 작업을 수행하십시오. 생성자는 생성자 매개 변수를 캡처하는 것 외에 많은 작업을 수행해서는 안됩니다. 다른 처리 비용은 필요할 때까지 지연되어야합니다.

원하는 조작의 의미가 새 인스턴스의 구성에 직접 맵핑되지 않는 경우 생성자 대신 정적 팩토리 메소드를 사용하십시오.


2

때로는 객체를 만드는 동안 일부 값 / 조건을 확인 / 계산해야합니다. 그리고 예외를 던질 수 있다면 constructro는 매우 나쁜 방법입니다. 따라서 다음과 같이해야합니다.

var value = new Instance(1, 2).init()
public function init() {
    try {
        doSome()
    }
    catch (e) {
        soAnotherSome()
    }
}

모든 추가 계산이 init ()에있는 경우 그러나 개발자로서 당신 만이이 init ()에 대해 알고 있습니다. 물론 몇 달이 지나면 잊어 버립니다. 그러나 공장이있는 경우-직접 호출 에서이 init ()를 숨기고 한 가지 방법으로 필요한 모든 작업을 수행하므로 문제가 없습니다. 이 접근 방식을 사용하면 생성 및 메모리 누수 문제가 발생하지 않습니다.

누군가 캐싱에 대해 말 했어요. 좋아요 그러나 팩토리 방식과 함께 사용하기에 좋은 Flyweight 패턴도 기억해야합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.