클래스 A의 자식 개체 속성을 참조하는 클래스 A에 속성을 구현하는 방법


9

단순화되면 다음과 같은 코드가 있습니다.

public class Room
{
    public Client Client { get; set; }

    public long ClientId
    {
        get
        {
            return Client == null ? 0 : Client.Id;
        }
    }
}

public class Client 
{
    public long Id { get; set; }
}

이제 세 가지 관점이 있습니다.

1) 이것은 Client속성이 항상 설정되어야 하므로 (예 : null이 아니므로) Client == null발생하지 않으며 Id 값 0은 어쨌든 잘못된 ID를 나타냅니다 (코드 작성자의 의견입니다 ;-))

2) 당신은 알고 발신자에 의존 할 수없는 0거짓에 대한 값 Id과 경우 Client속성이 항상 설정해야합니다 당신이 던져해야 exception의를 getClient속성이 null 될 일이

3) Client속성을 항상 설정 해야하는 경우 속성이 null 일 때 반환 Client.Id하고 코드에서 NullRef예외를 throw하도록하십시오 Client.

이 중 가장 올바른 것은 무엇입니까? 아니면 네 번째 가능성이 있습니까?


5
대답이 아니라 참고 사항 : 재산 게터로부터 예외를 던지지 마십시오.
Brandon

3
Brandon의 요점을 자세히 설명하려면 : stackoverflow.com/a/1488488/569777
MetaFight

1
물론 : 일반적인 아이디어 ( msdn.microsoft.com/en-us/library/… , stackoverflow.com/questions/1488472/… 등)는 속성이 가능한 한 적게 수행하고 가벼우 며 청결 함을 나타내야한다는 것입니다 , 인덱서가 약간 예외 인 객체의 공개 상태. 디버깅하는 동안 속성에 대한 감시 창에서 예외를 보는 것도 예기치 않은 동작입니다.
Brandon

1
프로퍼티 게터에 던지는 코드를 작성해서는 안됩니다. 그렇다고해서 개체가 지원되지 않는 상태가되어 발생하는 예외를 숨기고 삼켜야한다는 의미는 아닙니다.
Ben

4
왜 Room.Client.Id를 할 수 없습니까? 왜 이것을 Room.ClientId로 감싸고 싶습니까? 클라이언트를 확인하려면 Room.HasClient와 같은 것이 아닌 이유 {get {return client == null; }} 더 직관적이지만 사람들이 실제로 ID가 필요할 때 정상적인 Room.Client.Id를 수행하도록 허용합니까?
James

답변:


25

Room수업 에 참여할 수있는 주 수를 제한해야하는 것처럼 냄새가납니다 .

언제 Clientnull을 해야하는지 묻는 것은 Room상태 공간이 너무 크다는 힌트입니다 .

일을 단순하게 유지하기 위해 인스턴스 의 Client속성 Room이 null이되도록 허용하지 않습니다 . 이는 내부의 코드 가 null이 Room아니라고 안전하게 가정 할 수 있음을 의미합니다 Client.

미래에 어떤 이유로 경우 Client가된다 null그 상태를 지원하는 충동에 저항. 그렇게하면 유지 관리 복잡성이 증가합니다.

대신 코드가 실패하고 빠르게 실패하게하십시오. 결국 이것은 지원되는 상태가 아닙니다. 응용 프로그램 자체가이 상태가되면 이미 반환되지 않은 줄을 넘은 것입니다. 그런 다음해야 할 유일한 일은 응용 프로그램을 닫는 것입니다.

처리되지 않은 null 참조 예외의 결과로 (자연스럽게) 발생할 수 있습니다.


2
좋은. 내가 생각 즉, null의 경우 무엇을해야할지 참 좋은 질문보다 ". 내가 널 수에 어떤 방 인스턴스의 클라이언트 속성을 허용하지 않을 일을 간단하게 유지하기"와 같은
미셸

@Michel 나중에 나중에 해당 요구 사항을 변경하기로 결정한 경우 null값을 NullObject(또는 대신 NullClient) 값 으로 바꾸면 기존 코드로 계속 작동하여 존재하지 않는 클라이언트의 동작을 정의 할 수 있습니다. 문제는 "클라이언트 없음"사례가 비즈니스 로직의 일부인지 여부입니다.
null

3
완전히 맞습니다. 지원되지 않는 상태를 지원하기 위해 단일 문자 코드를 작성하지 마십시오. 그냥 NullRef를 던져 보자.
Ben

