[c#] List <T>로부터 상속받지 않는 이유는 무엇입니까?


11 Answers

마지막으로, 어떤 것은 List를 wrapping하는 것을 제안한다.

그것이 올바른 방법입니다. "불필요하게 말씨"는 이것을 보는 나쁜 방법입니다. my_team.Players.Count 를 작성할 때 명시 적으로 의미가 있습니다. 당신은 선수들을 세고 싶습니다.

my_team.Count

..아무 의미 없다. 뭐라구?

팀은 단순한 선수 목록 이상으로 구성된 목록이 아닙니다. 팀이 플레이어를 소유하므로 플레이어가 멤버가되어야합니다 (멤버).

지나치게 자세한 내용 걱정된다면 항상 팀의 속성을 노출 할 수 있습니다.

public int PlayerCount {
    get {
        return Players.Count;
    }
}

..된다 :

my_team.PlayerCount

이것은 지금 의미가 있으며 Demeter의 법칙을 준수합니다.

복합 재사용 원칙을 고수하는 것도 고려해야합니다. List<T> 를 상속하면 팀 플레이어 목록이며 불필요한 메서드를 노출한다는 것을 알 수 있습니다. 이것은 틀린 말입니다 - 명시된 바와 같이 팀은 이름, 관리자, 임원, 강사, 의료진, 급여 한도 등의 선수 목록 이상입니다. 팀 수업에 선수 목록이 포함되어 있으면 "팀 에는 선수 명단 있다"고 말하고 있지만 다른 것들도 가질 수있다.

Question

내 프로그램을 계획 할 때 종종 다음과 같은 사슬로 시작합니다.

축구 팀은 축구 선수 목록에 불과합니다. 그러므로 나는 다음과 같이 표현해야한다.

var football_team = new List<FootballPlayer>();

이 목록의 순서는 플레이어가 명부에 등재 된 순서를 나타냅니다.

그러나 나중에 팀은 기록되어야하는 단순한 선수 명단 외에 다른 속성을 가지고 있음을 깨닫습니다. 예를 들어, 이번 시즌의 총 득점, 현재 예산, 유니폼 색상, 팀 이름을 나타내는 string 등.

그래서 나는 생각한다.

좋아요, 풋볼 팀은 선수 목록과 비슷하지만, 이름 ( string )과 누적 합계 ( int )가 있습니다. .NET은 축구 팀 저장을위한 클래스를 제공하지 않으므로 내 자신의 클래스를 만들 것입니다. 가장 유사하고 관련성이 높은 기존 구조는 List<FootballPlayer> 이므로이를 상속받습니다.

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

그러나 List<T> 에서 상속해서는 안된다는 지침 이 있음이 밝혀졌습니다. 나는 두 가지면에서이 가이드 라인에 철저히 혼란 스럽다.

왜 안돼?

외관상으로는 List 는 어떻게해서든지 성과를 위해 낙관된다 . 어떻게 그렇게? List 확장하면 어떤 성능 문제가 발생합니까? 무엇이 깰 것인가?

필자가 보았던 또 다른 이유는 List 가 Microsoft에 의해 제공되며,이를 제어 할 수 없기 때문에 "공개 API"를 공개 한 후에 나중에 변경할 수 없다는 것 입니다. 그러나 나는 이것을 이해하기 위해 애 쓰고있다. 공개 API 란 무엇이며 왜 신경 써야합니까? 현재 프로젝트에 공개 API가 없거나없는 경우이 가이드 라인을 무시해도됩니까? List 에서 상속을 받고 공개 API가 필요하다면 어떤 어려움이 있습니까?

그것은 왜 중요합니까? 목록은 목록입니다. 무엇이 바뀔 수 있습니까? 무엇을 바꾸고 싶습니까?

그리고 마지막으로 Microsoft가 List 에서 상속 받기를 원하지 않는다면 왜 클래스를 sealed 하지 않았습니까?

내가 뭘 더 사용해야하니?

분명히 사용자 지정 컬렉션의 경우 Microsoft는 List 대신 확장해야하는 Collection 클래스를 제공합니다. 그러나이 클래스는 매우 베어 AddRange 와 같은 많은 유용한 것들을 가지고 있지 않습니다. jvitor83의 대답 은 특정 메소드에 대한 성능 근거를 제공하지만, 느린 AddRange 아닌 것보다 나은 점은 무엇입니까?

Collection 상속하는 것은 List 상속하는 것보다 더 많은 작업이며 아무런 이점도 없습니다. 확실히 Microsoft는 아무런 이유없이 추가 작업을 수행하라고 말하지 않을 것입니다. 그래서 나는 어쩐지 오해하고있는 것처럼 느낄 수 없으며 Collection 상속하는 것은 실제로 내 문제에 대한 올바른 해결책이 아닙니다.

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

마지막으로, 어떤 것은 List 를 wrapping하는 것을 제안한다.

class FootballTeam 
{ 
    public List<FootballPlayer> Players; 
}

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

  1. 그것은 내 코드를 불필요하게 장황하게 만든다. 이제 my_team.Players.Count 대신 my_team.Players.Count 를 호출해야합니다. 고맙게도 C #을 사용하면 색인 생성기를 정의하여 색인 생성을 투명하게 만들 수 있으며 내부 List 의 모든 메서드를 전달할 수 있습니다. 나는 그 모든 일을 위해 무엇을 얻습니까?

  2. 그것은 평범하지 않습니다. 축구 팀은 선수 명단을 갖고 있지 않습니다. 그것은 선수 목록입니다. 당신은 "John McFootballer가 SomeTeam의 선수들과 합류했다고"말하지 않습니다. 당신은 "존이 SomeTeam에 가입했다"고 말합니다. "문자열의 문자"에 문자를 추가하지 않으면 문자열에 문자를 추가합니다. 도서관의 서적에 책을 추가하지 않고 서적을 도서관에 추가합니다.

"내부에서 일어나는 일"이 "Y를 내부 목록에 추가"한다고 말할 수 있지만 이것은 세계에 대해 매우 반 직관적 인 사고 방식처럼 보입니다.

내 질문 (요약)

"논리적으로"(즉, "인간의 마음에") 몇 가지 종소리와 호루라기가있는 list things 뿐인 데이터 구조를 나타내는 올바른 C # 방식은 무엇입니까?

List<T> 로부터 상속받을 수 없는가? 언제 받아 들일 수 있습니까? 왜 안돼? List<T> 로부터 상속받을 것인지 아닌지를 결정할 때 프로그래머는 무엇을 고려해야 하는가?




What the guidelines say is that the public API should not reveal the internal design decision of whether you are using a list, a set, a dictionary, a tree or whatever. A "team" is not necessarily a list. You may implement it as a list but users of your public API should use you class on a need to know basis. This allows you to change your decision and use a different data structure without affecting the public interface.




A football team is not a list of football players. A football team is composed of a list of football players!

This is logically wrong:

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

and this is correct:

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



우선, 그것은 유용성과 관련이 있습니다. 상속을 사용하는 경우 Team 클래스는 순수하게 객체 조작을 위해 설계된 비헤이비어 (메서드)를 노출합니다. 예를 들어 AsReadOnly() 또는 CopyTo(obj) 메서드는 팀 객체에 대해 아무런 의미가 없습니다. AddRange(items) 메서드 대신에 당신은 아마 더 AddPlayers(players) 메서드를 원할 것입니다.

LINQ를 사용하려면 ICollection<T> 또는 IEnumerable<T> 과 같은 일반 인터페이스를 구현하는 것이 더 적합합니다.

앞서 언급했듯이 구성이 올바른 방법입니다. 플레이어 목록을 개인 변수로 구현하면됩니다.




If your class users need all the methods and properties** List has, you should derive your class from it. If they don't need them, enclose the List and make wrappers for methods your class users actually need.

This is a strict rule, if you write a public API , or any other code that will be used by many people. You may ignore this rule if you have a tiny app and no more than 2 developers. This will save you some time.

For tiny apps, you may also consider choosing another, less strict language. Ruby, JavaScript - anything that allows you to write less code.




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

이전 코드는 거리에서 축구를하는 사람들이 많다는 것을 의미합니다. 같은 것 :

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

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; }
    }

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

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

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

