생성자의 합법적 인 "실제 작업"?


23

디자인 작업을하고 있지만 계속 장애물을 치고 있습니다. XML 스키마를 구문 분석하여 생성 한 복잡한 노드 트리의 소유자 인 DOM (특정 클래스) 인 특정 클래스 (ModelDef)가 있습니다. 좋은 설계 원칙 (SOLID)을 따르고 결과 시스템을 쉽게 테스트 할 수 있는지 확인하고 싶습니다. DI를 사용하여 종속성을 ModelDef의 생성자로 전달하려는 모든 의도가 있습니다 (필요한 경우 테스트 중에 쉽게 바꿀 수 있도록).

그래도 내가 고투하고있는 것은 노드 트리를 만드는 것입니다. 이 트리는 독립적으로 테스트 할 필요가없는 단순한 "값"객체로 구성됩니다. (그러나 여전히 이러한 팩토리 생성을 돕기 위해 추상 팩토리를 ModelDef로 전달할 수 있습니다.)

그러나 나는 생성자 가 실제 작업을 수행해서는 안된다는 것을 계속 읽 습니다 (예 : Flaw : Constructor does Real Work ). "실제 작업"이 나중에 테스트를 위해 스터브하고 싶을 때 무거운 중량의 종속물을 만드는 것이 의미가 있다면 이것은 나에게 완벽하게 이해됩니다. (이들은 DI를 통해 전달되어야합니다.)

그러나이 노드 트리와 같은 경량의 값 객체는 어떻습니까? 나무는 어딘가에 만들어야합니까? 왜 ModelDef의 생성자를 통해 (예를 들어, buildNodeTree () 메소드를 사용하여)?

스키마를 파싱하여 노드 트리를 만들려면 상당한 양의 복잡한 코드 (철저히 테스트 해야하는 코드)가 필요하기 때문에 ModelDef 외부에서 노드 트리를 생성 한 다음 생성자 DI를 통해 전달하고 싶지 않습니다. . 나는이 코드를 "접착제 (glue)"코드로 공개하고 싶지 않다.

별도의 "builder"객체에 노드 트리를 작성하는 코드를 넣는 것을 생각했지만 실제로는 Builder Pattern과 일치하지 않기 때문에 "builder"라고 부르는 것이 주저합니다. 생성자). 그러나 내가 그것을 다른 것으로 불렀더라도 (예를 들어 NodeTreeConstructor) ModelDef 생성자가 노드 트리를 작성하지 못하게하는 것은 여전히 ​​약간의 해킹처럼 느껴집니다. 어딘가에 지어 져야한다. 왜 그것을 소유 할 대상에 있지 않습니까?


7
그래도 항상 담요 선언에주의해야합니다. 일반적인 경험 법칙은 상황에 따라 다양하고 명확하고 기능적이며 테스트, 재사용 및 유지 관리가 쉬운 코드입니다. 코드 복잡성과 혼동 만 있으면 "규칙"을 따르려고 시도하면 상황에 맞는 규칙이 아닙니다. 이러한 "패턴"및 언어 기능은 모두 도구입니다. 특정 작업에 가장 적합한 것을 사용하십시오.
Jason C

답변:


26

Ross Patterson이 제안한 것 외에도이 위치를 고려하십시오.

  1. 소금 한 알로 "너희는 너의 건설자들에게 실제 일을하지 말라"와 같은 극대를 취하십시오.

  2. 생성자는 실제로 정적 메서드 일뿐입니다. 따라서 구조적으로는 실제로 다음과 같이 큰 차이가 없습니다.

    a) 간단한 생성자와 복잡한 정적 팩토리 메소드

    b) 간단한 생성자와 더 복잡한 생성자.

생성자에서 실제 작업을 수행하는 것에 대한 부정적인 감정의 상당 부분은 생성자 내에서 예외가 발생하는 경우 개체가 어떤 상태에 있는지에 대한 논쟁이 있었을 때 C ++ 역사의 특정 기간에서 비롯됩니다. 그런 경우에는 소멸자를 호출해야합니다. C ++의 역사에서 그 부분은 끝났고 문제는 해결되었지만 Java와 같은 언어에서는 이런 종류의 문제가 처음부터 없었습니다.

내 의견은 new(Dependency Injection을 사용하려는 의도대로) 생성자에서 사용 을 피하면 괜찮을 것입니다. "생성자의 조건부 또는 루핑 논리는 결함의 경고 신호입니다"와 같은 문장을 비웃습니다.

