OOP Aggregation

  • Thread starter Thread starter John Wood
  • Start date Start date
J

John Wood

Let's say you're provided with an instance of a class. The instantiation
takes place in another module that you have no control over.

However, you've extended that class with your own value-added functionality.

In C#, given such an instance, and a derived class, there's no way to
'attach' the instance to the class -- you have to either change the way the
class was instantiated (not possible in this case), or wrap the class and
delegate each function (a concept known as aggregation).

I'm sure I've asked this question on the group before, and I settled for
aggregation. But I just don't like it.

I was wondering if anyone had any thoughts on this... ideas of more elegant
work-arounds / design patterns... solutions in other languages etc.

Thanks.
 
Let's assume for a moment we were real IL-assembler cracks and could bend
the CLR in any way we wanted: We could (in theory) reallocate the instance
of the old class we're given to make room for the additional members in the
new class and modify the virtual function table pointer, and the interface
pointers and metadata pointers to point to the new classes ones. If I got
you right, that's roughly what you had in mind?
First of all: This is (almost) possible. You could simply allocate an
instance of the new class, and copy each member (using reflection) from the
old class into the new one. Modify all pointers to the old instance to point
to the new one (probably the ugliest part) and you're done.
There are major drawbacks in this approach, namely that it's hard to
identify all pointers to the old instance (think of locks or event
subscriptions) and that you don't really know the internal state of the
"old" object when you "apply" your new class.
For your second question: COM had a concept called "aggregation" in which a
COM object could expose some other object's interfaces transparently as if
they were it's own ones, btu I don't know how to recreate that in the .net
environment.
I think I read somewhere that Smalltalk supported something like you
suggested, but that's all i know about it - maybe you can get some ideas
from looking at that language.
IMO the idea behind a OOP language *is* to restrict what is possible on
objects/classes/interfaces; Otherwise we could all build C structures with
function pointers - you could do *anything* with those...

That said, the decorator pattern (which you described) is probably by far
the most common and reliable solution to this problem.

Niki
 
Thanks for your thoughts.

A long time ago I implemented something very hacky in COM where I would
synthetically add support for new interfaces into an existing instance (that
didn't support aggregation). Sounds similar to your suggestion re
reflection... I think I've matured enough not to try anything like that
again! :)

It's true, the decorator pattern is the most common (and probably best)
solution available today -- but it does seem like a common and legitimate
problem that would warrant native support in a popular language such as C#.

I really don't like the idea of manually delegating each method, because (a)
you lose the ability to cast to base classes of the original implementation
and (b) you have to maintain those delegates and keep the, in sync.

In my mind, it's one of the biggest stumbling blocks of an extensible object
oriented system. You create classes, you want to allow other vendors to
'plug-in' their extensions in the form of derived classes so they can
override various bits of functionality, but the 'plugging in' doesn't occur
until after instantiation when it's too late.

But there just isn't a way to do this neatly in C# (or C++ for that matter).
I think we really need native support for the decorator pattern in C#.
 
They probably thought that many common applications of the decorator pattern
could be easily solved using events. (Add functionality at runtime by
overriding some event) Also, delegates are a lot cleaner and simpler than
good ol' function pointers, and if your "mother objects" contained
replaceable delegates instead of virtual functions, the kind of
"runtime-inheritance" you had in mind would be easily possible. If the
object's clients only access the object by a well-defined set of interfaces,
you could only replace one of those interfaces, leaving the others
unchanged, and introduce new behaviour this way.
Of course, all these solutions (which are the typical C# solutions) require
provisions on the parent class side, and some special design on the client
side.
Thanks to IL it's quite easy nowadays to create code at runtime, so creating
some proxy class on-the-fly from metadata would be entirely possible, too.

So, in the end I still think you have plenty of options to choose from; And
I do remember good old windows-message-handlers that did all kinds of "dirty
tricks" to re-route messages at runtime. That is, I do remember all those
debugging sessions...

Niki
 
Yeah events are an option, but that sounds awfully like another
manifestation of the decorator pattern. Although it does have the benefit of
multicasting, but then I suppose you have to worry about the order in which
the delegates are invoked and such.

I agree that creating the proxy at runtime is an option -- and probably what
..net languages like Smalltalk and Eiffel would do to emulate some of the OOP
features not directly supported in the CLR.

Also, another idea I had, would be to use the contextboundobject features of
the CLR, intercepting method calls and giving 'plugins' a chance to handle
the method. The only problem is that the whole remoting infrastructure adds
significant performance penalties (method calls become something like 100
times slower), and wouldn't work on MBV objects, so it's not really an
option in serious apps.

And the biggest problem I have with the decorator pattern is that you lose
all of the class hierarchy parenthood of the object that the proxy is
encapsulating, so it's hardly a transparent solution.

Maybe I'll investigate some of the runtime reflection/codedom features to
see what kind of runtime options I might have for doing this
programmatically.

Thanks.
 
Priceless words of wisdom there.

--
John Wood
EMail: first name, dot, last name, at priorganize.com

Beeeeeeeeeeeeves said:
The solution is normally to manhandle the module that you have "no
control" over, such that you DO have control over it.
 
Back
Top