Raja said:
[...]
Maybe I'm splitting hairs due to being influenced by Java, but I see a
difference between "inherits" (as a synonym for "is a") and "implements"
("has an aspect which is a").
IMHO, that "influence" exists only if one takes the words "inherits" and
"implements" too literally. The fact is, to implement an interface
should also mean the type "is a", not "has a". In the general
guidelines of OOP design, there's not a third option, "has an aspect
which is a".
In C#, this is even made explicit, via the "is" operator, which returns
"true" for both concrete types and interfaces. Note also that the world
of OOP is not limited to Java or C# where one is forced to use
interfaces in order to accomplish something like multiple inheritance,
and in other languages the line between inherited types and implemented
interfaces is much more blurry.
Finally, there is the school of thought in OOP that inheritance of
implementation (e.g. Java's "extends") should be _strictly_ a private
affair, with non-implementation interfaces being the only visible
"inheritance" for a type. IMHO, that's a perfectly valid approach to
designing an OOP language (C++, C#, and Java don't support that kind of
design…though, C++ comes closest with public and private inheritance),
and OOP concepts such as "is a" and "has a" should still apply in a
context like that, which they could not if you treat inheritance of an
interface differently from inheritance of an implementation in terms of
object identity.
As far as the specific example of XmlNode goes, it seems to me that
having XmlNode inherit and implement IEnumerable<XmlNode> is a
potentially practical departure from a rigid OOP design (as could be
argued for any implementation of IEnumerable<T> in a class that isn't
literally an enumeration itself, but which owns or otherwise is
associated with an enumeration).
But I also note that a) XmlNode in .NET was not upgraded with the advent
of generics to implement IEnumerable<XmlNode> (it does implement
IEnumerable though), as well as the fact that the newer XNode (or even
XContainer) type in System.Xml.Linq does _not_ implement either
enumeration interface.
This, in spite of the fact that LINQ is _all_ about enumerations and
System.Xml.Linq exists to support a LINQ-style approach to dealing with XML.
This suggests to me that while the original design of XmlNode was
informed by practicality but not necessarily good OOP design practices,
when it came time to designing the all-new System.Xml.Linq types, more
thought to the basic OOP concepts was being done, and they refrained
from the temptation of a potentially awkward inheritance design, opting
rather for a better OOP approach to the classes.
I wouldn't say it's a fatal flaw for a class to implement an interface
that strictly speaking it's not really "is a" rather than "has a", but I
would say it's not the best approach to a clean OOP design.
After all, consider:
class Person : IEnumerable<Person>, IEnumerable<Girl>,
IEnumerable<Boy>, IEnumerable<Car>, IEnumerable<House>
{
private List<Person> _children;
public IEnumerator<Person> GetEnumerator()
{
return _children.GetEnumerator();
}
public IEnumerator<Girl> GetEnumerator()
{
return _children.GetEnumerator()
.Where(child => child is Girl);
}
public IEnumerator<Boy> GetEnumerator()
{
return _children.GetEnumerator()
.Where(child => child is Boy);
}
}
If we allow IEnumerable<Person> as a legitimate interface for Person to
implement, then why not subsets of IEnumerable<Person> describing the
commonly used divisions of children? Why not other enumerations of
other things that a Person might _have_ rather than just what a Person
_is_? Where do you draw the line?
I agree that at times, pure OOP practices aren't the last word in type
design. But when one deviates from pure OOP practices in the
furtherance of some practical matter, one ought to at least be cognizant
that that's what they are doing, rather than trying to rationalize the
deviant design in terms of pure OOP practices.
IMHO, that will result in a much better type design than if one is
telling oneself that they are in fact following good, pure OOP
practices. Once you've rationalized some specific design as good, pure
OOP even though it's not, it becomes a lot easier for the rest of the
type to be poorly design, and to move on to designing other poorly
designed types. By remaining fully aware of when one is breaking the
rules, even if there's a good reason to do so, it remains a lot easier
to continue to follow the rules generally.
Pete