[c#] List <T>에서 상속받지 않겠습니까?

프로그램을 계획 할 때 종종 다음과 같은 생각으로 시작합니다.

축구 팀은 축구 선수 목록입니다. 따라서 다음과 같이 표현해야합니다.

var football_team = new List<FootballPlayer>();

이 목록의 순서는 선수가 명단에 나열되는 순서를 나타냅니다.

그러나 나중에 팀에는 단순한 플레이어 목록 외에도 기록해야 할 다른 속성이 있다는 것을 알고 있습니다. 예를 들어, 이번 시즌의 총 점수, 현재 예산, 균일 한 색상, string팀 이름을 나타내는 등

그래서 나는 생각합니다 :

풋볼 팀은 선수 목록과 비슷하지만, 이름 (a string)과 총점 (an int) 을가 집니다. .NET은 풋볼 팀을 저장하기위한 수업을 제공하지 않기 때문에 나만의 수업을 만들 것입니다. 가장 유사하고 관련성이 높은 기존 구조는입니다 List<FootballPlayer>.

class FootballTeam : List<FootballPlayer> 
{ 
    public string TeamName; 
    public int RunningTotal 
}

그러나 지침에 따르면 상속해서는 안됩니다List<T> . 나는이 지침에서 두 가지 측면에서 완전히 혼란스러워합니다.

왜 안돼?

분명히 List성능에 최적화되어 있습니다. 어떻게 요? 연장하면 어떤 성능 문제가 발생 List합니까? 정확히 무엇을 깰까요?

내가 본 또 다른 이유 List는 Microsoft에서 제공 한 것이므로이를 제어 할 수 없으므로 “public API”노출 후 나중에 변경할 수 없습니다 . 그러나 나는 이것을 이해하려고 노력합니다. 공개 API 란 무엇이며 왜 관심을 가져야합니까? 현재 프로젝트에이 공용 API가 없거나 포함되어 있지 않을 경우이 지침을 무시해도됩니까? 상속을 List 받고 공개 API가 필요한 것으로 밝혀지면 어떤 어려움이 있습니까?

왜 중요합니까? 리스트는리스트입니다. 무엇이 변경 될 수 있습니까? 무엇을 바꾸고 싶습니까?

마지막으로, Microsoft가 내가 상속받지 않기를 원한다면 List왜 수업을하지 않았 sealed습니까?

내가 무엇을 사용해야합니까?

분명히, 사용자 지정 컬렉션을 위해 Microsoft는 Collection대신에 확장해야하는 클래스를 제공했습니다 List. 그러나이 클래스는 매우 나쁘고 예를 들어와 같은AddRange 유용한 것들이 많지 않습니다 . jvitor83의 대답 은 특정 방법에 대한 성능 이론적 근거를 제공하지만 느리게는 AddRange아니지 않은 방법은 AddRange무엇입니까?

에서 상속하는 Collection것이 에서 상속하는 것보다 훨씬 많은 작업이며 List이점이 없습니다. 분명히 Microsoft는 아무런 이유없이 추가 작업을 수행하도록 지시하지 않으므로 어떻게 든 무언가를 오해하고 있다고 느끼는 것을 도울 수 없으며 상속 Collection은 실제로 내 문제에 대한 올바른 해결책이 아닙니다.

구현과 같은 제안을 보았습니다 IList. 안돼. 이것은 아무것도 얻지 못하는 수십 줄의 상용구 코드입니다.

마지막으로, 일부는 List무언가를 감싸는 것이 좋습니다 .

class FootballTeam 
{ 
    public List<FootballPlayer> Players; 
}

이것에는 두 가지 문제가 있습니다 :

  1. 내 코드를 불필요하게 장황하게 만듭니다. 이제 my_team.Players.Count그냥 대신에 전화해야합니다 my_team.Count. 고맙게도 C #을 사용하면 인덱서를 정의하여 인덱싱을 투명하게 만들고 내부의 모든 메소드를 전달할 수 있습니다 List…하지만 많은 코드입니다! 그 모든 일을 위해 무엇을 얻습니까?

  2. 그냥 평범하지 않습니다. 풋볼 팀은 선수 목록을 가지고 있지 않습니다. 그것은 이다 플레이어의 목록입니다. “John McFootballer가 SomeTeam의 플레이어에 합류했습니다”라고 말하지 않습니다. “John이 SomeTeam에 가입했습니다”라고 말합니다. “문자열 문자”에 문자를 추가하지 않고 문자열에 문자를 추가합니다. 책을 도서관의 책에 추가하지 않고 책을 도서관에 추가합니다.

“후드에서”발생하는 일이 “Y의 내부 목록에 X 추가”라고 말할 수 있지만, 이것은 세상에 대해 매우 반 직관적 인 사고 방식처럼 보입니다.

내 질문 (요약)

“논리적”( “인간의 마음에”라고하는 것입니다)이 단지 인 데이터 구조, 표현의 올바른 C #을 방법은 무엇입니까 listthings몇 종과 경적은?

상속은 List<T>항상 허용되지 않습니까? 언제 받아 들일 수 있습니까? 왜 왜 안돼? 상속 여부를 결정할 때 프로그래머가 고려해야 할 사항은 무엇입니까 List<T>?



답변

여기에 좋은 답변이 있습니다. 나는 그들에게 다음과 같은 점을 덧붙일 것입니다.

데이터 구조를 나타내는 올바른 C # 방식은 무엇입니까? “논리적으로”(즉, “사람의 마음에”) 몇 개의 종소리와 휘파람이있는 것의 목록 ​​일 뿐입니 까?

축구의 존재에 익숙한 컴퓨터 프로그래머가 아닌 10 명에게 빈 칸을 채우라고 요청하십시오.

축구 팀은 특별한 종류의 _____

셨습니까 사람의 말은 “몇 종과 경적을 가진 축구 선수의 목록”, 또는 그들은 모두 말 “스포츠 팀”또는 “클럽”또는 “조직”를했다? 축구 팀이 특정 종류의 선수 목록 이라는 생각은 인간의 마음과 인간의 마음에만 있습니다.

List<T>A는 기구 . 풋볼 팀은 비즈니스 객체 , 즉 프로그램 의 비즈니스 영역 에있는 일부 개념을 나타내는 객체입니다 . 그것들을 섞지 마십시오! 축구 팀 은 일종의 팀입니다. 그것은 명단은 명단을 플레이어의 목록입니다 . 명단은 특정 종류의 선수 목록 이 아닙니다 . 명단 선수 목록입니다. 그래서라는 속성 수 있도록 Roster입니다 List<Player>. ReadOnlyList<Player>풋볼 팀에 대해 알고있는 모든 사람이 명단에서 선수를 삭제한다고 믿지 않는 한, 당신이있는 동안 그것을 만드십시오 .

상속이 List<T>항상 허용되지 않습니까?

누구에게 받아 들여지지 않습니까? 나를? 아니.

언제 받아 들일 수 있습니까?

당신이 메커니즘을 구축 할 때 확장 List<T>메커니즘을 .

상속 여부를 결정할 때 프로그래머가 고려해야 할 사항은 무엇입니까 List<T>?

나는 건물입니다 메커니즘 또는 비즈니스 오브젝트를 ?

그러나 그것은 많은 코드입니다! 그 모든 일을 위해 무엇을 얻습니까?

List<T>50 번 이상 관련 멤버를위한 전달 방법을 작성하는 데 걸리는 질문에 더 많은 시간을 투자 했습니다. 당신은 명백하게 자세한 것을 두려워하지 않으며, 우리는 여기서 아주 적은 양의 코드에 대해 이야기하고 있습니다; 이것은 몇 분 작업입니다.

최신 정보

나는 그것을 좀 더 생각했고 축구 팀을 선수 목록으로 모델링하지 않는 또 다른 이유가 있습니다. 실제로 축구 팀 을 선수 목록 도 가지고 있는 것으로 모델링하는 것은 좋지 않습니다 . 팀이 플레이어 목록을 가지고 있거나 가지고있는 문제는 당신이 가진 것이 순간 의 팀 스냅 샷 이라는 것 입니다. 이 클래스의 비즈니스 사례가 무엇인지 모르겠지만 축구 팀을 대표하는 클래스가 있다면 “2003 년에서 2013 년 사이에 부상으로 몇 명의 Seahawks 선수가 게임을 놓쳤습니까?”와 같은 질문을하고 싶습니다. 또는 “이전에 다른 팀에서 뛰었던 덴버 선수는 전년 대비 가장 많은 야드를 기록 했습니까?” 또는 ” Piggers는 올해도 끝까지 갔습니까?

즉, 풋볼 팀은 선수가 모집, 부상, 퇴직했을 때와 같은 역사적 사실의 모음 으로 잘 모델링 된 것 같습니다 . 분명히 현재 선수 명단은 아마도 전면 및-해야 할 중요한 사실입니다 보다 역사적인 관점을 필요로하는이 대상으로하고 싶은 다른 흥미로운 일이있을 수 있습니다.


답변

와우, 게시물에는 수많은 질문과 요점이 있습니다. Microsoft에서 얻은 대부분의 추론은 바로 그 시점에 있습니다. 에 대한 모든 것부터 시작합시다List<T>

  • List<T> 되어 최적화. 주요 사용법은 개체의 개인 구성원으로 사용됩니다.
  • 때로는 친숙한 이름을 가진 클래스를 만들고 싶을 수도 있기 때문에 Microsoft는 봉인하지 않았습니다 class MyList<T, TX> : List<CustomObject<T, Something<TX>> { ... }. 이제하는 것만 큼 쉽습니다 var list = new MyList<int, string>();.
  • CA1002 : 일반 목록을 공개하지 마십시오 . 기본적으로이 앱을 단독 개발자로 사용하려는 경우에도 좋은 코딩 방법을 사용하여 개발하는 것이 좋습니다. IList<T>소비자가 색인화 된 목록을 보유해야하는 경우 에도 목록을 노출로 표시 할 수 있습니다 . 이것으로 나중에 클래스 내에서 구현을 변경할 수 있습니다.
  • 마이크로 소프트는 Collection<T>일반적인 개념이기 때문에 매우 일반적으로 사용되었습니다. 그것은 단지 모음 일뿐입니다. 같은 더 정확한 버전이있다 SortedCollection<T>, ObservableCollection<T>, ReadOnlyCollection<T>구현, 각각의 등 IList<T>하지 않음 List<T> .
  • Collection<T>구성원 (가상, 제거 등)이 가상이기 때문에 재정의 할 수 있습니다. List<T>하지 않습니다.
  • 당신의 질문의 마지막 부분은 자리에 있습니다. 풋볼 팀은 단순한 플레이어 목록 그 이상이므로 해당 플레이어 목록을 포함하는 클래스 여야합니다. 구성 대 상속을 생각하십시오 . 풋볼 팀 에는 선수 명단이 있습니다 (명단). 선수 명단은 아닙니다.

이 코드를 작성하는 경우 클래스는 다음과 같이 보일 것입니다.

public class FootballTeam
{
    // Football team rosters are generally 53 total players.
    private readonly List<T> _roster = new List<T>(53);

    public IList<T> Roster
    {
        get { return _roster; }
    }

    // Yes. I used LINQ here. This is so I don't have to worry about
    // _roster.Length vs _roster.Count vs anything else.
    public int PlayerCount
    {
        get { return _roster.Count(); }
    }

    // Any additional members you want to expose/wrap.
}


답변

class FootballTeam : List<FootballPlayer>
{
    public string TeamName;
    public int RunningTotal;
}

이전 코드는 거리에서 축구를하는 많은 사람들을 의미하며 이름이 생깁니다. 다음과 같은 것 :

축구하는 사람들

어쨌든,이 코드 (내 대답에서)

public class FootballTeam
{
    // A team's name
    public string TeamName;

    // Football team rosters are generally 53 total players.
    private readonly List<T> _roster = new List<T>(53);

    public IList<T> Roster
    {
        get { return _roster; }
    }

    public int PlayerCount
    {
        get { return _roster.Count(); }
    }

    // Any additional members you want to expose/wrap.
}

의미 : 이것은 관리, 선수, 관리자 등이있는 축구 팀입니다.

맨체스터 유나이티드 팀의 구성원 (및 기타 속성)을 보여주는 이미지

이것은 당신의 논리가 그림에 어떻게 표현되어 있는가…


답변

이것은 구성상속 의 고전적인 예입니다 .

이 특정한 경우에 :

팀에 행동이 추가 된 선수 목록이 있습니까?

또는

팀이 플레이어 목록을 포함하는 자체 개체입니까?

List를 확장하면 여러 가지 방법으로 자신을 제한 할 수 있습니다.

  1. 액세스를 제한 할 수 없습니다 (예 : 명단 변경 사람들 중지). 모든 List 메소드는 필요한지 여부에 관계없이 얻을 수 있습니다.

  2. 다른 것들의 목록을 원한다면 어떻게됩니까? 예를 들어, 팀에는 코치, 관리자, 팬, 장비 등이 있습니다. 그 중 일부는 자신의 목록 일 수도 있습니다.

  3. 상속 옵션이 제한됩니다. 예를 들어 일반 Team 객체를 만든 다음 그로부터 상속받은 BaseballTeam, FootballTeam 등을 원할 수 있습니다. List에서 상속하려면 Team에서 상속을 수행해야하지만 이는 모든 다양한 유형의 팀이 해당 명부를 동일하게 구현해야 함을 의미합니다.

컴포지션-객체 내부에서 원하는 동작을 제공하는 객체를 포함합니다.

상속-개체가 원하는 동작을 가진 개체의 인스턴스가됩니다.

둘 다 용도가 있지만 구성이 바람직한 명확한 경우입니다.


답변

모두가 지적했듯이, 플레이어 팀은 플레이어 목록이 아닙니다. 이 실수는 다양한 수준의 전문 지식을 가진 곳곳의 많은 사람들에 의해 이루어집니다. 이 경우와 같이 종종 문제는 미묘하고 때로는 매우 심각합니다. 이러한 설계는 Liskov 대체 원칙을 위반하기 때문에 좋지 않습니다 . 인터넷에는이 개념을 설명하는 좋은 기사가 많이 있습니다. 예 : http://en.wikipedia.org/wiki/Liskov_substitution_principle

요약하면, 클래스 간 부모 / 자식 관계에서 유지해야 할 두 가지 규칙이 있습니다.

  • 아동은 부모를 완전히 정의하는 것보다 적은 특성을 요구해서는 안됩니다.
  • 부모는 아동을 완전히 정의하는 것 외에 특성이 필요하지 않아야합니다.

다시 말해, 부모는 자녀에 대한 필수 정의이고, 자녀는 부모에 대한 충분한 정의입니다.

여기에 솔루션을 통해 생각하고 그러한 실수를 피하는 데 도움이되는 위의 원칙을 적용하는 방법이 있습니다. 부모 클래스의 모든 작업이 파생 클래스에 대해 구조적 및 의미 적으로 유효한지 확인하여 가설을 테스트해야합니다.

  • 축구 팀은 축구 선수 목록입니까? (리스트의 모든 속성이 같은 의미로 팀에 적용됩니까)
    • 팀은 동종 단체의 집합입니까? 예, 팀은 플레이어의 모음입니다
    • 플레이어 포함 순서는 팀의 상태를 설명하고 팀은 명시 적으로 변경되지 않는 한 순서가 유지되도록 보장합니까? 아니, 아니
    • 팀의 순서에 따라 선수를 포함 / 삭제해야합니까? 아니

보시다시피, 목록의 첫 번째 특성 만 팀에 적용됩니다. 따라서 팀은 목록이 아닙니다. 목록은 팀을 관리하는 방법에 대한 구현 세부 사항이므로 플레이어 개체를 저장하고 Team 클래스의 메서드로 조작하는 데만 사용해야합니다.

이 시점에서 나는 Team 클래스가 List를 사용하여 구현되어서는 안된다고 말하고 싶습니다. 대부분의 경우 Set 데이터 구조 (예 : HashSet)를 사용하여 구현해야합니다.


답변

FootballTeam예비 팀에 주 팀과 함께 예비 팀이 있으면 어떻게 됩니까?

class FootballTeam
{
    List<FootballPlayer> Players { get; set; }
    List<FootballPlayer> ReservePlayers { get; set; }
}

그걸 어떻게 모델링 하시겠습니까?

class FootballTeam : List<FootballPlayer>
{
    public string TeamName;
    public int RunningTotal
}

관계는 명확하게 A가 들어 하지 입니다 .

또는 RetiredPlayers?

class FootballTeam
{
    List<FootballPlayer> Players { get; set; }
    List<FootballPlayer> ReservePlayers { get; set; }
    List<FootballPlayer> RetiredPlayers { get; set; }
}

일반적으로 컬렉션에서 상속하려면 클래스 이름을 지정하십시오 SomethingCollection.

당신이 있습니까 SomethingCollection의미 이해? 유형 이의 모음 인 경우에만이 작업을 수행하십시오 Something.

이 경우 FootballTeam제대로 들리지 않습니다. A Team는 이상 Collection입니다. A는 Team다른 답변이 지적한대로 등 코치, 트레이너를 가질 수 있습니다.

FootballCollection축구 모음 또는 축구 도구 모음처럼 들립니다. TeamCollection팀의 모음입니다.

FootballPlayerCollectionList<FootballPlayer>당신이 정말로 그것을 원한다면 상속받은 클래스의 유효한 이름이 될 플레이어 모음처럼 들립니다 .

정말 List<FootballPlayer>완벽하게 다루는 유형입니다. 아마도 IList<FootballPlayer>당신이 메소드에서 그것을 반환한다면.

요약하자면

자신에게 물어

  1. X a는 Y? 또는 가지고 X A는 Y?

  2. 수업 이름이 무엇인가요?


답변

디자인> 구현

어떤 방법과 속성이 노출되는지는 디자인 결정입니다. 상속받은 기본 클래스는 구현 세부 사항입니다. 전 단계로 돌아가는 것이 가치가 있다고 생각합니다.

객체는 데이터와 행동의 모음입니다.

따라서 첫 번째 질문은 다음과 같아야합니다.

  • 이 개체는 내가 만드는 모델에서 어떤 데이터를 구성합니까?
  • 이 객체는 해당 모델에서 어떤 동작을 보입니까?
  • 미래에는 어떻게 변할까요?

상속은 “isa”(is) 관계를 의미하는 반면, composition은 “has”(hasa) 관계를 의미합니다. 응용 프로그램의 발전에 따라 상황이 달라질 수 있다는 점을 염두에두고보기에 상황에 맞는 것을 선택하십시오.

어떤 사람들은 두뇌를 “디자인 모드”에 놓는 것이 더 쉽다는 것을 알기 때문에 구체적인 유형으로 생각하기 전에 인터페이스를 생각하는 것을 고려하십시오.

이것은 일상적으로 코딩 할 때이 수준에서 모든 사람이 의식적으로하는 것이 아닙니다. 그러나 이런 종류의 주제를 궁리하고 있다면 디자인 수역을 밟고있는 것입니다. 그것을 알고 있으면 해방 될 수 있습니다.

설계 특성 고려

MSDN 또는 Visual Studio에서 List <T> 및 IList <T>를 살펴보십시오. 그들이 어떤 방법과 속성을 노출하는지 확인하십시오. 이 방법들이 모두 누군가가 당신의 관점에서 FootballTeam에하고 싶은 것처럼 보입니까?

footballTeam.Reverse ()는 당신에게 의미가 있습니까? footballTeam.ConvertAll <TOutput> ()이 원하는 것입니까?

이것은 속임수가 아닙니다. 대답은 “예”일 수 있습니다. List <Player> 또는 IList <Player>를 구현 / 상속하면이 문제가 발생합니다. 그것이 모델에 이상적이라면 그렇게하십시오.

당신이 그렇다고 생각한다면, 그것은 말이되고, 당신의 객체가 플레이어들의 행동 / 모음으로 취급 될 수 있기를 원하기 때문에, 당신은 반드시 ICollection 또는 IList를 구현하기를 원합니다. 명목상 :

class FootballTeam : ... ICollection<Player>
{
    ...
}

객체에 플레이어의 컬렉션 / 목록 (데이터)이 포함되도록하려는 경우 컬렉션 또는 목록이 속성 또는 멤버가되기를 원하는 경우 반드시 그렇게하십시오. 명목상 :

class FootballTeam ...
{
    public ICollection<Player> Players { get { ... } }
}

사람들이 플레이어 수를 열거하거나 추가하거나 제거하는 대신 플레이어 세트 만 열거 할 수 있기를 원할 수도 있습니다. IEnumerable <Player>는 고려해야 할 완벽하게 유효한 옵션입니다.

이러한 인터페이스 중 어느 것도 모델에 전혀 유용하지 않다고 생각할 수도 있습니다. 이것은 가능하지 않지만 (IEnumerable <T>는 많은 상황에서 유용합니다) 여전히 가능합니다.

이 중 하나가 모든 경우에 범주 및 결정적으로 잘못되었다고 말하려는 사람 은 잘못 안내됩니다. 당신에게 그것을 말하려고 시도하는 사람 은 모든 경우에 범주 적으로 그리고 결정적으로 습니다.

구현으로 이동

데이터와 행동을 결정한 후에는 구현에 대한 결정을 내릴 수 있습니다. 여기에는 상속 또는 구성을 통해 의존하는 구체적인 클래스가 포함됩니다.

이것은 큰 걸음이 아닐 수 있으며 사람들은 종종 디자인과 구현을 혼동하기 때문에 머리에서 두세 초 만에 완전히 입력하고 입력을 시작할 수 있기 때문에 가능합니다.

생각 실험

인공적인 예 : 다른 사람들이 언급했듯이 팀은 항상 플레이어의 모음 일뿐입니다. 팀의 경기 점수 수집을 유지합니까? 귀하의 모델에서 팀이 클럽과 교환이 가능합니까? 그렇다면 팀이 플레이어 모음 인 경우 직원 모음 및 / 또는 점수 모음 일 수도 있습니다. 그런 다음 다음과 같이 끝납니다.

class FootballTeam : ... ICollection<Player>,
                         ICollection<StaffMember>,
                         ICollection<Score>
{
    ....
}

그럼에도 불구하고 디자인은 C #에서이 시점에서 List <T>에서 상속하여이 모든 것을 구현할 수는 없습니다. C # “only”는 단일 상속 만 지원하기 때문입니다. (당신이 C에서이 malarky을 시도한 경우 ++,이 좋은 것을 고려할 수 있습니다.) 구성을 통해 하나 개의 상속을 통해 수집 및 하나를 구현하는 것입니다 느낌이 더러운. 또한 ILIst <Player> .Count 및 IList <StaffMember> .Count 등을 명시 적으로 구현하지 않으면 Count와 같은 속성이 사용자에게 혼란스러워지고 혼란 스럽기만하면 고통 스럽습니다. 어디로 가는지 알 수 있습니다. 이 길을 생각하는 동안 직감은이 방향으로 향하는 것이 잘못되었다고 말할 수도 있습니다 (올바르거나 잘못하면, 당신이 이런 식으로 구현하면 동료가 할 수도 있습니다!)

짧은 대답 (너무 늦음)

컬렉션 클래스에서 상속하지 않는 것에 대한 지침은 C #에 국한되지 않으며 많은 프로그래밍 언어에서 찾을 수 있습니다. 그것은 법이 아닌 지혜를받습니다. 실제로 컴포지션은 이해, 구현 및 유지 관리 측면에서 상속을이기는 것으로 간주되기 때문입니다. 실제 세계 / 도메인 객체에서는 일반적으로, 특히 시간이 지나고 코드에서 객체의 정확한 데이터와 동작이 깊지 않은 한 유용하고 일관된 “사”관계보다 유용하고 일관된 “하사”관계를 찾는 것이 더 일반적입니다 변화. 컬렉션 클래스에서 상속을 항상 배제하지는 않습니다. 그러나 암시 할 수 있습니다.