그 외에도 개인적으로 XML 구문 분석 논리를 생성자에서 가져옵니다. 생성자에 복잡한 논리가있는 것이 악의적 인 것이 아니라 "관심 분리"원칙을 따르는 것이 좋기 때문입니다. 따라서 XML 구문 분석 논리를 클래스에 속하는 정적 메서드가 아닌 별도의 클래스로 옮길 것 ModelDef입니다.

개정

외부 에서 XML ModelDef을 작성 하는 메소드가있는 경우 ModelDef동적 임시 트리 데이터 구조를 인스턴스화하고 XML을 구문 분석하여 채우고 새 ModelDef구조를 생성자 매개 변수로 작성해야 한다고 가정합니다. 따라서 이것은 "빌더"패턴의 응용으로 생각 될 수 있습니다. 당신이하고 싶은 일과 String& StringBuilder쌍 사이에는 매우 가까운 비유가 있습니다 . 그러나 나에게 명확하지 않은 이유로이 Q & A가 동의하지 않는 것으로 나타났습니다 : Stackoverflow-StringBuilder and Builder Pattern . StringBuilder"빌더"패턴을 구현할 것인지 아닌지에 관한 긴 논쟁을 피하기 위해 , 나는 어떻게 영감을 받는지에 대해 자유롭게 말할 것입니다.StrungBuilder 는 귀하의 요구에 맞는 솔루션을 고안하고 세부 사항이 해결 될 때까지이를 "빌더"패턴의 응용 프로그램으로 연기합니다.

이 새로운 질문을보십시오 : 프로그래머 SE :“StringBuilder”는 Builder Design Pattern의 응용입니까?


3
@RichardLevasseur 90 년대 초 중반 중반에 C ++ 프로그래머들 사이에서 우려와 토론의 주제로 기억합니다. 이 게시물을 보면 : gotw.ca/gotw/066.htm 당신은 그것이 상당히 복잡하고 상당히 복잡한 것들이 논란의 여지가 있음을 알 수 있습니다. 나는 확실히 모르겠지만, 내가 생각하는 그 물건의 90 년대 초반 부분에서 아직 표준화되지 않은 것을. 그러나 죄송합니다. 좋은 참고 자료를 제공해 드릴 수 없습니다.
Mike Nakis

1
@Gurtz xml 파일 (또는 문서의 구조) 형식은 가능성에 관계없이 개발중인 특정 응용 프로그램에 연결되어 있기 때문에 응용 프로그램 별 "xml 유틸리티"와 같은 클래스를 생각합니다. "ModelDef"를 재사용하십시오.
Mike Nakis

1
@Gurtz 그래서 아마도 메인 "Application"클래스의 인스턴스 메소드를 만들거나 Ross Patterson이 제안한 것과 매우 유사한 방식으로 일부 도우미 클래스의 정적 메소드를 만들 것입니다.
Mike Nakis

1
@Gurtz는 "빌더"접근 방식을 구체적으로 언급하지 않은 것에 대해 사과드립니다. 내 답변을 수정했습니다.
Mike Nakis

3
@Gurtz 그것은 가능하지만 학문적 호기심 외에는 중요하지 않습니다. "패턴 안티 패턴"에 빠지지 마십시오. 패턴은 실제로 공통적이고 유용한 코딩 기술을 다른 사람들에게 편리하게 설명하기위한 이름 일뿐입니다. 설명이 필요한 경우 나중에해야 할 일을하고 나중에 레이블을칩니다. 코드가 의미가있는 한 "어쩌면 어떤 방식 으로든 빌더 패턴과 같은"것을 구현하는 것이 좋습니다. 새로운 기술을 배울 때 패턴에 집중하는 것이 합리적입니다. 단지 모든 것이 명명 된 패턴이어야한다는 생각의 함정에 빠지지 마십시오.
Jason C

9

ModelDef생성자 에서이 작업을 수행하지 않는 가장 좋은 이유는 이미 있습니다 .

  1. XML 문서를 노드 트리로 파싱하는 것에 대한 "가벼운"것은 없습니다.
  2. ModelDefXML 문서에서만 만들 수 있다고 말하는 것은 분명하지 않습니다 .

클래스 ModelDef.FromXmlString(string xmlDocument)ModelDef.FromXmlDoc(XmlDoc parsedNodeTree), 의 다양한 정적 메소드가 있어야하는 것처럼 들립니다 .


답장을 보내 주셔서 감사합니다! 정적 메소드 제안에 대해. 이들은 다양한 XML 소스에서 ModelDef 인스턴스를 작성하는 정적 팩토리입니까? 아니면 이미 작성된 ModelDef 오브젝트를로드해야합니까? 후자의 경우, 객체가 부분적으로 만 초기화되는 것에 대해 걱정할 것입니다 (ModelDef가 완전히 초기화되기 위해서는 노드 트리가 필요하기 때문에). 생각?
Gurtz

