Java / Java EE에서와 유사한 복잡성을 관리하면서 다른 인기있는 언어가 팩토리 패턴을 사용하지 않아도되는 방법은 무엇입니까?


23

팩토리 패턴 (또는 적어도의 사용 FactoryFactory..)은 here 과 같은 많은 농담의 엉덩이입니다 .

RequestProcessorFactoryFactory.RequestProcessorFactory 와 같이 상세하고 "창의적인"이름을 갖는 것 외에도 Java / C ++로 프로그래밍해야하고 Abstract_factory_pattern 의 사용 사례가 있다면 팩토리 패턴에 근본적으로 잘못된 것이 있습니까?

다른 유명한 언어 (예 : Ruby 또는 Scala )가 유사한 복잡성을 관리하면서 언어 를 사용하지 않아도되는 방법은 무엇입니까?

내가 묻는 이유는 주로 Java / Java EE 생태계와 관련하여 언급 된 공장에 대한 비판을 볼 수 있지만 다른 언어 / 프레임 워크가 어떻게이를 해결하는지 설명하지는 않기 때문입니다.



4
문제는 공장을 사용하는 것이 아니라 완벽하게 훌륭한 패턴입니다. 특히 엔터프라이즈 자바 세계에서 널리 사용되는 공장을 과도하게 사용하고 있습니다.
코드 InChaos

아주 좋은 농담 링크. 스파이스 랙을 만들기 위해 도구를 검색하는 기능적 언어 해석을보고 싶습니다. 그것은 정말로 그것을 집으로 몰아 넣을 것이라고 생각합니다.
Patrick M

답변:


28

귀하의 질문은 "Java"로 태그가 지정됩니다. Factory 패턴이 왜 조롱되는지 묻는 것은 놀라운 일이 아닙니다. Java 자체에는 해당 패턴에 대한 패키지 악용이 포함되어 있습니다.

예를 들어 파일에서 XML 문서를로드하고 XPath 쿼리를 실행하십시오. 팩토리와 빌더를 설정하려면 10 줄의 코드가 필요합니다.

DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = builderFactory.newDocumentBuilder(); 

Document xmlDocument = builder.parse(new FileInputStream("c:\\employees.xml"));
XPath xPath =  XPathFactory.newInstance().newXPath();

xPath.compile(expression).evaluate(xmlDocument);

이 API를 디자인하는 사람들이 개발자로 일한 적이 있는지 또는 책을 읽고 물건을 던지는 지 궁금합니다. 나는 그들이 파서를 직접 작성하고 싶지 않고 다른 사람들에게 과제를 맡기고 싶지만 여전히 추한 구현을 만든다는 것을 이해합니다.

대안이 무엇인지 묻기 때문에 C #으로 XML 파일을로드하는 중입니다.

XDocument xml = XDocument.Load("c:\\employees.xml");
var nodes = xml.XPathSelectElements(expression);

새로 부화 한 Java 개발자들이 Factory의 광기를보고 의심 스럽다고 생각합니다. Java를 직접 만든 천재들이 그렇게 많이 사용한다면 말입니다.

공장 및 기타 패턴은 각각 특정 작업에 적합한 도구입니다. 작업에 적용하면 적합하지 않은 코드가 있어야합니다.


1
C # 대안이 새로운 LINQ to XML을 사용하고 있다는 점에 주목하십시오. 이전의 DOM 대안은 다음과 같이 보일 것 XmlDocument xml = new XmlDocument(); xml.Load("c:\\employees.xml"); XmlNodeList nodes = xml.SelectNodes(expression); 입니다. MS 권장 방법으로 다음을 발견했습니다. support.microsoft.com/kb/308333
Bob

36

종종 사람들은 무슨 일이 일어나고 있는지 (그리고 많은 웃음을 포함하여) 오해합니다.
많은 사람들이 그것을 사용하는 방식만큼 나쁘다는 것은 그 자체가 팩토리 패턴이 아닙니다.

그리고 의심 할 여지없이 프로그래밍 (및 패턴)을 가르치는 방식에서 비롯됩니다. Schoolkids (종종 "학생"이라고 함)는 "패턴 Y를 사용하여 X 만들기"라는 메시지를 받고 몇 번의 반복 후에는 프로그래밍 문제에 접근 할 수있는 방법이라고 생각합니다.
그래서 그들은 적절하든 그렇지 않든 학교에서 좋아하는 특정 패턴을 모든 것에 대해 적용하기 시작합니다.

