생성자가 얼마나 복잡한가


18

생성자와 함께 할 수있는 작업량에 대해 동료와 논의하고 있습니다. 내부에 다른 개체 A가 필요한 클래스 B가 있습니다. 개체 A는 클래스 B가 작업을 수행해야하는 몇 가지 구성원 중 하나입니다. 모든 공용 메소드는 내부 객체 A에 따라 다릅니다. 객체 A에 대한 정보는 DB에 저장되므로 생성자의 DB에서 조회하여 확인하고 가져 오려고합니다. 내 동료는 생성자가 생성자 매개 변수를 캡처하는 것 외에 많은 작업을 수행해서는 안된다고 지적했습니다. 생성자에 대한 입력을 사용하여 객체 A를 찾지 못하면 모든 공용 메소드가 실패하기 때문에 인스턴스를 작성하고 나중에 실패하는 대신 실제로 생성자에서 조기에 처리하는 것이 좋습니다.

다른 사람들은 어떻게 생각합니까? 차이가 있다면 C #을 사용하고 있습니다.

읽기 생성자에서 모든 객체의 작업을 수행 할 이유가 있습니까? DB로 이동하여 객체 A를 가져 오는 것이 "객체를 사용할 준비가되도록하는 데 필요한 다른 초기화"의 일부인지 궁금합니다. 사용자가 생성자에게 잘못된 값을 전달하면 공용 메소드를 사용할 수 없기 때문입니다.

생성자는 객체의 필드를 인스턴스화하고 객체를 사용할 준비를하는 데 필요한 다른 초기화를 수행해야합니다. 이것은 일반적으로 생성자가 작다는 것을 의미하지만 상당한 양의 작업이 필요한 시나리오가 있습니다.


8
동의하지 않습니다. 의존성 역전 원리를 살펴보면, 객체 A가 생성자에서 유효한 상태로 객체 B에 전달되는 것이 좋습니다.
Jimmy Hoffa

5
얼마나 수 있는지 묻고 있습니까? 얼마나 해야 합니까? 아니면 특정 상황에서 얼마나 해야합니까? 세 가지 매우 다른 질문입니다.
Bobson

1
나는 의존성 주입 (또는 "의존성 반전")에 대한 Jimmy Hoffa의 제안에 동의한다. 설명을 읽는 것에 대한 나의 첫 생각이었습니다. 이미 클래스 B에서 사용중인 클래스 A로 분리 된 기능이 있습니다. 나는 당신의 경우에 말할 수는 없지만 일반적으로 의존성 주입 패턴을 사용하는 리팩토링 코드는 클래스를 더 간단하고 덜 얽히게합니다.
윌리 박사의 견습생

1
Bobson, 제목을 'should'로 변경했습니다. 여기에 모범 사례를 요구하고 있습니다.
김태인

1
@JimmyHoffa : 아마도 당신은 당신의 코멘트를 답으로 확장해야 할 것입니다.
케빈 클라인

답변:


22

귀하의 질문은 완전히 별개의 두 부분으로 구성됩니다.

생성자에서 예외를 throw해야합니까, 아니면 메소드가 실패해야합니까?

이것은 Fail-fast 원칙을 분명히 적용한 것입니다 . 메소드 실패 이유를 찾는 것보다 생성자 실패를 디버그하는 것이 훨씬 쉽습니다. 예를 들어, 코드의 다른 부분에서 인스턴스가 이미 생성되어 메소드를 호출 할 때 오류가 발생할 수 있습니다. 객체가 잘못 생성 된 것이 분명합니까? 아니.