이것은 당신의 논리가 그림에 어떻게 표시되는지입니다 ...




It depends on the behaviour of your "team" object. If it behaves just like a collection, it might be OK to represent it first with a plain List. Then you might start to notice that you keep duplicating code that iterates on the list; at this point you have the option of creating a FootballTeam object that wraps the list of players. The FootballTeam class becomes the home for all the code that iterates on the list of players.

It makes my code needlessly verbose. I must now call my_team.Players.Count instead of just my_team.Count. Thankfully, with C# I can define indexers to make indexing transparent, and forward all the methods of the internal List... But that's a lot of code! What do I get for all that work?

Encapsulation. Your clients need not know what goes on inside of FootballTeam. For all your clients know, it might be implemented by looking the list of players up in a database. They don't need to know, and this improves your design.

It just plain doesn't make any sense. A football team doesn't "have" a list of players. It is the list of players. You don't say "John McFootballer has joined SomeTeam's players". You say "John has joined SomeTeam". You don't add a letter to "a string's characters", you add a letter to a string. You don't add a book to a library's books, you add a book to a library.

Exactly :) you will say footballTeam.Add(john), not footballTeam.List.Add(john). The internal list will not be visible.




This reminds me of the "Is a" versus "has a" tradeoff. Sometimes it is easier and makesmore sense to inherit directly from a super class. Other times it makes more sense to create a standalone class and include the class you would have inherited from as a member variable. You can still access the functionality of the class but are not bound to the interface or any other constraints that might come from inheriting from the class.

