I have a problem with D
Adam Sansier via Digitalmars-d
digitalmars-d at puremagic.com
Mon Jun 27 21:37:34 PDT 2016
Hi,
I have designed a class based system that involves
self-delegation instead of override.
It is similar to event based programming.
I have defined an event as a container type that holds
functions(or possibly delegates, but the desire is to avoid them).
class Base
{
alias EventMethod = void function(Base _this);
public Event!EventMethod MyEvents
public MyEvent()
{
// Go ahead and inform subscribed handlers
MyEvents(this);
// Do other stuff here
}
}
The outside world can attach their own method to the event as
normal. They act as effective members to Base but with only
access to public members.
Now, the normal workflow in OOP is to derive from Base
class Derived : Base
{
public override MyEvent()
{
// must call super.TriggerMyEvents for design to work, else
events won't be called. This is dangerous
// Other work done here.
}
}
To prevent the dangerous scenario of the deriving user from not
calling the base class method, the workflow is changed to
class Derived : Base
{
public static myEvent(Derived _this)
{
// Other work done here
}
this()
{
// Subscribe to Base's MyEvent
MyEvents += &myEvent;
}
}
The idea is that the Derived type tries to behave as much as
possible like a non-derived type when it can. This separates
certain behaviors from the base class. It helps in other areas of
the design(because then those behaviors can be reused since they
do not directly depend on the base type). It also free's up the
methods to be overridable if necessary without breaking the event
processing, since now the event processing is mainly done through
subscriber model. Also, The derived class or anyone else can now
unhook it's own event handling. If we need to temporarily
"un-override" some behavior, we cannot do this in the inheritance
model without mucking with the VTable which is dangerous.
Problems with this setup that are unfortunate but seem to be due
to language limitations and no fundamental reasons:
If myEvent is static:
1. myEvent cannot use this, but _this is functionally the same.
We have to prepend every access to the object's members with
_this. It becomes very ugly code.
2. We also can only access public members because it is not a
dynamic member. Yet it is clear that myEvent, being defined
inside the class and having a _this ptr is meant to have full
access like any normal member.
If myEvent is non-static:
1. Since Event only takes functions, we cannot directly add to
it. There are some nasty hacks that seem to work but they have
created other problems in the design.
2. It then invokes the GC even though there is no reason this is
required. The design supplies the this ptr.
---
One has an either or situation here and neither are desirable.
It seems that D can do better.
1. Allow for module level, static member functions with a special
_this parameter to act as a in between a function and a delegate.
this = _this inside the body and has the class/struct type. The
only difference between this an C++ delegates is that _this is
explicitly passable.
This avoids the need to prefix _this to every member access.
2. Allow for C++ delegate style method. This is essentially 1 but
hides the explicit passing of _this. It can be hacked though.
3. Allow access to private members of the enclosing type. For all
practical purposes, it is part of the class construction and does
not need to have the members hidden from it. It is both inside
the module and inside the type and has a this type of parameter.
It should be a first class citizen then.
4. Allow these new method types to be virtual. This is a bit of
complexity that might not be desirable at this point though, but
would allow them to be extended instead of acting like static or
final methods.
The goal being to allow for derived types to participate in the
oop hierarchy as general types do but to have special privileges.
Maybe only protected members could be accessed by _this to allow
for some level of control(but somewhat meaningless I believe).
These functions should be implicitly convertible to a function
with first parameter of the class/struct type.
Maybe there is a cleaner way to accomplish this with templates
though. I believe it essentially requires the assignment to this
though, which I believe is illegal?
import std.stdio;
class Event(T, P)
{
T[] callbacks;
void opCall(P p)
{
foreach(e; callbacks)
e(p);
}
void opOpAssign(string op, T)(T c)
{
static if (op == "~")
callbacks ~= c;
}
}
class Base
{
string test = "This is a test, only a test!";
alias EventMethod = void function(Base _this);
//alias EventMethod = void delegate(Base _this);
auto MyEvents = new Event!(EventMethod, Base)();
void MyEvent()
{
MyEvents(this);
}
void MyEventTrigger()
{
MyEvents(this);
}
}
class Derived : Base
{
this()
{
MyEvents ~= cast(void function(Base
_this))(cast(void*)&myEventHandler); // Notice the ugly hack to
insert our event hanlder but works
//MyEvents ~= cast(void delegate(Base
_this))(cast(void*)&myEventHandler); // can't cast!
depreciated
}
override void MyEvent()
{
// Oops, what if we forgot to call base or did something hokey?
}
// Lets keep things separated
static void myEventHandler(Derived _this) // If not static and
function events used, crashes.
{
writeln("Hello D-World! " ~ _this.test);
}
}
int main(string[] argv)
{
auto d = new Derived();
d.MyEvent(); // Oops, nothing!!!
d.MyEventTrigger(); // Better!
return 0;
}
More information about the Digitalmars-d
mailing list