그리고 슬프게도 소프트웨어 디자인에 관한 책을 쓰는 대학 교수도 포함됩니다.
이것의 절정은 내가 만든 것 중 하나에 의해 유지되어야하는 명백한 불만을 가진 시스템이었다. ).
그것은 각 계층 자체가 3 계층 시스템 (분리해야 함)과 함께 3 계층 패턴을 기반으로했습니다. 각 계층 세트 사이의 인터페이스에는 양쪽에 데이터를 다른 계층으로 전송할 오브젝트를 생성하는 팩토리와 수신 된 오브젝트를 수신 계층에 속하는 오브젝트로 변환하는 오브젝트를 생성하는 팩토리가있었습니다.
각 팩토리마다 추상 팩토리가있었습니다 (누군가가 팩토리를 변경해야 할 수도 있고 호출 코드를 변경하지 않으려는 것을 알고 있습니다 ...).
그리고 그 모든 혼란은 물론 완전히 문서화되지 않았습니다.

시스템은 데이터베이스를 5 번째 정규 형식으로 정규화했습니다.

기본적으로 수신 및 발신 문서를 기록하고 추적하는 데 사용할 수있는 주소록 이상의 시스템 인 C ++에는 100MB 코드베이스와 50 개가 넘는 데이터베이스 테이블이있었습니다. 소프트웨어를 실행하는 컴퓨터에 직접 연결된 프린터를 사용하여 500 개의 양식 편지를 인쇄하는 데 72 시간이 걸릴 수 있습니다.

그것이 사람들이 패턴을 비웃는 이유, 특히 사람들이 단일의 특정 패턴에 독특하게 집중하는 이유입니다.


40
다른 이유는 4 가지 갱 (Gang of Four) 패턴 중 일부는 일류 함수를 사용하여 언어로 사소하게 수행 할 수있는 것들에 대한 장황한 해킹이기 때문에 불가피한 반 패턴이되기 때문입니다. "Strategy", "Observer", "Factory", "Command"및 "Template Method"패턴은 함수를 인수로 전달하는 데 사용됩니다. "방문자"는 합계 유형 / 변형 / 태그 조합에서 패턴 일치를 수행하는 핵입니다. 어떤 사람들은 문자 그대로 다른 많은 언어에서 사소한 것에 대해 많은 열망을 가지고 있다는 사실 때문에 사람들은 C ++, Java 및 유사한 언어를 조롱하게됩니다.
Doval

7
이 일화에 감사드립니다. 또한 "대체 방법은 무엇입니까?" 에 대해 좀 더 자세히 설명해 주 시겠습니까? 질문의 일부로 우리도 그렇게되지 않습니까?
Philipp

1
@Doval 함수 만 전달할 수있을 때 실행 된 명령 또는 호출 된 전략에서 값을 반환하는 방법을 알려줄 수 있습니까? 그리고 클로저를 말하면 클래스를 만드는 것과 정확히 같은 것을 명심하십시오.
Euphoric

2
@Euphoric 문제가 보이지 않습니다. 정교하게 말씀해 주시겠습니까? 어쨌든 그들은 정확히 같은 것이 아닙니다. 단일 필드가있는 객체가 변수에 대한 포인터 / 참조와 정확히 동일하거나 정수가 (실제) 열거 형과 정확히 동일하다는 것 외에도 말할 수 있습니다. 실제로 차이점이 있습니다. 함수를 비교하는 것은 의미가 없습니다. 익명 클래스에 대한 간결한 구문을 아직 보지 못했습니다. 그리고 같은 인자와 리턴 타입을 가진 모든 함수는 같은 타입을 가진다 (같은 이름 일 때도 다른 타입을 가진 클래스 / 인터페이스와 달리, 동일한 타입 일지라도 다른 타입이다)
Doval

2
@ 유포 닉 올바름. 부작용없이 작업을 수행 할 수있는 방법이 있다면 나쁜 기능적 스타일로 간주됩니다. 간단한 대안은 합계 유형 / 태그 조합을 사용하여 N 종류의 값 중 하나를 반환하는 것입니다. 어쨌든 실제적인 방법이 없다고 가정하자. 수업과 같지 않습니다. 실제로는 인터페이스입니다 (OOP 의미). 동일한 서명을 가진 함수 쌍이 상호 교환 가능하다고 생각하는지 여부는 어렵지 않지만 두 클래스가 동일한 내용을 가지고 있어도 상호 교환 가능하지 않습니다.
Doval

17

일부 상황에서 공장은 우아한 애플리케이션 설계를 가능하게하는 많은 장점이 있습니다. 하나는 팩토리를 만들어 나중에 생성하려는 객체의 속성을 한 곳에서 설정 한 다음 해당 팩토리를 넘길 수 있다는 것입니다. 그러나 종종 실제로 그렇게 할 필요는 없습니다. 이 경우 팩토리를 사용하면 실제로 대가로 아무것도주지 않고 복잡성을 추가합니다. 이 공장을 예로 들어 보겠습니다.

