일반적인 질문이지만 C ++과 같은 언어에는 생성자 실행, 메모리 관리, 정의되지 않은 동작 등과 관련하여 다른 의미가 있다는 것을 알고 있으므로 내 범위는 C #입니다.
누군가 나에게 쉽게 대답하지 못하는 흥미로운 질문을했다.
클래스의 생성자가 결코 끝나지 않는 루프 (예 : 게임 루프)를 시작하게하는 나쁜 디자인으로 간주되는 이유는 무엇입니까?
이것에 의해 깨지는 몇 가지 개념이 있습니다.
- 최소 놀랍게도의 원리와 같이, 사용자는 생성자가 이런 식으로 행동 할 것을 기대하지 않습니다.
- 루프를 종료하지 않으므로이 클래스를 만들거나 주입 할 수 없으므로 단위 테스트가 더 어렵습니다.
- 그런 다음 루프의 끝 (게임 끝)은 개념적으로 생성자가 끝나는 시간이며 홀수입니다.
- 기술적으로 이러한 클래스에는 생성자를 제외한 공용 멤버가 없으므로 이해하기가 어렵습니다 (특히 구현할 수없는 언어의 경우)
그리고 기술적 인 문제가 있습니다 :
- 생성자는 실제로 완료되지 않으므로 GC는 어떻게됩니까? 이 개체가 이미 Gen 0에 있습니까?
- 이러한 클래스에서 파생하는 것은 기본 생성자가 절대로 반환하지 않기 때문에 불가능하거나 최소한 매우 복잡합니다.
그러한 접근 방식으로 더 분명하거나 나쁜 것이 있습니까?
답변
생성자의 목적은 무엇입니까? 새로 생성 된 객체를 반환합니다. 무한 루프는 무엇을합니까? 절대 반환하지 않습니다. 생성자가 전혀 생성하지 않으면 새로 생성 된 객체를 어떻게 반환 할 수 있습니까? 할 수 없습니다.
Ergo, 무한 루프는 생성자의 기본 계약을 위반합니다.
답변
그러한 접근 방식으로 더 분명하거나 나쁜 것이 있습니까?
물론입니다. 불필요하고, 예상치 못한, 쓸모없고, 우아하지 않습니다. 그것은 클래스 디자인 (응집력, 결합)의 현대 개념을 위반합니다. 메소드 계약을 위반합니다 (생성자가 정의 된 작업을 가지고 있으며 임의의 메소드가 아닙니다). 확실히 유지 보수가 쉽지는 않지만 미래의 프로그래머는 무슨 일이 일어나고 있는지 이해하고 그 이유를 추측하려고 많은 시간을 할애합니다.
코드가 작동하지 않는다는 점에서 이것은 “버그”가 아닙니다. 그러나 코드를 유지하기 어렵게 (즉, 테스트를 추가하기 어렵고, 재사용하기 어렵고, 디버깅하기 어렵고, 확장하기 어렵게하여) 장기적으로 막대한 2 차 비용 (코드를 처음 작성하는 비용과 관련하여)이 발생할 수 있습니다. ).
소프트웨어 개발 방법의 많은 / 가장 현대적인 개선은 소프트웨어의 실제 작성 / 테스트 / 디버깅 / 유지 보수 프로세스를보다 쉽게하기 위해 수행됩니다. 이 모든 것은 “작동”하기 때문에 코드가 무작위로 배치되는 다음과 같은 것들로 우회됩니다.
불행히도, 당신은 정기적 으로이 모든 것을 완전히 모르는 프로그래머를 만날 것입니다. 작동합니다.
유추로 마무리하려면 (다른 프로그래밍 언어, 여기서 당면한 문제는 계산하는 것입니다 2+2
) :
$sum_a = `bash -c "echo $((2+2)) 2>/dev/null"`; # calculate
chomp $sum_a; # remove trailing \n
$sum_a = $sum_a + 0; # force it to be a number in case some non-digit characters managed to sneak in
$sum_b = 2+2;
첫 번째 접근 방식에 어떤 문제가 있습니까? 상당히 짧은 시간이 지나면 4를 반환합니다. 맞습니다. 반대 의견 (개발자가 반박 할 수있는 모든 일반적인 이유와 함께)은 다음과 같습니다.
- 읽기가 어렵습니다 (그러나 좋은 프로그래머는 쉽게 읽을 수 있으며, 항상 이렇게 읽었습니다. 원한다면 메소드로 리팩터링 할 수 있습니다!)
- 느리게 (그러나 이것은 중요한 시점에 있지 않으므로 무시할 수 있으며, 필요할 때만 최적화하는 것이 가장 좋습니다!)
- 훨씬 더 많은 리소스를 사용합니다 (새로운 프로세스, 더 많은 RAM 등-그러나 서버는 충분히 빠릅니다)
- 에 의존성을 소개합니다
bash
(그러나 어쨌든 Windows, Mac 또는 Android에서는 실행되지 않습니다) - 그리고 위에서 언급 한 다른 모든 이유들.
답변
당신은 당신의 질문에이 접근법을 배제하기에 충분한 이유를 제시하지만 여기서 실제적인 질문은 “이러한 접근 방식에 더 분명하거나 나쁜 것이 있습니까?”입니다.
내 첫 번째는 이것이 무의미하다는 것입니다. 생성자가 완료되지 않으면 프로그램의 다른 부분에서는 생성 된 객체에 대한 참조를 얻을 수 없으므로 일반 메소드 대신 생성자에 배치하는 논리는 무엇입니까? 그런 다음 유일한 차이점은 루프에서 루프에서 부분적으로 구성된 this
이스케이프에 대한 참조를 허용 할 수 있다는 것 입니다. 이것이이 상황에만 국한되는 것은 아니지만, this
탈출을 허용 하면 해당 참조는 항상 완전히 구성되지 않은 객체를 가리킬 것입니다.
이런 종류의 상황에 대한 의미가 C #에서 잘 정의되어 있는지 여부는 알 수 없지만 대부분의 개발자가 탐구하고자하는 것이 아니기 때문에 중요하지 않다고 주장합니다.
답변
+1 놀랍게도, 이것은 새로운 개발자들에게 표현하기 어려운 개념입니다. 실용적인 수준에서는 생성자 내에서 발생한 예외를 디버깅하기가 어렵습니다. 개체가 초기화되지 않으면 해당 생성자 외부의 상태를 검사하거나 로그에서 기록 할 수 없습니다.
이런 종류의 코드 패턴이 필요하다고 생각되면 대신 클래스에서 정적 메소드를 사용하십시오.
클래스 정의에서 객체를 인스턴스화 할 때 초기화 로직을 제공하기 위해 생성자가 존재합니다. 이 개체 인스턴스는 나머지 응용 프로그램 논리에서 호출하려는 속성 및 기능 집합을 캡슐화하는 컨테이너로 구성되므로이 개체 인스턴스를 생성합니다.
“구축하고있는”객체를 사용하지 않으려면 먼저 객체를 인스턴스화하는 시점은 무엇입니까? 생성자에 while (true) 루프가 있다는 것은 효과적으로 완료 할 의도가 없다는 것을 의미합니다.
C #은 다양한 구조와 패러다임을 가진 매우 풍부한 객체 지향 언어로 도구를 탐색하고 사용하는 방법과 사용시기를 알 수 있습니다.
C #에서는 생성자 내에서 확장되거나 끝없는 논리를 실행하지 마십시오. 왜냐하면 더 나은 대안이 있기 때문입니다.
답변
본질적으로 나쁜 것은 없습니다. 그러나 중요한 것은이 구문을 사용하기로 선택한 이유에 대한 질문입니다. 이렇게함으로써 무엇을 얻었습니까?
먼저 while(true)
생성자에서의 사용 에 대해서는 이야기하지 않겠습니다 . 없다 절대적으로 생성자에서이 구문을 사용하여 잘못된 것은. 그러나 “생성자에서 게임 루프 시작”이라는 개념은 몇 가지 문제를 일으킬 수 있습니다.
이러한 상황에서 생성자가 단순히 객체를 구성하고 무한 루프를 호출하는 함수를 갖는 것이 매우 일반적입니다. 이렇게하면 코드 사용자에게 유연성이 향상됩니다. 표시 될 수있는 문제의 예로는 객체 사용을 제한하는 것입니다. 누군가 클래스에서 “게임 오브젝트”인 멤버 오브젝트를 가지려면 오브젝트를 구성해야하기 때문에 생성자에서 전체 게임 루프를 실행할 준비가되어 있어야합니다. 이 문제를 해결하기 위해 고문 코딩이 발생할 수 있습니다. 일반적인 경우 게임 개체를 미리 할당 한 다음 나중에 실행할 수 없습니다. 문제가되지 않는 일부 프로그램의 경우 모든 것을 미리 할당 한 다음 게임 루프를 호출 할 수있는 프로그램 클래스가 있습니다.
프로그래밍의 경험 법칙 중 하나는 작업에 가장 간단한 도구를 사용하는 것입니다. 어렵고 빠른 규칙은 아니지만 매우 유용한 규칙입니다. 함수 호출이 충분하다면 왜 클래스 객체를 생성해야합니까? 더 강력하고 복잡한 도구가 사용되면 개발자가 그럴만한 이유가 있다고 가정하고 어떤 종류의 이상한 트릭을하는지 탐구 할 것입니다.
이것이 합리적인 경우가 있습니다. 생성자에 게임 루프가있는 것이 직관적 인 경우가 있습니다. 그런 경우, 당신은 그것으로 실행! 예를 들어 게임 루프는 더 큰 프로그램에 내장되어 일부 데이터를 구현하는 클래스를 구성 할 수 있습니다. 해당 데이터를 생성하기 위해 게임 루프를 실행해야하는 경우 생성자에서 게임 루프를 실행하는 것이 매우 합리적 일 수 있습니다. 이 프로그램을 특별한 경우로 취급하면됩니다. 가장 중요한 프로그램은 다음 개발자가 자신이 한 일과 이유를 직관적으로 이해할 수있게 해주기 때문에 유효합니다.
답변
제 생각에는 일부 개념이 섞여서 잘 표현되지 않은 질문이 생겼다는 것입니다.
클래스의 생성자는 (내가 사용하는 것을 선호하지만 끝 없다 “결코”하는 새로운 스레드를 시작할 수 while(_KeepRunning)
이상 while(true)
어디 선가 false로 부울 멤버를 설정할 수 있기 때문에 참조).
객체 생성과 실제 작업 시작을 분리하기 위해 스레드를 만들고 자체의 기능으로 시작하는 방법을 추출 할 수 있습니다. 리소스에 대한 액세스를보다 잘 제어 할 수 있기 때문에이 방법을 선호합니다.
“활성 객체 패턴”을 볼 수도 있습니다. 결국, 질문은 언제 “Active Object”의 스레드를 시작해야하는지에 대한 것이 었습니다. 제가 선호하는 것은 구성을 분리하고 더 나은 제어를 시작하는 것입니다.
답변
당신의 객체는 작업 시작을 상기시켜줍니다 . 작업 객체는 생성자를 가지고 있지만 그것을 같은 정적 팩토리 메소드의 무리도있다 : Task.Run
, Task.Start
및 Task.Factory.StartNew
.
이것들은 당신이하려고하는 것과 매우 유사하며 이것이 아마도 ‘협약’일 것입니다. 사람들이 가지고있는 주요 문제는 생성자를 사용하면 놀라게한다는 것입니다. @ JörgWMittag는 기본 계약을 위반한다고 말합니다 .Jörg 는 매우 놀랐습니다. 동의하고 더 이상 추가 할 것이 없습니다.
그러나 OP가 정적 팩토리 메소드를 시도한다고 제안하고 싶습니다. 놀람은 사라지고 여기 에는 근본적인 계약 이 없습니다 . 사람들은 특별한 일을하는 정적 팩토리 메소드에 익숙하며 그에 따라 이름을 지정할 수 있습니다.
당신은 @Brandin 같은, 뭔가를 알 수 있듯이 (세밀하게 제어 할 수 생성자 제공 할 수있는 var g = new Game {...}; g.MainLoop();
바로 게임을 시작하고 싶지 않은 사용자를 수용하고, 어쩌면 주변에 먼저 전달하려는. 그리고 당신이 뭔가 좋아 쓸 수있는 var runningGame = Game.StartNew();
시작을 위해 즉시 쉽게.