@Ben 동의하지 않음; MS 지침에 따르면 속성 Getter는 예외를 발생시키지 않아야합니다. 더 의미있는 예외가 대신 발생할 수있는 경우 null 참조를 발생시키는 것은 게으른 일입니다.
Andy

1
@Andy는 지침의 이유를 이해하면 적용되지 않는시기를 알 수 있습니다.
Ben

21

몇 가지 고려 사항 :

a) Client 인스턴스 자체에 대한 공개 getter가 이미 있는데 왜 ClientId에 대한 getter가 있습니까? 나는 왜 방의 long서명에서 ClientId라는 정보가 돌로 새겨 져야하는지 모르겠다 .

b) 두 번째 의견과 관련하여 상수를 도입 할 수 Invalid_Client_Id있습니다.

c) 의견 1과 3에 관해 (그리고 나의 주요 요점) : 방에는 항상 고객이 있어야합니까? 어쩌면 의미론 일 수도 있지만 이것은 올바르게 들리지 않습니다. 룸과 클라이언트를 위해 완전히 분리 된 클래스와이를 묶는 다른 클래스를 갖는 것이 더 적절할 것입니다. 어쩌면 약속, 예약, 점유? (이것은 실제로 무엇을하고 있는지에 달려 있습니다.) 그리고 그 수업에서 "공간이 있어야합니다"와 "클라이언트가 있어야합니다"라는 제약 조건을 시행 할 수 있습니다.


5
+1 "예를 들어 ClientId가 긴 정보가 왜 방의 시그니처에 돌로 새겨 져야하는지"
Michel

당신의 영어는 괜찮습니다. ;)이 답변을 읽은 것만으로도 당신의 모국어가 영어가 아니라는 것을 알지 못했을 것입니다.
jpmc26

포인트 B & C가 좋습니다. RE : a, 편리한 방법, 그리고 방에 고객에 대한 참조가 있다는 사실은 구현 세부 사항 일 수 있습니다 (고객이 공개적으로 보이지 않아야한다고 주장 할 수 있음)
Andy

17

나는 세 의견 모두에 동의하지 않습니다. 경우 Client캔은 결코 null, 다음도 가능은 할 수 할 수 있도록하지 않습니다null !

  1. Client생성자에서 값을 설정하십시오.
  2. ArgumentNullException생성자에를 던지십시오 .

따라서 코드는 다음과 같습니다.

public class Room
{
    private Client theClient;

    public Room(Client client) {
        if(client == null) throw new ArgumentNullException();
        this.theClient = client;
    }

    public Client Client { 
        get { return theClient; }
        set 
        {
            if (value == null) 
                throw new ArgumentNullException();
            theClient = value;
        }
    }

    public long ClientId
    {
        get
        {
            return theClient.Id;
        }
    }
}
public class Client 
{
    public long Id { get; set; }
}

이 코드와 관련이없는,하지만 당신은 아마 더 나은 개시 될 것이다 불변 의 버전을 Room제작하여 theClient readonly, 다음의 경우 새로운 방을 클라이언트 변경됩니다. 이렇게하면 내 대답의 다른 측면의 null 안전성뿐만 아니라 코드의 스레드 안전성이 향상됩니다. 가변 대 불변에 대한이 토론을 참조하십시오


1
이것들은 ArgumentNullExceptions 아니어야합니까 ?
Mathieu Guindon

4
프로그램 코드는 NullReferenceException.Net VM에서 "널 객체 참조를 역 참조하려고 할 때"발생합니다 . "null 참조 (Visual Basic의 경우 Nothing)가 유효한 인수로 허용되지 않는 메서드에 전달 될 때ArgumentNullException 발생하는을 throw 해야합니다 .
Pharap

2
@Pharap 저는 C # 코드를 작성하려고하는 Java 프로그래머입니다.
durron597