WidgetFactory redWidgetFactory = new ColoredWidgetFactory(COLOR_RED);
Widget widget = redWidgetFactory.create();

팩토리 패턴의 대안 중 하나는 매우 유사한 빌더 패턴입니다. 가장 큰 차이점은 팩토리가 생성 될 때 팩토리가 생성 한 객체의 속성이 설정되는 반면 빌더는 기본 상태로 초기화되고 모든 속성이 나중에 설정된다는 것입니다.

WidgetBuilder widgetBuilder = new WidgetBuilder();
widgetBuilder.setColor(COLOR_RED);
Widget widget = widgetBuilder.create();

그러나 오버 엔지니어링이 문제인 경우 팩토리를 빌더로 교체해도 크게 개선되지는 않습니다.

두 패턴 중 가장 간단한 대체는 물론 new연산자를 사용하여 간단한 생성자로 객체 인스턴스를 만드는 것 입니다.

Widget widget = new ColoredWidget(COLOR_RED);

그러나 생성자는 대부분의 객체 지향 언어에서 결정적인 단점이 있습니다. 해당 클래스의 객체를 반환해야하며 하위 유형을 반환 할 수 없습니다.

런타임에 하위 유형을 선택해야하지만 완전히 새로운 빌더 또는 팩토리 클래스를 작성하지 않으려는 경우 팩토리 메소드를 대신 사용할 수 있습니다. 이것은 해당 클래스 또는 하위 클래스 중 하나의 새 인스턴스를 반환하는 클래스의 정적 메서드입니다. 내부 상태를 유지하지 않는 팩토리는 종종 다음과 같은 팩토리 방법으로 교체 할 수 있습니다.

 Widget widget = Widget.createColoredWidget(COLOR_RED); // returns an object of class RedColoredWidget

Java 8의 새로운 기능 은 상태 비 저장 팩토리에서와 마찬가지로 메소드를 전달할 수 있는 메소드 참조 입니다. 편리하게도 메소드 참조를 받아들이는 것은 동일한 기능 인터페이스를 구현하는 모든 객체를 허용하며, 내부 상태를 가진 본격적인 팩토리 일 수도 있으므로 나중에 이유를 알면 팩토리를 쉽게 소개 할 수 있습니다.


2
팩토리는 종종 생성 후 구성 할 수도 있습니다. 빌더와의 주요 차이점은 빌더가 일반적으로 단일의 복잡한 인스턴스를 작성하는 데 사용되고 팩토리는 많은 유사한 인스턴스를 작성하는 데 사용된다는 것입니다.
Cephalopod

1
고맙지 만 (+1), 대답은 다른 PL이 어떻게 그것을 해결하는지 설명하지 못하지만 Java 8 메소드 참조를 지적 해 주셔서 감사합니다.
senseiwu

1
나는 종종 문제의 유형과 실제 객체 생성을 제한하기 위해 객체 지향 프레임 워크 이해하고있는 것이라고 생각했습니다 foo = new Bar(23);동등 foo = Bar._createInstance(23);. 객체는 전용 getRealType()메소드를 사용하여 자신의 유형을 안정적으로 쿼리 할 수 ​​있어야 하지만 외부 호출에 의해 반환 될 수퍼 타입을 지정할 수 있어야합니다 getType(). 외부 코드는 [예를 들어 인스턴스와 달리 ] new String("A")실제로 인스턴스를 반환 하는지 여부는 신경 쓰지 않아야합니다 . StringSingleCharacterString
supercat

1
컨텍스트를 기반으로 서브 클래스를 선택해야 할 때 많은 정적 메소드 팩토리를 사용하지만 차이점은 호출자에게 불투명해야합니다. 예를 들어 모든 draw(OutputStream out)HTML 클래스를 포함 하지만 약간 다른 HTML을 생성 하는 일련의 이미지 클래스가 있습니다 . 정적 메소드 팩토리는 상황에 맞는 클래스를 작성한 다음 호출자가 draw 메소드를 사용할 수 있습니다.
Michael Shopsin

1
@supercat 당신은 objective-c를 보길 원할 것입니다. 객체의 구성은 보통 간단한 메소드 호출로 구성됩니다 [[SomeClass alloc] init]. 완전히 다른 클래스의 인스턴스 또는 다른 객체 (캐시 된 값과 같은)를 반환 할 수 있습니다
axelarge

3

어떤 종류의 공장은 적절한 상황에서 거의 모든 객체 지향 언어로 발견됩니다. 때로는 문자열과 같은 간단한 매개 변수를 기반으로 만들 개체 종류를 선택하는 방법이 필요할 때가 있습니다.

어떤 사람들은 그것을 너무 멀리 가져 와서 공장 내부를 제외하고 생성자를 호출 할 필요가 없도록 코드를 설계하려고합니다. 공장 공장이 있으면 상황이 말도 안되기 시작합니다.