"시도 / 캐치로 통화 랩핑"문제에 관해서는. 예외는 예외적 입니다. 일부 코드에서 예외가 발생한다는 것을 알고 있으면 try / catch로 코드를 래핑하지 않지만 예외를 발생시킬 수있는 코드를 실행하기 전에 해당 코드의 매개 변수를 확인하십시오. 예외는 시스템이 유효하지 않은 상태가되지 않도록하기위한 것입니다. 일부 입력 매개 변수가 유효하지 않은 상태가 될 수 있음을 알고 있으면 해당 매개 변수가 발생하지 않도록하십시오. 이런 식으로, 시스템의 경계에있는 예외를 논리적으로 처리 할 수있는 장소에서만 시도 / 잡기를 수행해야합니다.

생성자에서 "DB와 같은 시스템의 다른 부분"에 액세스 할 수 있습니까?

나는 이것이 가장 놀랍지 않은 원칙에 위배 된다고 생각한다 . 생성자가 DB에 액세스 할 것으로 기대하는 사람은 많지 않습니다. 아니, 그렇게해서는 안됩니다.


2
이에 대한 더 적절한 해결책은 일반적으로 구성이 자유로운 "개방형"방법을 추가하는 것이지만 실제 열기가 모두 발생하는 "개방"/ "연결"/ etc 이전에는 사용할 수 없습니다. 생성자가 실패하면 나중에 유용한 기능인 객체의 부분 구성을 원할 때 모든 종류의 문제가 발생할 수 있습니다.
Jimmy Hoffa

@JimmyHoffa 생성자 메소드와 생성을위한 특정 메소드 사이에는 차이가 없습니다.
Euphoric

4
그렇지는 않지만 많은 사람들이 할 수 있습니다. 연결 객체를 만들 때 생각하면 생성자가 연결합니까? 객체가 연결되어 있지 않으면 사용할 수 있습니까? 두 질문 모두 대답은 '아니요'입니다. 연결 개체를 부분적으로 구성하면 연결을 열기 전에 설정과 내용을 조정할 수 있으므로 도움이됩니다. 이 시나리오에 대한 일반적인 접근 방식입니다. 나는 여전히 객체 생성에 객체 의존성이 있지만 오픈 메소드가 위험한 작업을 수행하는 것보다 오픈 메소드가 더 나은 시나리오에 생성자 주입이 올바른 접근법이라고 생각합니다.
Jimmy Hoffa

@JimmyHoffa 그러나 이것은 OOP 디자인의 일반적인 규칙이 아니라 연결 클래스의 특정 요구 사항입니다. 나는 실제로 규칙보다 예외적 인 경우라고 부릅니다. 기본적으로 빌더 패턴에 대해 이야기하고 있습니다.
Euphoric

2
요구 사항이 아니라 디자인 결정이었습니다. 생성자가 연결 문자열을 가져 와서 생성 즉시 연결하도록 만들 수 있습니다. 그것들은 객체를 생성하는데 도움이되기 때문에 그렇게 결정하지 않았다. 그런 다음 연결 문자열이나 다른 비트와 요철을 알아 내고 나중에 연결한다 .
Jimmy Hoffa

19

어.

생성자는 가능한 한 적은 작업을 수행해야합니다. 문제는 생성자의 예외 동작이 어색하다는 것입니다. 적절한 동작이 무엇인지 (상속 시나리오 포함) 알지 못하는 프로그래머는 거의 없으며, 사용자가 모든 단일 인스턴스화를 시도 / 잡으라고 강요하는 것은 최선의 방법입니다.

이이 두 부분으로,의 생성자 및 사용하여 생성자를. 예외가 발생하면 할 수 없기 때문에 생성자가 어색합니다. 다른 유형을 반환 할 수 없습니다. null을 반환 할 수 없습니다. 기본적으로 예외를 다시 던지거나 깨진 객체를 반환하거나 (불량한 구성자) 또는 (가장 좋은 경우) 일부 내부 부품을 적절한 기본값으로 교체 할 수 있습니다. 생성자를 사용 하는 것은 어색합니다. 따라서 인스턴스화 및 모든 파생 유형의 인스턴스화가 발생할 수 있기 때문입니다. 그런 다음 멤버 이니셜 라이저에서 사용할 수 없습니다. 어디에서나 try / catch로 인한 가독성 (및 취약성)을 넘어서 "제작이 성공 했습니까?"라는 논리에 대한 예외를 사용하게됩니다.

