생성자는 일반적으로 메소드를 호출하지 않아야합니다


12

나는 왜 메소드를 호출하는 생성자가 반 패턴이 될 수 있는지 동료에게 설명했다.

예 (내 녹슨 C ++에서)

class C {
public :
    C(int foo);
    void setFoo(int foo);
private:
    int foo;
}

C::C(int foo) {
    setFoo(foo);
}

void C::setFoo(int foo) {
    this->foo = foo
}

추가 기여를 통해이 사실을 더 잘 동기를 부여하고 싶습니다. 예, 서적 참조, 블로그 페이지 또는 원칙 이름이 있으면 매우 환영받을 것입니다.

편집 : 나는 일반적으로 이야기하고 있지만 파이썬으로 코딩하고 있습니다.


이것이 일반적인 규칙입니까 아니면 특정 언어에만 해당됩니까?
ChrisF

어느 언어? C ++에서 그것은 반 패턴 이상입니다 : parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.5
LennyProgrammers

OP는 @ Lenny222에서 "인스턴스 메소드"에 대해 이야기하는데, 이는 적어도 인스턴스가 아닌 메소드를 의미 합니다 . 따라서 가상이 될 수 없습니다.
Péter Török

3
@Alb Java에서는 완벽하게 괜찮습니다. 그래도 안되는 것은 this생성자에서 호출하는 모든 메소드에 명시 적으로 전달 하는 것입니다.
biziclop

3
@ 스테파노 보리 니 : 파이썬으로 코딩하는 경우 녹슨 C ++ 대신 파이썬으로 예제를 보여주지 않겠습니까? 또한 이것이 왜 나쁜지 설명하십시오. 우리는 항상 그렇게합니다.
S.Lott

답변:


26

언어를 지정하지 않았습니다.

C ++에서 생성자는 가상 함수를 호출 할 때 호출하는 실제 함수가 클래스 구현이라는 점에서주의해야합니다. 구현이없는 순수한 가상 방법 인 경우 액세스 위반이됩니다.

생성자는 비가 상 함수를 호출 할 수 있습니다.

언어가 기본적으로 함수가 일반적으로 가상 인 언어 인 경우, 각별히주의해야합니다.

C #은 상황을 예상대로 처리하는 것처럼 보입니다. 생성자에서 가상 메서드를 호출 할 수 있으며 가장 최종 버전을 호출합니다. 따라서 C #에서는 안티 패턴이 아닙니다.

생성자에서 메소드를 호출하는 일반적인 이유는 공통 "init"메소드를 호출하려는 여러 생성자가 있기 때문입니다.

소멸자는 가상 메소드와 동일한 문제가 있으므로 소멸자 외부에 있고 기본 클래스 소멸자가 호출 할 것으로 예상되는 가상 "정리"메소드를 가질 수 없습니다.

Java와 C #에는 소멸자가 없으며 종료자가 있습니다. Java의 동작을 모르겠습니다.

C #은 이와 관련하여 정리를 올바르게 처리하는 것으로 보입니다.

Java 및 C #에는 가비지 수집이 있지만 메모리 할당 만 관리합니다. 소멸자가 수행해야하는 다른 정리는 메모리를 해제하지 않습니다.


13
여기에 몇 가지 작은 오류가 있습니다. C #의 메소드는 기본적으로 가상이 아닙니다. C #은 생성자에서 가상 메소드를 호출 할 때 C ++과 다른 의미를 갖습니다. 가장 많이 파생 된 유형의 가상 메소드가 호출되며 현재 구성중인 유형의 일부에 대한 가상 메소드가 호출되지 않습니다. C #에서는 종료 방법을 "소멸자"라고하지만 최종 자 의미 체계가있는 것은 옳습니다. C # 소멸자에서 호출 된 가상 메소드는 생성자에서와 동일한 방식으로 작동합니다. 가장 파생 된 방법이 호출됩니다.
Eric Lippert

@ 피터 : 인스턴스 메소드를 의도했습니다. 혼란을 드려 죄송합니다.
스테파노 보리 니

1
@ 에릭 리퍼 트. C #에 대한 귀하의 전문 지식에 감사 드리며 이에 따라 답변을 편집했습니다. 나는 그 언어를 알 수 없으며 C ++을 잘 알고 Java를 잘 모른다.
CashCow

5
천만에요. C #의 기본 클래스 생성자에서 가상 메서드를 호출하는 것은 여전히 ​​나쁜 생각입니다.
Eric Lippert