스칼라를 배웠을 때 충격을 받았지만 루비를 알지 못했지만 거의 같은 방식이라고 생각합니다. 언어가 표현력이 충분하기 때문에 프로그래머가 항상 "배관 작업"을 외부 구성 파일로 푸시하려고하지는 않습니다. . 스칼라에서는 특성이 혼합 된 객체를 사용하여 여러 구성으로 클래스를 연결하는 팩토리 팩토리를 작성하려고하지 않고, 종종 Java로 과도하게 엔지니어링되는 작업에 대해 언어로 간단한 DSL을 작성하는 것이 상대적으로 쉽습니다.

또한 다른 의견과 답변에서 지적했듯이 클로저와 일류 함수는 많은 패턴이 필요하지 않습니다. 이런 이유로 C ++ 11과 Java 8이 널리 채택되면서 많은 안티 패턴이 사라지기 시작할 것이라고 생각합니다.


감사합니다 (+1). 당신의 대답은 스칼라가 그것을 피하는 방법에 관한 나의 의심 중 일부를 설명합니다. 솔직히 말하면, 엔터프라이즈 수준에서 한 번만 (만약) 스칼라가 자바보다 절반 이상 인기를 얻었고 프레임 워크, 패턴, 유료 앱 서버 등이 등장한다고 생각하지 않습니까?
senseiwu

스칼라가 자바를 능가한다면 사람들은 소프트웨어 설계의 "스칼라 방식"을 선호하기 때문일 것이다. Java 5는 제네릭, Java 8의 람다 및 스트림과 같이 사람들이 스칼라로 전환하도록하는 더 많은 기능을 계속해서 채택하고 있기 때문에 프로그래머는 결국 안티 패턴을 포기하게 될 것입니다. 기능을 사용할 수 없습니다. 언어가 아무리 좋더라도 항상 FactoryFactory 준수자가 있습니다. 분명히 어떤 사람들은 그러한 아키텍처를 좋아하거나 그렇게 흔하지 않을 것입니다.
Karl Bielefeldt

2

일반적으로 Java 프로그램이 엄청나게 과도하게 엔지니어링되는 경향이 있습니다 [인용 필요]. 공장을 많이 보유하는 것은 과잉 엔지니어링의 가장 흔한 증상 중 하나입니다. 그렇기 때문에 사람들이 재미를 느끼게됩니다.

특히, Java가 팩토리에서 갖는 문제는 Java에서 a) 생성자가 함수가 아니며 b) 함수가 일류 시민이 아니라는 것입니다.

이와 같은 것을 작성할 수 있다고 상상해보십시오 ( JREFunction 의 잘 알려진 인터페이스가 되십시오 )

// Framework code
public Node buildTree(Function<BranchNode, Node, Node> branchNodeFactory) {
    Node current = nextNode();
    while (hasNextNode()) {
        current = branchNodeFactory.call(current, nextNode());
    }
    return current
}

// MyDataNode.java
public class MyBranchNode implements BranchNode {

    public MyBranchNode(Node left, Node right) { ... }
}

// Client code
Node root = buildTree(MyBranchNode::new);

팩토리 인터페이스 또는 클래스가 없음을 참조하십시오. 많은 동적 언어에는 일류 기능이 있습니다. 아아, 이것은 Java 7 또는 이전 버전에서는 불가능합니다 (공장으로 구현하는 것은 독자에게 연습으로 남습니다).

따라서 대안은 "다른 패턴을 사용하는 것"이 ​​아니라 "더 나은 객체 모델을 가진 언어를 사용하는 것"또는 "간단한 엔지니어링을하지 않는 간단한 문제"입니다.


2
이것은 "대체가 무엇입니까?"라는 질문에 대답조차 시도하지 않습니다.
gnat

1
두 번째 단락을 지나면 "다른 언어로 수행하는 방법"부분을 보게됩니다. (PS : 다른 답변도 대안을 보여주지 않습니다)
Cephalopod

4
@gnat 그는 대안이 일급 함수를 사용하는 것이라고 간접적으로 말하지 않습니까? 원하는 것이 객체를 생성 할 수있는 블랙 박스라면 옵션은 팩토리 또는 함수이며 팩토리는 변장 기능 일뿐입니다.
Doval

3
실제로 필요한 것은 일류 함수를 가진 언어이기 때문에 "많은 동적 언어에서"는 약간 오해의 소지가 있습니다. "동적"은 이것과 직교합니다.
Andres F.

사실이지만 종종 동적 언어로 볼 수있는 속성입니다. 나는 대답의 시작 부분에서 일급 기능의 필요성을 언급했다.
Cephalopod
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.