If we want to access base members from the target class in the mixin class, we must use the generic Mixin<TThis, TBase>
type, i.e. we must provide a parameter for TBase
.
As the wiki page
re-motion mixins basics -- the generic 'Mixin' type
explains, "for technical reasons" you are constrained to pass an interface as TBase
parameter. This wiki page explains this constrain and how data is organized at run-time.
As soon as TBase
enters the picture, we face a complication, because then the mixed class ("Parrot_Mixed_SOM3GUID") can have two versions of each target method:
Say (string s)
Base.Say (string s)
when seen from the mixin class)Now look at this code snippet:
public void PretendToTalkOnThePhone () { This.Say ("Halloh?"); // overridden 'Say' Base.Say ("Hi!"); // original (base) 'Say' } |
Clearly, Base
and This
can't hold a reference to the same instance of the mixed class, because that would make the overridden Say ()
the same as the original (base) Say ()
.
So Base
holds a reference to a different instance – a so-called base call proxy. Another synthesized class enters the UML picture, in this example Parrot_BaseCallProxy_SOM3GUID
(labeled "Parrot_BaseCallProxy..." in the illustration).
It is an inner class to Parrot_Mixed_SOM3GUID
and holds a reference to its nesting class. Remember that you can't pass classes as a TBase
parameter to TThis
, only interfaces. Parrot_BaseCallProxy_SOM3GUID
implements IParrot
, which in turn was passed as second parameter to Mixin<TThis, TBase>
for this diagram:
The most important points in the diagram are:
OnThePhoneMixin
holds a reference to Parrot_BaseCallProxy_SOM3GUID
(labeled "Parrot_BaseCallProxy..." in the illustration) in Base
Parrot_BaseCallProxy_SOM3GUID
holds a reference to the mixed class Parrot_Mixed_SOM3GUID
(labeled "Parrot_SOM3GUID")Parrot_BaseCallProxy_SOM3GUID
implements IParrot
(note that, in this diagram, Parrot
or Parrot_Mixed_SOM3GUID
do not}}Parrot_Mixed_SOM3GUID
has been endowed with a Base_Say
methodA call OnThePhoneMixin
's PretendToTalkOnThePhone ()
to Base.Say
will eventually call Parrot
's Say ()
. Here is a walk-through for how this works.
Base.Say ("Halloh?")
delegates to the Say ()
method in Parrot_BaseCallProxy_SOM3GUID
Parrot_BaseCallProxy_SOM3GUID
's Say ()
method delegates to Base_Say ()
in Parrot_Mixed_SOM3GUID
Parrot_Mixed_SOM3GUID
's Say ()
delegates to Parrot
's Say ()
via base.Say ()
(note the lowercase 'b' in base.Say ()
– not a typo; Parrot_Mixed_SOM3GUID
calls its base-class' Say ()
Why can't Parrot_Mixed_SOM3GUID
's own Say ()
method be called here? Simple: that's the Say ()
overridden by the mixin class – the one called by This.Say ()
.
As promised in the opening of this wiki page, a word on the technical reasons why you can't pass a class as type parameter to Mixin<TThis, TBase>
. The problem is that you basically need two instances to delegate to – one for the Base
property, one for the This
property. Two instances mean two instantiations, and instantiations can have side-effects (dumping to the screen, opening a socket, new thread, etc.) If the ObjectFactory
would cause two such side effects for each instantiation of the desired target class, the programmer using mixins could get into trouble and would have a hard time figuring out why. Having an ObjectFactory
create two instances instead of one is more tasteless prank than sound engineering, so that's a no-go. The base-call proxy class synthesized at run-time has no side-effects upon instantiation, and no state. It simply steals the interface signatures (IParrot
, in this example) and delegates every method (and every property) implementation to the mixed class – either to the corresponding method (if no call to a base method is needed) or the corresponding Base_X
method.
Note that the construction of the two implementor classes works differently for each interface if you pass TWO interfaces to Mixin<TThis, TBase>
, as in Mixin<IParrot, IParrot>
, for example. This call is equivalent to Mixin<Parrot, IParrot>
, because re-motion mixin is smart enough to do the right thing here:
IParrot
, the object factory finds Parrot
as the implementor and subclasses it to the mixed classIParrot
, the object factory generates the base call proxy with no life of its own, delegating everything to the mixed class