3
맞대기 위해 실례하지만, Ross가 의미하는 바는 완전히 구성된 인스턴스를 반환하는 정적 팩토리 메소드입니다. 전체 프로토 타입은 다음과 같습니다 public static ModelDef createFromXmlString( string xmlDocument ). 이것은 상당히 일반적인 관행입니다. 가끔 나도 그래 당신이 또한 단지 생성자 를 할 수 있다는 제 제안은 대안적인 접근법이 정당한 이유없이 "고정되지 않은"것으로 기각 될 것으로 의심되는 상황에서 표준적인 유형의 반응입니다.
Mike Nakis

1
@ Mike-Nakis, 설명해 주셔서 감사합니다. 따라서이 경우 정적 팩토리 메소드는 노드 트리를 빌드 한 다음 ModelDef의 생성자 (아마도 개인용)로 전달합니다. 이해하세요 감사.
Gurtz

@ 구르 츠
로스 패터슨

5

나는 그 "규칙"을 전에 들었다. 내 경험상 그것은 참과 거짓입니다.

보다 "고전적인"객체 지향에서는 상태와 행동을 캡슐화하는 객체에 대해 이야기합니다. 따라서 객체 생성자는 객체가 유효한 상태로 초기화되고 제공된 인수가 객체를 유효한 것으로 만들지 않으면 오류 신호를 보냅니다. 객체가 유효한 상태로 초기화되었는지 확인하면 실제 작업처럼 들립니다. 그리고이 생각은 만 생성자를 통해 유효한 상태로 초기화를 허용하는 객체가있는 경우, 장점을 가지고 객체를 제대로 또한 검사 상태를 변경 각각의 방법이 뭔가 나쁜으로 상태를 변경하지 않는 정도로 상태 캡슐화 ... 그러면 본질적으로 그 객체는 "항상 유효"하다는 것을 보장합니다. 정말 좋은 재산입니다!

따라서 문제를 해결하기 위해 모든 것을 작은 조각으로 나누려고 할 때 문제가 발생합니다. 객체가 실제로 제대로 캡슐화되어 있으면 실제로 거기에 들어가서 FooBarService를 모의 FooBarService로 바꿀 수 없으며 테스트에 맞게 값을 변경할 수는 없습니다 (또는, 간단한 할당보다 더 많은 코드).

따라서 우리는 다른 "생각 학교"인 SOLID를 얻습니다. 그리고이 생각의 학교에서 우리가 생성자에서 실제 작업을 수행해서는 안되는 것이 훨씬 더 사실입니다. SOLID 코드는 종종 (항상 그런 것은 아님) 테스트하기가 더 쉽습니다. 그러나 추론하기가 더 어려울 수도 있습니다. 우리는 단일 책임으로 코드를 작은 객체로 분리하므로 대부분의 객체는 더 이상 상태를 캡슐화하지 않으며 일반적으로 상태 또는 동작을 포함합니다. 유효성 검사 코드는 일반적으로 유효성 검사기 클래스로 추출되어 상태와 별도로 유지됩니다. 그러나 이제 우리는 응집력을 잃어 버렸습니다. 더 이상 우리가 얻을 수 있고 완전히 될 때 객체가 유효한지 더 이상 확신 할 수 없습니다객체에 대해 무언가를 시도하기 전에 객체에 대해 가지고 있다고 생각하는 전제 조건이 항상 맞는지 항상 확인해야합니다. (물론, 일반적으로 한 계층에서 유효성 검사를 한 다음 객체가 하위 계층에서 유효하다고 가정합니다.) 그러나 테스트하기가 더 쉽습니다!

누가 맞습니까?

아무도 없습니다. 두 생각 학교 모두 장점이 있습니다. 현재 SOLID는 모든 분노이며 SRP와 Open / Closed 그리고 모든 재즈에 대해 이야기하고 있습니다. 그러나 인기가 있다고해서 모든 애플리케이션에 적합한 디자인을 선택한다는 의미는 아닙니다. 따라서 다릅니다. SOLID 원칙을 많이 따르는 코드 기반에서 작업하는 경우 생성자에서의 실제 작업은 좋지 않은 생각 일 수 있습니다. 그러나 그렇지 않으면 상황을보고 판단을 사용하십시오. 개체의 어떤 속성을 않는 이득 생성자에서 일을에서, 어떤 속성은 않습니다 분실 ? 애플리케이션의 전체 아키텍처와 얼마나 잘 맞습니까?

