6 ms·
Do you want to be able to pass in Team for every function that currently asks for List<T>? Because that is what you are stating through the act of subclassing.
by tolmasky 13y ago
Do you want to be able to pass in Team for every function that currently asks for List<T>? Because that is what you are stating through the act of subclassing. At some point some part of your program may have a List<T>, but actually have a Team.
For starters, most teams I know don't have cloning technology, and thus can't have the same member of a team multiple times, but List<T> absolutely allows this. There are no restrictions on uniqueness in a List<T>. So then you may proceed to override Add() to first check if the team member is already in the team:
void Add(Player aPlayer)
{
if (Contains(aPlayer))
return;
base.Add(aPlayer);
}
Wonderful! But now you're a List<T> by name only, because you don't actually behave like a List. You don't follow the most basic postcondition of the API. Every single method that adds an item to a List<T> expects the List<T> to be 1 item larger afterward. So basically, you've created a List<T> that has undefined behavior with every existing function that ever calls Add(), because it doesn't work the way it was originally sold as working, so what is the point of going around calling yourself a List? You are a crash waiting to happen. You are not a List, you are a list manager.
Now is when someone will probably say, no no no. Subclassing isn't the problem. The problem is you didn't subclass the right thing. You should have subclassed Set<T>. Because teams don't have a natural ordering and contain unique players. But now you're just going to run into new problems. Like the fact that you have to check if a player is first in a sane state before adding them:
void Add(Player aPlayer)
{
if (aPlayer.team != null)
return; // need to be a free agent!
base.Add(aPlayer);
}
We can argue whether this should throw, or whether it should do it unconditionally, but the point is that the virtue of needing logic means its not this thing, its something managing this thing. Someone already wrote Set<T> and List<T> code that was almost certainly smarter than you. You wouldn't go poking around their original source to make your program a tiny bit easier to write at risk of ruining other things, so subclassing it is just a way of cheating and only poking around their source only for specific instances, but still causing all the same potential problems for those instances.
Remember, you should ask yourself "Can I guarantee every function that expects a T to behave exactly the same way when I pass a P instead (where P is a subclass of T)?". Almost always the answer is NO, since that's why you subclassed it to begin with! So you shouldn't do it. If the answer is YES, then you've probably only added an ivar or new methods in which case its equivalent to composition. The only thing subclassing gives you over composition is the ability to modify the behavior of existing methods, which breaks contracts with every other part of the code.
- RyanZAG 13y agoYour whole argument seems to base around List<T> allowing for duplicate entries while a Team would not. I don't think this is a very good argument, because you could just extend from Set<T> instead which ensures uniqueness - but would still be the wrong choice.
- tolmasky 13y agoThis is a joke right? I literally cover this word for word. I think you read the first paragraph of my argument, stopped reading, rolled your eyes, and decided you understood my "whole argument".
- alcari 13y ago[n.b: I'm used to duck typed languages, where you can get away with far more shenanigans.] > "Can I guarantee every function that expects T to behave exactly the same way when I pass P (where P is a subclass of T)". Almost always the answer is NO, that's why you subclassed it to begin with! So you shouldn't do it. The entire point of subclassing is to be able to answer that question "no". By your logic, we should never subclass anything, because it will behave differently! It seems to me you're not really trying to argue against subclassing here, but rather against making specific container types subclasses of generic containers types with extra logic (that breaks the container's documented guarantees) added, instead of containing the logic and the container as class attributes. I'm reminded of the basic inheritance examples that show up all over, where Dog inherits from Animal: this makes sense because a Dog is a type of Animal. A Team is neither a type of List<T> nor a type of Set<T>, so it should not inherit from them. A team can, however, contain a roster represented by a List or Set alongside its other data.
- tolmasky 13y ago> The entire point of subclassing is to be able to answer that question "no". By your logic, we should never subclass anything, because it will behave differently! 100% Correct, that is indeed my argument: the very purpose of subclassing is contradictory. Subclassing exists solely to mask the mutation of existing behavior, that is the only feature it adds to composition. Composition is perfectly capable of ADDING and SUBTRACTING behavior, but to modify behavior it must do so transparently. You can't for example change the behavior of Add, you must call another method that internally calls Add on a child object and makes it explicit to the user code that the behavior is thus different. There is no point in having a type system if the types become meaningless through subclass manipulation. I make a class T that has method M with postcondition P. People write functions that accept T with these expectations. When you create a subclass of T that modifies the behavior of M, the existing functions can no longer be trusted to behave correctly -- this is just a fact. It's not at all just about "container classes". We see this broken behavior all the time in UI classes like UITableView extending UIView. UIView theoretically has a publicly mutable subviews array, but it stops being so by certain subclasses. So all of a sudden you have a UIView subclass that can't be passed in as an argument to functions with UIView parameters. This may be obvious to people familiar with the framework, but to novices, it now becomes meaningless to see the parameter UIView. Maybe it will do the right thing, maybe it won't, depends on the subclass. Now you have to go read the documentation (hopefully its documented). The reason inheritance is taught with ridiculous examples like Dog and Animal (or even worse, Triangle and Shape) is precisely because it is so hard to find legitimate examples. You always end up just overriding things like Speak() (which does nothing in Animal and prints "woof" in Dog). "Good" (re: concrete) examples are usually really convoluted and hard to understand (or end up being basically just interfaces like NSResponder which is 99% empty methods). Duck typing environments are saner in my opinion because they are really just interfaces. You are not making the strong statement "I am an X" (and thus creating the expectation of static behavior), but instead "I have loose behaviors A,B, and C" (in the same way as interfaces).