@ durron597 나는 'final'이라는 용어를 사용하여 추측했을 것입니다 (이 경우 해당 C # 키워드는 readonly). 나는 방금 편집했을 것이지만, 주석은 왜 미래의 독자들을 위해 더 잘 설명 할 수 있다고 생각했습니다. 이 답변을 아직주지 않았다면 똑같은 해결책을 줄 것입니다. 불변성은 좋은 생각이지만 항상 원하는만큼 쉬운 것은 아닙니다.
Pharap

2
속성은 일반적으로 예외를 던져서는 안됩니다. ArgumentNullException을 발생시키는 setter 대신 SetClient 메소드를 제공하십시오. 누군가가 질문에 대한 답변에 대한 링크를 게시했습니다.
Andy

5

두 번째와 세 번째 옵션은 피해야합니다. getter는 제어 할 수없는 예외를 제외하고 호출자를 때려서는 안됩니다.

Client가 null 일 수 있는지 여부를 결정해야합니다. 그렇다면 호출자가 액세스하기 전에 null인지 확인하는 방법을 제공해야합니다 (예 : bool ClientIsNull 속성).

Client가 널이 될 수 없다고 결정한 경우,이를 생성자에 필수 매개 변수로 만들고 널이 전달되면 예외를 처리하십시오.

마지막으로, 첫 번째 옵션에는 코드 냄새도 포함됩니다. 고객이 자체 ID 속성을 처리하도록해야합니다. 포함 된 클래스에서 getter를 호출하는 컨테이너 클래스에서 getter를 코딩하는 것은 과도한 것처럼 보입니다. 클라이언트를 속성으로 노출하기 만하면됩니다 (그렇지 않으면 클라이언트가 이미 제공 한 모든 것을 복제하게됩니다 ).

long clientId = room.Client.Id;

Client가 null 일 수있는 경우 최소한 발신자에게 책임을 져야합니다.

if (room.Client != null){
    long clientId = room.Client.Id;
    /* other code follows... */
}

1
"고객이 이미 제공 한 모든 것을 복제하게 될" 은 대담하거나 적어도 이탤릭체 여야 한다고 생각 합니다 .
Pharap

2

널 클라이언트 특성이 지원되는 상태 인 경우 NullObject 사용을 고려하십시오 .

그러나 이것은 아마도 예외적 인 상태이므로 null로 끝나는 것이 불가능하거나 매우 편리하지 않아야합니다 Client.

public Client Client { get; private set; }

public Room(Client client)
{
    Client = client;
}

public void SetClient(Client client)
{
    if (client == null) throw new ArgumentNullException();
    Client = client;
}

그러나 지원되지 않으면 반 구운 용액에서 시간을 낭비하지 마십시오. 이 경우 :

return Client == null ? 0 : Client.Id;

여기서 NullReferenceException을 '해결'했지만 많은 비용이 듭니다! 이렇게하면 모든 발신자가 Room.ClientId0을 확인하게됩니다.

var aRoom = new Room(aClient);
var clientId = aRoom.ClientId;
if (clientId == 0) RestartRoombookingWizard();
else ContinueRoombooking(clientid);

이상한 버그는 다른 개발자 (또는 자신!)와 특정 시점에서 "ErrorCode"반환 값을 확인하는 것을 잊어 버릴 수 있습니다.
안전하고 빠르게 실패하십시오. NullReferenceException이 발생하도록하고 호출자가 더 많은 것을 엉망으로 만드는 대신에 호출 스택의 상위에서 "우아하게"포착하십시오.

다른 메모에서, 당신이 TellDontAsk에Client 대한 ClientId생각 과 노출에 너무 열중하고 있다면 . 달성하고자하는 것을 말하지 않고 물건을 너무 많이 요구하면 모든 것을 함께 결합하여 나중에 변경하기가 더 어려워 질 수 있습니다.


그것은 NotSupportedException잘못 사용되고 있습니다, 당신은 ArgumentNullException대신 사용해야합니다 .
Pharap

1

현재 릴리스 된 c # 6.0에서이 작업을 수행하면됩니다.

public class Room
{
    public Room(Client client)
    {
        this.Client = client;
    }

    public Client Client { get; }
}

public class Client
{
    public Client(long id)
    {
        this.Id = id;
    }

    public long Id { get; }
}

의 속성 올리기 Client로를Room 캡슐화의 명백한 위반과 DRY 원칙이다.

당신이 액세스해야하는 경우 Id(A)의 ClientRoom당신이 할 수 있습니다,

var clientId = room.Client?.Id;

의 사용을 참고 널 조건부 연산자 , clientId될 것입니다 long?경우, Client이다 null, clientId할 것이다 null, 그렇지 않으면 clientId의 값을 갖게됩니다 Client.Id.


그러나의 유형은 clientid오랫동안 nullable입니까?
Michel

@Michel 음, 방에 고객이 있어야하는 경우 ctor에 null 검사를하십시오. 그러면 var clientId = room.Client.Id안전하고 안전 long합니다.
Jodrell
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.