생성자에서 Java로 (가상) 메소드를 호출하면 항상 가장 파생 된 대체를 호출합니다. 그러나“네가 기대하는 방식”이라고 부르는 것은 내가 혼란스럽게 부르는 것입니다. Java는 가장 파생 된 재정의를 호출하지만 해당 메서드는 파일 화 된 이니셜 라이저 만 처리되지만 자체 클래스 실행의 생성자는 볼 수 없습니다. 아직 고정되지 않은 클래스에서 메소드를 호출하면 위험 할 수 있습니다. C ++이 더 나은 선택이라고 생각합니다.
5gon12eder

18

이제 클래스 메소드인스턴스 메소드 에 대한 혼란 이 해결 되었으므로 대답을 줄 수 있습니다 :-)

문제는 일반적으로 생성자에서 인스턴스 메소드를 호출하는 것이 아닙니다. 가상 메서드를 직접 또는 간접적으로 호출하는 것입니다 . 그리고 주된 이유는 생성자 내부에서 객체가 아직 완전히 생성되지 않았기 때문 입니다. 특히 하위 클래스 부분은 기본 클래스 생성자가 실행되는 동안 전혀 구성되지 않습니다. 따라서 내부 상태가 언어 의존적 인 방식으로 일치하지 않아서 언어마다 다른 미묘한 버그가 발생할 수 있습니다.

C ++과 C #은 이미 다른 사람들에 의해 논의되었습니다. Java에서는 가장 파생 된 유형의 가상 메소드가 호출되지만 해당 유형은 아직 초기화되지 않았습니다. 따라서 해당 메소드가 파생 된 유형의 필드를 사용하는 경우 해당 시점에서 해당 필드가 아직 제대로 초기화되지 않을 수 있습니다. 이 문제는 Effecive Java 2 판 , 항목 17 : 상속을위한 디자인 및 문서 에서 자세히 다루 거나 금지합니다 .

이것은 객체 참조를 조기게시 하는 일반적인 문제의 특별한 경우입니다 . 인스턴스 메서드에는 암시 적 this매개 변수가 있지만 this메서드에 명시 적으로 전달 하면 비슷한 문제가 발생할 수 있습니다. 특히 객체 참조가 다른 스레드에 조기에 게시되는 동시 프로그램에서 첫 번째 스레드의 생성자가 완료되기 전에 해당 스레드가 이미 해당 메서드를 호출 할 수 있습니다.


3
(+1) "생성자 안에는 개체가 아직 완전히 구성되지 않았습니다." "클래스 메소드 대 인스턴스"와 동일합니다. 일부 프로그래밍 언어는 생성자가 값을 생성자에 할당하는 것처럼 프로그래머가 입력자를 생성 할 때 생성 된 것으로 간주합니다.
umlcat

7

여기에서 메소드 호출은 그 자체로 반 패턴, 더 코드 냄새라고 생각하지 않습니다. 클래스가 reset메소드를 제공 하면 객체를 원래 상태로 반환 reset()하면 생성자 를 호출 하는 것은 DRY입니다. (재설정 방법에 대해서는 언급하지 않습니다).

다음은 권위에 대한 귀하의 이의 제기를 충족시키는 데 도움이되는 기사입니다. http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work/

실제로 메서드를 호출하는 것이 아니라 너무 많은 생성자에 관한 것입니다. IMHO, 생성자에서 메서드를 호출하면 생성자가 너무 무거울 수 있습니다.

이것은 코드를 테스트하는 것이 얼마나 쉬운 지와 관련이 있습니다. 이유는 다음과 같습니다.

  1. 단위 테스트에는 많은 생성과 파괴가 포함되므로 시공이 빨라야합니다.

  2. 이러한 메소드의 기능에 따라 생성자에서 설정 한 (잠재적으로 테스트 할 수없는) 전제 조건 (예 : 네트워크에서 정보 가져 오기)에 의존하지 않고 개별 코드 단위를 테스트하기가 어려울 수 있습니다.


3

철학적으로 생성자의 목적은 원시 메모리 덩어리를 인스턴스로 만드는 것입니다. 생성자가 실행되는 동안 개체가 아직 존재하지 않으므로 메서드를 호출하는 것은 좋지 않습니다. 당신은 그들이 내부적으로 무엇을하는지 모를 수도 있고, 그들이 부름을 받았을 때 적어도 그 존재가 존재하는 것으로 정당하게 고려할 수도 있습니다.