생성자의 실제 작업은 반 패턴이 아니며, 올바른 위치에서 사용하면 상당히 반대가 될 수 있습니다. 그러나 (있을 경우 예외가 발생할 수있는 경우와 함께) 명확하게 문서화해야하며 디자인 결정과 마찬가지로 현재 코드베이스에서 사용되는 일반적인 스타일에 맞아야합니다.


이것은 환상적인 답변입니다.
jrahhali

0

이 규칙에는 근본적인 문제가 있으며 이것이 바로 "실제 작업"을 구성하는 것입니까?

당신은에서 볼 수있는 원래의 기사 저자의 시도는 "진짜 일"이 무엇인지 정의하는 문제에 게시,하지만 심각한 결함이있다. 좋은 연습을 위해서는 잘 정의 된 원칙이어야합니다. 즉, 소프트웨어 엔지니어링과 관련하여 아이디어는 이식 가능해야하고 (어떤 언어에 상관없이) 테스트되고 입증되어야합니다. 이 기사에서 주장되는 대부분은 첫 번째 기준에 맞지 않습니다. 다음은 저자가이 기사에서 "실제 작업"을 구성하는 요소와 이들이 왜 나쁜 정의가 아닌지에 대해 언급 한 지표입니다.

의 사용 new키워드 . 이 정의는 도메인마다 다르기 때문에 근본적으로 결함이 있습니다. 일부 언어는 new키워드를 사용하지 않습니다 . 궁극적으로 그가 암시하는 것은 다른 객체를 구성해서는 안된다는 것입니다. 그러나 많은 언어에서 가장 기본적인 값조차도 그 자체가 객체입니다. 따라서 생성자에 할당 된 모든 값은 새 객체를 구성합니다. 따라서이 아이디어는 특정 언어로 제한되며 "실제 작업"을 구성하는 요소에 대한 나쁜 지표입니다.

생성자가 완료된 후 개체가 완전히 초기화되지 않았습니다 . 이것은 좋은 규칙이지만이 기사에서 언급 한 다른 규칙들과 모순됩니다. 그것이 어떻게 다른 사람들과 모순 될 수 있는지에 대한 좋은 예가 나를 여기로 데려온 질문에 언급되어 있습니다. 이 질문에서 누군가는 sort이 원칙으로 인해 JavaScript 인 것처럼 생성자에서 메소드를 사용하는 것에 대해 걱정 하고 있습니다. 이 예에서 사람은 다른 객체의 정렬 된 목록을 나타내는 객체를 만들고있었습니다. 토론을 위해 정렬되지 않은 객체 목록이 있고 정렬 된 목록을 나타내는 새로운 객체가 필요하다고 상상해보십시오. 소프트웨어의 일부가 정렬 된 목록을 기대하고이 객체를 호출 할 수 있기 때문에이 새로운 객체가 필요합니다SortedList. 이 새 객체는 정렬되지 않은 목록을 허용하며 결과 객체는 현재 정렬 된 객체 목록을 나타내야합니다. 우리가 그 문서에 언급 된 다른 규칙, 즉 정적 메소드 호출, 제어 흐름 구조, 할당 이외의 규칙을 따라야한다면 결과 객체는 유효한 상태로 구성되지 않고 다른 규칙은 완전히 초기화됩니다. 생성자가 완료된 후 이 문제를 해결하려면 정렬되지 않은 목록을 생성자에서 정렬하려면 몇 가지 기본 작업을 수행해야합니다. 이렇게하면 다른 3 가지 규칙이 깨져서 다른 규칙은 관련이 없습니다.

궁극적으로 생성자에서 "실제 작업"을하지 않는이 규칙은 잘못 정의되고 결함이 있습니다. "실제 작업"이 무엇인지 정의하려고 노력하는 것은 무익한 운동이다. 이 기사에서 가장 좋은 규칙은 생성자가 완료되면 완전히 초기화해야한다는 것입니다. 생성자에서 수행되는 작업량을 제한하는 다른 모범 사례가 많이 있습니다. 이들 대부분은 SOLID 원칙으로 요약 될 수 있으며, 동일한 원칙으로 인해 생성자에서 작업을 수행 할 수 있습니다.

추신. 나는 여기서 생성자에서 어떤 일을하는 데 아무런 문제가 없다고 주장하지만, 많은 일을하는 곳이 아니라고 말할 의무가 있습니다. SRP는 생성자가 유효하도록 충분한 작업을 수행해야한다고 제안합니다. 생성자가 너무 많은 코드 줄을 가지고 있다면 (이것은 매우 주관적입니다) 아마도이 원칙을 위반하는 것일 수 있으며 더 작은 정의 된 메소드와 객체로 나눌 수 있습니다.

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