Which do you do? As with a lot of things...it depends on the context. The guide I would use is that in order to inherit from another class there truly should be an "is a" relationship. So if you a writing a class called BMW, it could inherit from Car because a BMW truly is a car. A Horse class can inherit from the Mammal class because a horse actually is a mammal in real life and any Mammal functionality should be relevant to Horse. But can you say that a team is a list? From what I can tell, it does not seem like a Team really "is a" List. So in this case, I would have a List as a member variable.




What is the correct C# way of representing a data structure...

Remeber, "All models are wrong, but some are useful." - George EP Box

There is no a "correct way", only a useful one.

Choose one that is useful to you and/your users. 그게 전부 야. Develop economically, don't over-engineer. The less code you write, the less code you will need to debug. (read the following editions).

-- Edited

My best answer would be... it depends. Inheriting from a List would expose the clients of this class to methods that may be should not be exposed, primarily because FootballTeam looks like a business entity.

-- Edition 2

I sincerely don't remember to what I was referring on the “don't over-engineer” comment. While I believe the KISS mindset is a good guide, I want to emphasize that inheriting a business class from List would create more problems than it resolves, due abstraction leakage .

On the other hand, I believe there are a limited number of cases where simply to inherit from List is useful. As I wrote in the previous edition, it depends. The answer to each case is heavily influenced by both knowledge, experience and personal preferences.

Thanks to @kai for helping me to think more precisely about the answer.




While I don't have a complex comparison as most of these answers do, I would like to share my method for handling this situation. By extending IEnumerable<T> , you can allow your Team class to support Linq query extensions, without publicly exposing all the methods and properties of List<T> .

class Team : IEnumerable<Player>
{
    private readonly List<Player> playerList;

    public Team()
    {
        playerList = new List<Player>();
    }

    public Enumerator GetEnumerator()
    {
        return playerList.GetEnumerator();
    }

    ...
}

class Player
{
    ...
}



모두가 지적했듯이, 한 팀의 팀은 선수 명단이 아닙니다. 이 실수는 다양한 분야의 전문가에 의해 이루어집니다. 종종이 문제는 미묘하고 때때로 매우 심합니다. 이러한 디자인은 Liskov Substitution Principle을 위반하기 때문에 좋지 않습니다. 인터넷에는 http://en.wikipedia.org/wiki/Liskov_substitution_principle 이 개념을 설명하는 좋은 기사가 많이 있습니다 http://en.wikipedia.org/wiki/Liskov_substitution_principle

요약하면 클래스 간의 부모 / 자식 관계에는 두 가지 규칙이 유지됩니다.

  • 자녀는 부모를 완전히 정의하는 것보다 적은 특성을 요구해서는 안됩니다.
  • 부모는 아동을 완전히 정의하는 것 이외에 어떤 특성도 요구해서는 안됩니다.

즉, 학부모는 아동에 대해 필요한 정의이며, 아동은 학부모에 대한 충분한 정의입니다.

여기에 하나의 해결책을 생각해보고 그러한 실수를 피하는 데 도움이되는 위의 원칙을 적용하는 방법이 있습니다. 부모 클래스의 모든 연산이 구조적 및 의미 론적으로 파생 클래스에 대해 유효한지 검증함으로써 가설을 테스트해야합니다.

  • 축구 팀이 축구 선수 명단인가요? (목록의 모든 속성은 동일한 의미의 팀에 적용됩니다)
    • 팀은 동질적인 단체입니까? 예, 팀은 Players 컬렉션입니다.
    • 플레이어의 포함 순서는 팀의 상태를 설명하고 명시 적으로 변경하지 않으면 시퀀스가 ​​유지되는지 팀이 확인합니까? 아니요, 아니요.
    • 선수들은 팀의 연속적인 위치에 따라 포함 / 드롭 될 것으로 예상됩니까? 아니

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

이 시점에서 Team 클래스는 List를 사용하여 구현되지 않아야한다고 생각합니다. 대부분의 경우 Set 데이터 구조 (예 : HashSet)를 사용하여 구현해야합니다.




Let me rewrite your question. so you might see the subject from a different perspective.

When I need to represent a football team, I understand that it is basically a name. Like: "The Eagles"

string team = new string();

Then later I realized teams also have players.

Why can't I just extend the string type so that it also holds a list of players?

Your point of entry into the problem is arbitrary. Try to think what does a team have (properties), not what it is .

After you do that, you could see if it shares properties with other classes. And think about inheritance.



Related