기술적으로는 C ++, 특히 Python에서는 아무런 문제가 없을 수 있으므로주의해야합니다.

실제로 클래스 멤버를 초기화하는 메소드로만 호출을 제한해야합니다.


2

일반적인 문제는 아닙니다. C ++에서는 특히 상속 및 가상 메소드를 사용할 때 문제가 발생합니다. 오브젝트 구성이 거꾸로 발생하고 상속 계층의 각 생성자 계층으로 vtable 포인터가 재설정되므로 가상 메소드를 호출하는 경우 실제로 만들려는 클래스에 해당하는 클래스를 얻으면 가상 메소드 사용의 모든 목적을 상실합니다.

정상적인 OOP를 지원하는 언어에서 처음부터 vtable 포인터를 올바르게 설정하면이 문제가 존재하지 않습니다.


2

메소드 호출에는 두 가지 문제가 있습니다.

  • 예상치 못한 (C ++) 작업을 수행하거나 아직 초기화되지 않은 객체의 일부를 사용할 수있는 가상 메소드 호출
  • 객체가 아직 완성되지 않았기 때문에 (불변 값을 보유하지 않을 수 있음) 공용 메소드 호출 (클래스 불변량을 강제해야 함)

이전 두 경우에 해당하지 않는 한 도우미 함수를 호출해도 아무런 문제가 없습니다.


1

나는 이것을 사지 않습니다. 객체 지향 시스템에서 메소드 호출은 당신이 할 수있는 유일한 것입니다. 사실, 그것은 "객체 지향" 의 정의 입니다. 따라서 생성자가 메서드를 호출 할 없다면 어떻게 할 수 있습니까?


개체를 초기화하십시오.
스테파노 보리 니

@ 스테파노 보리 니 : 어떻게? 객체 지향 시스템에서 할 수있는 유일한 것은 메소드 호출입니다. 또는 반대 각도에서 그것을 보아라 : 무엇이든 메소드를 호출하여 수행됩니다. 그리고 "anything"은 분명히 객체 초기화를 포함합니다. 따라서 객체를 초기화하기 위해 메소드를 호출해야하지만 생성자가 메소드를 호출 할 수 없다면 생성자는 어떻게 객체를 초기화 할 수 있습니까?
Jörg W Mittag

당신이 할 수있는 유일한 방법이 메소드를 호출하는 것은 사실이 아닙니다. 아무 것도 호출하지 않고 객체 내부로 직접 상태를 초기화 할 수 있습니다. 생성자의 요점은 객체를 일관된 상태로 만드는 것입니다. 당신이 다른 방법을 호출하는 경우 그들은 방법이되지 않는 한, 이러한 부분적인 상태에서 개체를 처리하는 문제가있을 수 있습니다 구체적으로 (일반적으로 도우미 메서드)를 생성자에서 호출 할 만든
스테파노 Borini

@Stefano Borini : "객체 내부로 직접 호출하지 않고 상태를 초기화 할 수 있습니다." 슬프게도, 그것이 방법과 관련 될 때, 당신은 무엇을합니까? 코드를 복사하여 붙여 넣으시겠습니까?
S.Lott

1
@ S.Lott : 아니오, 그것을 호출하지만 객체 메소드 대신 모듈 함수를 유지하고 생성자에서 객체 상태에 넣을 수있는 반환 데이터를 제공하려고합니다. 실제로 객체 메소드가 있어야하는 경우 비공개로 설정하고 올바른 이름을 지정하는 등 초기화를 위해 명확하게 설명합니다. 그러나 생성자에서 객체 상태를 설정하기 위해 공개 메소드를 호출하지는 않습니다.
스테파노 보리 니

0

OOP 이론에서는 중요하지 않지만 실제로 각 OOP 프로그래밍 언어는 생성자를 다르게 처리합니다 . 정적 메서드를 자주 사용하지 않습니다.

C ++ & Delphi에서 일부 속성 ( "필드 멤버")에 초기 값을 제공해야하고 코드가 매우 확장 된 경우 생성자의 확장으로 보조 메서드를 추가합니다.

더 복잡한 작업을 수행하는 다른 메소드를 호출하지 마십시오.

"getters"& "setters"메소드의 경우 일반적으로 개인 / 보호 된 변수를 사용하여 상태를 저장하고 "getters"및 "setters"메소드를 사용합니다.

생성자에서 나는 속성 상태 필드에 "기본"값을 할당 없이 은 "접근"을 호출.

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