Why not inherit from List?

前端 未结 27 1700
悲哀的现实
悲哀的现实 2020-11-21 05:39

When planning out my programs, I often start with a chain of thought like so:

A football team is just a list of football players. Therefore, I should

相关标签:
27条回答
  • 2020-11-21 05:58

    There are a lot excellent answers here, but I want to touch on something I didn't see mentioned: Object oriented design is about empowering objects.

    You want to encapsulate all your rules, additional work and internal details inside an appropriate object. In this way other objects interacting with this one don't have to worry about it all. In fact, you want to go a step further and actively prevent other objects from bypassing these internals.

    When you inherit from List, all other objects can see you as a List. They have direct access to the methods for adding and removing players. And you'll have lost your control; for example:

    Suppose you want to differentiate when a player leaves by knowing whether they retired, resigned or were fired. You could implement a RemovePlayer method that takes an appropriate input enum. However, by inheriting from List, you would be unable to prevent direct access to Remove, RemoveAll and even Clear. As a result, you've actually disempowered your FootballTeam class.


    Additional thoughts on encapsulation... You raised the following concern:

    It makes my code needlessly verbose. I must now call my_team.Players.Count instead of just my_team.Count.

    You're correct, that would be needlessly verbose for all clients to use you team. However, that problem is very small in comparison to the fact that you've exposed List Players to all and sundry so they can fiddle with your team without your consent.

    You go on to say:

    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're wrong about the first bit: Drop the word 'list', and it's actually obvious that a team does have players.
    However, you hit the nail on the head with the second. You don't want clients calling ateam.Players.Add(...). You do want them calling ateam.AddPlayer(...). And your implemention would (possibly amongst other things) call Players.Add(...) internally.


    Hopefully you can see how important encapsulation is to the objective of empowering your objects. You want to allow each class to do its job well without fear of interference from other objects.

    0 讨论(0)
  • 2020-11-21 05:58

    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.

    0 讨论(0)
  • 2020-11-21 05:58

    My dirty secret: I don't care what people say, and I do it. .NET Framework is spread with "XxxxCollection" (UIElementCollection for top of my head example).

    So what stops me saying:

    team.Players.ByName("Nicolas")
    

    When I find it better than

    team.ByName("Nicolas")
    

    Moreover, my PlayerCollection might be used by other class, like "Club" without any code duplication.

    club.Players.ByName("Nicolas")
    

    Best practices of yesterday, might not be the one of tomorrow. There is no reason behind most best practices, most are only wide agreement among the community. Instead of asking the community if it will blame you when you do that ask yourself, what is more readable and maintainable?

    team.Players.ByName("Nicolas") 
    

    or

    team.ByName("Nicolas")
    

    Really. Do you have any doubt? Now maybe you need to play with other technical constraints that prevent you to use List<T> in your real use case. But don't add a constraint that should not exist. If Microsoft did not document the why, then it is surely a "best practice" coming from nowhere.

    0 讨论(0)
  • 2020-11-21 05:58

    I just wanted to add that Bertrand Meyer, the inventor of Eiffel and design by contract, would have Team inherit from List<Player> without so much as batting an eyelid.

    In his book, Object-Oriented Software Construction, he discusses the implementation of a GUI system where rectangular windows can have child windows. He simply has Window inherit from both Rectangle and Tree<Window> to reuse the implementation.

    However, C# is not Eiffel. The latter supports multiple inheritance and renaming of features. In C#, when you subclass, you inherit both the interface and the implemenation. You can override the implementation, but the calling conventions are copied directly from the superclass. In Eiffel, however, you can modify the names of the public methods, so you can rename Add and Remove to Hire and Fire in your Team. If an instance of Team is upcast back to List<Player>, the caller will use Add and Remove to modify it, but your virtual methods Hire and Fire will be called.

    0 讨论(0)
  • 2020-11-21 06:01

    As everyone has pointed out, a team of players is not a list of players. This mistake is made by many people everywhere, perhaps at various levels of expertise. Often the problem is subtle and occasionally very gross, as in this case. Such designs are bad because these violate the Liskov Substitution Principle. The internet has many good articles explaining this concept e.g., http://en.wikipedia.org/wiki/Liskov_substitution_principle

    In summary, there are two rules to be preserved in a Parent/Child relationship among classes:

    • a Child should require no characteristic less than what completely defines the Parent.
    • a Parent should require no characteristic in addition to what completely defines the Child.

    In other words, a Parent is a necessary definition of a child, and a child is a sufficient definition of a Parent.

    Here is a way to think through ones solution and apply the above principle that should help one avoid such a mistake. One should test ones hypothesis by verifying if all the operations of a parent class are valid for the derived class both structurally and semantically.

    • Is a football team a list of football players? ( Do all properties of a list apply to a team in the same meaning)
      • Is a team a collection of homogenous entities? Yes, team is a collection of Players
      • Is the order of inclusion of players descriptive of the state of the team and does the team ensure that the sequence is preserved unless explicitly changed? No, and No
      • Are players expected to be included/dropped based on their sequencial position in the team? No

    As you see, only the first characteristic of a list is applicable to a team. Hence a team is not a list. A list would be a implementation detail of how you manage your team, so it should only be used to store the player objects and be manipulated with methods of Team class.

    At this point I'd like to remark that a Team class should, in my opinion, not even be implemented using a List; it should be implemented using a Set data structure (HashSet, for example) in most cases.

    0 讨论(0)
  • 2020-11-21 06:02

    What if the FootballTeam has a reserves team along with the main team?

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

    How would you model that with?

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

    The relationship is clearly has a and not is a.

    or RetiredPlayers?

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

    As a rule of thumb, if you ever want to inherit from a collection, name the class SomethingCollection.

    Does your SomethingCollection semantically make sense? Only do this if your type is a collection of Something.

    In the case of FootballTeam it doesn't sound right. A Team is more than a Collection. A Team can have coaches, trainers, etc as the other answers have pointed out.

    FootballCollection sounds like a collection of footballs or maybe a collection of football paraphernalia. TeamCollection, a collection of teams.

    FootballPlayerCollection sounds like a collection of players which would be a valid name for a class that inherits from List<FootballPlayer> if you really wanted to do that.

    Really List<FootballPlayer> is a perfectly good type to deal with. Maybe IList<FootballPlayer> if you are returning it from a method.

    In summary

    Ask yourself

    1. Is X a Y? or Has X a Y?

    2. Do my class names mean what they are?

    0 讨论(0)
提交回复
热议问题