D adding functionality to classes in an unrelated way

I wrote a class called Container that processes hierarchies (as classes are visible to the user) and converts them internally to a flat array. Thus, for the outside of the container, it looks like a hierarchy of containers, each with parent and child nodes.

This is the functionality that I want to add to specific classes. For example, the Widget class, it must have the same functionality as in the Container.

I could let Widget inherit from Container. A container is now defined as a class with this (), data member, member functions, invariant, and unittests. A container contains an array of containers, so there is one mistake in the design: what if Foobar also inherits Container, and we add Foobar elements to the Widget container? This should be prohibited. They share the same base class, but they are fundamentally different things with different goals ... they just seem to have some functionality.

Defining a container as an interface is not possible because it contains data members (and does not solve the problem). There is no definition of Container as mixin, since we have this () functionality too (or how will it work out?). Visibility attributes for functions in mixins also do not work. Also, I cannot pass it the this argument to the Widget class, because it must be the first element of a flat array.

I was thinking of giving Container a template argument, specifying in which container it is:

 abstract class Container(T) { ... T[] elements; } class Widget: Container!Widget { } 

This gives an error: the container of the class .__ unittest2.Widget base class is redirected to the container.

How would you implement this? I could also add checks to the Container, which ensures that when the child is added, it is of the same type as the parent. But how can I check this?

 abstract class Container { void add(Container child) { // pseudo-code assert (is(getFirstDerivedType(this) == getFirstDerivedType(child))); ... } ... Container[] elements; } 

EDIT: Even if the first part of the code does not signal an error, it still does not solve the problem. I cannot arbitrarily add more functionality, since only one base class is allowed. The rest should be interfaces that are fundamentally different from each other. Interfaces guarantee that the derived class has certain functionality; they do not add the functionality itself.

This should be allowed with (template) mixes. But mixins cannot add code to the constructor (only replace if not defined), cannot add code to invariant (invariant with several temporary values), cannot indicate the visibility of a member function or use other class / structure keywords ...

+4
source share
3 answers

I think you are going wrong on this. You are trying to add functionality to many classes from the inside, while it would be much more reasonable to add them from the outside, i.e. Expand them.

The container must contain the Widget, and not vice versa. You can then use alias this to forward non-container calls to the contained widget.

Something like that:

 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()); } 

Note that you can use w1 as a Widget by calling w1.m_x and w1.foo() .

+2
source

Update . I'm not sure your examples are related to the question. D does not allow adding functionality to a class outside of a class modification.

Inheritance does not allow expanding the functionality of the base class, but only changing it.

Mixins only allows you to “mix” predefined functions. Again, you cannot modify an existing class with it.

One option that you have not considered is a "pseudonym." This creates a compositional relationship that again prevents you from modifying an existing class, or even one that contains 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(ap); } 

Or maybe we need to do some code generation and use line mixes.

And the last option is to change your design. Instead of creating a mixin that modifies invariants / constructors and the like, create a mixin that provides functions that can be called by the class developer. Msgstr "Mix the MyContainer template and then call TestMe in your invariant."

Comments on the operators :

there is one mistake in the design: what if Foobar also inherits Container and we add Foobar elements to the Widget container? This should be prohibited.

Then do not put an array of containers in the container.

 class Widget { Widget[] elements; } 

Please note that in your example using templates, even if you worked (and it should be), Container! (Widget) is not the same class as Container! (SomeClass). So effectively solve your problem:

 class ContainerWidget { Widget[] elements; } class ContainerSomeClass { SomeClass[] elements; } 

To properly support a flat array, you need to create a tagged container similar to how the union tag works.

+2
source

If I were you, I would do Container be Widget. One way or another, this is done in many existing GUI toolkits, and it makes sense to be so.

+1
source

Source: https://habr.com/ru/post/1388804/


All Articles