About pushBack:
Andreas Raab
andreas.raab at gmx.de
Sun Aug 5 23:32:37 UTC 2007
Jason Johnson wrote:
> I don't personally see traits as something that comes up that often.
> 99% of the time inheritance, or some OO pattern gives the best
> solution. But in that 1% something like a trait is the correct solution
> and not having it shows up a design wart.
I completely agree with this statement. And in return I *urge* you to
look at actual code and stand corrected ;-) Let's take an example:
Here is what the base class hierarchy in Nile looks like:
AbstractCollectionStream
ReadableCollectionStream
ByteReader
StringReader
WritableCollectionStream
ByteWriter
StringWriter
ReadWriteCollectionStream
Except from the -somewhat arbitrary- distinction between byte and string
reader it's pretty standard and looks (if you replace
AbstractCollectionStream with PositionableStream) fairly similar to what
we have today:
PositionableStream
ReadStream
WriteStream
ReadWriteStream
But now, let's look at how it works. Here is how the Nile class library
looks when we add the traits (written as <trait>):
AbstractCollectionStream
ReadableCollectionStream <ReadableCollectionStream - position,
setPosition, collection, setCollection:>
ByteReader <ByteReading - next, nextInto:, next:>
StringReader <CharacterReading>
WritableCollectionStream <NSTWriteableCollectionStream - position,
setPosition:, collection, setCollection:>
ByteWriter <ByteWriting - nextPut:, nextPutAll:>
StringWriter <CharacterWriting - nextPut:>
ReadWriteCollectionStream <ReadableCollectionStream - size,
collection, setCollection:, position, setPosition:>
and the traits hierarchy itself is here:
<ByteReading>
<ByteWriting>
<CharacterReading>
<CharacterWriting>
<CollectionStream>
<PositionableStream>
<GettableStream>
<Stream>
<PositionableStream>
<Stream>
<PuttableStream>
<Stream>
<ReadableCollectionStream>
<GettableStream @ basicBack -> back>
<CollectionStream>
<Stream>
<WriteableCollectionStream>
<PuttableStream>
<CollectionStream>
Now let's start counting: Instead of a single inheritance hierarchy with
8 entities (classes) that I need to know about I have 19 entities (8
classes, 11 traits) in an MI hierarchy. In addition we have nested
traits (so you'll have to know the other inheritance branches fairly
well), renames (ReadableCollectionStream) and exclusions (6 out of 8
classes have exclusions) complicating the structure even further. A more
realistic complexity measure (counting the number of occurrences of the
various entities) ends up with about 4x the complexity of the traits
version (which is certainly what it feels like).
So why would this be advantageous to a single inheritance hierarchy?
I'm pretty sure someone will come back and say "but wait! isn't the idea
that *other* clients will use those traits and in return will be simpler
and better and easier?" So let's look at this too:
I'll be looking at the Files hierarchy in Nile for the sole purpose that
it is the only comparable piece that has counter parts in Squeak today
and Squeak's files are certainly messy enough that they need a good
makeover. So here is the hierarchy for files in Nile (again without the
traits):
AbstractFileStream
AbstractBinaryStream
ReadFileBinaryStream
WriteFileBinaryStream
ReadWriteFileBinaryStream
AbstractTextStream
ReadFileTextStream
WriteFileTextStream
ReadWriteFileTextStream
The structure is ... shall we say unusual and (I think) heavily
influenced by the desire to create permutations of existing traits
instead of applying other OO design principles. And here is the version
with traits information included:
AbstractFileStream
AbstractBinaryStream
ReadFileBinaryStream <ReadFileStream - fileName, bufferType,
getFileID, setFileID: setFileName> <ByteReading>
WriteFileBinaryStream <WriteFileStream - fileName, bufferType,
getFileID, setFileID: setFileName> <ByteWriting>
ReadWriteFileBinaryStream <ReadFileStream - fileName, bufferType,
getFileID, setFileID: setFileName> <WriteFileStream - fileName,
bufferType, getFileID, setFileID: setFileName> <ByteWriting>
AbstractTextStream
ReadFileTextStream <ReadFileStream - fuleName, bufferType,
getFileID, setFileID: setFileName> <CharacterReading>
WriteFileTextStream <WriteFileStream - fileName, bufferType,
getFileID, setFileID: setFileName> <CharacterWriting>
ReadWriteFileTextStream <ReadFileStream - fileName, bufferType,
getFileID, setFileID: setFileName> <WriteFileStream - fileName,
bufferType, getFileID, setFileID: setFileName> <ByteWriting>
<FileStream>
<PositionableStream - isBinary>
<ReadFileStream>
<GettableStream - close, isBinary, isClosed>
<FileStream>
<WriteFileStream>
<PuttableStream - close, isBinary, isClosed>
<FileStream>
The amount of added complexity is mindboggling to me, and other than
AbstractFileStream being a subclass of Object there doesn't seem to be
*any* advantage of using traits. The disadvantages however, like the
explosion in the number of entities are very real (I think anyone who
has tried to understand the Squeak 3.9 and later meta class hierarchy
will agree).
So ultimately, I agree with your statement. There may be *some* uses for
traits, but those are few and far inbetween (and, realistically
speaking, nobody has found one yet). In the cases that we have seen so
far, traits are simply used as MI with all the same problems that MI
has. Nile certainly shows the symptoms (added complexity, conflicts)
that we've come to recognize as the hallmark of MI systems.
Cheers,
- Andreas
More information about the Squeak-dev
mailing list
|