Reputation: 1758
I wrote a class called Container which handles hierarchies (as visible to the class user) and converts them internally to a flat array. So for the outside of Container it looks like a hierarchy of Containers, each with parent and child nodes.
This functionality I want to add to certain classes. For example the Widget class, it must have that same functionality defined by Container.
I could let Widget inherit from Container. Container is now defined as a class with this(), data member, member functions, invariant and unittests. Container contains an array of Containers, so there is one fault in the design: what if Foobar also inherits Container and we add Foobar items to the Widget's container? That must be forbidden. They do share the same base class, but they are fundamentally different things with different purposes...they just seem to share some functionality.
Defining Container as an interface is not possible, since it contains data members (and doesn't solve the problem). Defining Container as mixin neither, since we have this() functionality too (or how would this work out?). Visibility attributes for functions in mixins doesn't work either. And additionally, I can't pass it the this
argument of the Widget class, for that needs to be the first element of the flat array.
I thought of giving Container a template argument, telling it what container it is of:
abstract class Container(T)
{
...
T[] elements;
}
class Widget: Container!Widget
{
}
This gives an error: class container.__unittest2.Widget base class is forward referenced by Container.
How would you implement this? I could also add checks in Container that makes sure that when a child is added, it has the same type as the parent. But how do I check that?
abstract class Container
{
void add(Container child)
{
// pseudo-code
assert (is(getFirstDerivedType(this) == getFirstDerivedType(child)));
...
}
...
Container[] elements;
}
EDIT: Even if the first piece of code does not signal an error, it still doesn't really solve the problem. I cannot arbitrarily add more functionality since only one base class is allowed. The others need to be interfaces, which are fundamentally different things. Interfaces ensure there is certain functionality in the derived class, they don't add the functionality themselves.
This is supposed to be solved with (template) mixins. But mixins cannot add code to the constructor (only replace if not defined), cannot add code to invariant (invariant multiple times defined), cannot specify member function visibility or use other class/struct specific keywords...
Upvotes: 4
Views: 311
Reputation: 54270
I think you are going about this the wrong way. You are trying to add functionality to a variety of classes from the inside, whereas it would make much more sense to add it from the outside, i.e. extend them.
The Container should contain the Widget, not the other way around. You can then use alias this
to forward non-container calls to the contained Widget.
Something like this:
import std.stdio;
class Container(T)
{
public:
this(T elem) { m_this = elem; add(this); }
void add(Container elem) { m_elems ~= elem; }
alias m_this this;
private:
T m_this;
Container m_elems[];
}
class Widget
{
public:
this(int x) { this.m_x = x; }
int foo() { return m_x; }
int m_x;
}
alias Container!Widget CWidget;
void main()
{
CWidget w1 = new CWidget(new Widget(1));
CWidget w2 = new CWidget(new Widget(2));
w1.add(w2);
writeln(w1.m_x);
writeln(w1.foo());
}
Notice how you can still use w1
like a Widget
by calling w1.m_x
and w1.foo()
.
Upvotes: 2
Reputation: 6774
Update: I'm not sure what your examples have to do with the question. D does not allow you to add functionality to a class outside of modifying the class.
Inheritance doesn't let you extend functionality of the base class, only modify it.
Mixins only let you "mix in" pre-defined functionality. Again, you can't modify an existing class with it.
One option you haven't looked at is "alias this." This creates a composition relationship, which again, does not allow modifying the existing class or even the one containing it.
import std.stdio;
struct B
{
int p, q, r, s;
}
struct A
{
B b;
alias b this;
void foo() {
p = 6;
}
}
void main()
{
A a;
a.foo();
writeln(a.p);
}
Or maybe what we need is to do some code generation and use string mixins.
And the last option is change your design. Instead of creating a mixin that modifies invariants/constructors and the likes, create a mixin that provides functions which can be called by the implementer of the class. "Mixin the MyContainer template and then call TestMe in your invariant."
Comments on statements:
there is one fault in the design: what if Foobar also inherits Container and we add Foobar items to the Widget's container? That must be forbidden.
Then don't put an array of Containers in Container.
class Widget {
Widget[] elements;
}
Note that in your the example using templates, even if you it was working (and it should), Container!(Widget) is not the same class as Container!(SomeClass). So effectively to solve your problem:
class ContainerWidget {
Widget[] elements;
}
class ContainerSomeClass {
SomeClass[] elements;
}
To properly support a flat array you would have to create a tagged Container similar to how a tagged union would work.
Upvotes: 2
Reputation: 19797
If I were you, I would make Container be a Widget. That is how it is done in many existing GUI toolkits anyway, and it does make sense to be so.
Upvotes: 1