생성자가 사소하지 않은 작업이나 합리적으로 실패 할 수있는 작업을 수행하는 경우 Create대신 정적 메서드 (공개되지 않은 생성자 사용)를 고려하십시오. 훨씬 더 유용하고 디버깅하기 쉬우 며 성공할 때 완전히 구성된 인스턴스를 얻을 수 있습니다.


2
복잡한 구성 시나리오는 여기에서 언급 한 것과 같은 생성 패턴이 존재하는 이유입니다. 이것은 이러한 문제가있는 구성에 대한 좋은 접근 방법입니다. .NET에서 Connection객체가 어떻게 CreateCommand당신을 위해 명령을 올바르게 연결했는지 알 수 있도록 생각해야합니다.
Jimmy Hoffa

2
여기에 데이터베이스가 외부 의존성이 높다고 덧붙일 것입니다.이 시점에서 데이터베이스를 전환하거나 테스트를 위해 객체를 조롱하고 이러한 종류의 코드를 Factory 클래스 (또는 IService와 같은 것)로 옮기고 싶다고 가정 해보십시오. 제공자)보다 깨끗한 접근 방식으로 보입니다
Jason Sperske

4
"생성자의 예외 동작이 어색하다"고 설명 할 수 있습니까? Create를 사용한다면 생성자를 호출하는 것처럼 catch를 시도 할 필요가 없습니까? Create는 생성자의 이름과 다른 느낌입니다. 어쩌면 나는 당신의 대답이 옳지 않을까요?
김태인

1
생성자에서 발생하는 예외를 포착하려고 시도했지만 이에 대한 인수에 +1했습니다. "생성자에서 작업하지 않음"이라고 말하는 사람들과 함께 작업 한 결과 이상한 패턴을 만들 수도 있습니다. 모든 객체에 생성자뿐만 아니라 Initialize () 형식이있는 코드를 보았습니다. 어떻게 추측합니까? 객체는 호출되기 전까지는 실제로 유효하지 않습니다. 그리고 ctor와 Initialize ()라는 두 가지를 호출해야합니다. 개체 설정 작업이 사라지지 않습니다. C #은 C ++과 다른 솔루션을 제공 할 수 있습니다. 공장은 좋다!
J Trana

1
@ jtrana-예, 초기화는 좋지 않습니다. 생성자는 정상적인 기본값 또는 팩토리로 초기화하거나 create 메소드가 빌드하고 사용 가능한 인스턴스를 리턴합니다.
Telastyn

1

생성자-메소드 복잡성 균형은 실제로 좋은 스타일에 대한 논의의 문제입니다. 더 복잡한 것들을위한 Class() {}방법과 함께 빈 생성자 가 종종 사용됩니다 Init(..) {..}. 고려해야 할 주장 중 :

  • 클래스 직렬화 가능성
  • 생성자와 분리 Init 메소드를 사용하면 Class x = new Class(); x.Init(..);단위 테스트에서 부분적으로 동일한 시퀀스를 여러 번 반복해야하는 경우가 종종 있습니다 . 그러나 읽기에는 명확하지 않습니다. 때로는 코드 한 줄에 넣었습니다.
  • 단위 테스트 목표가 클래스 초기화 테스트 인 경우, 중간 어설트를 사용하여 더 복잡한 작업을 하나씩 수행하기 위해 Init를 소유하는 것이 좋습니다.

내 의견으로는이 init방법 의 장점 은 실패 처리가 훨씬 쉽다는 것입니다. 특히, init메소드 의 리턴 값은 초기화가 성공적인지 여부를 표시 할 수 있으며 메소드는 오브젝트가 항상 양호한 상태임을 보장 할 수 있습니다.
pqnet
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.