D以非耦合方式向类添加功能

时间:2012-01-01 14:30:05

标签: inheritance d hierarchy

我编写了一个名为Container的类来处理层次结构(对类用户可见)并将它们内部转换为平面数组。因此,对于Container的外部,它看起来像Container的层次结构,每个容器都有父节点和子节点。

我希望将此功能添加到某些类中。例如Widget类,它必须具有Container定义的相同功能。

我可以让Widget从Container继承。现在,Container被定义为具有this(),数据成员,成员函数,不变量和单元测试的类。容器包含一个容器数组,因此设计中有一个错误:如果Foobar还继承了Container并且我们将Foobar项添加到Widget的容器​​中怎么办?这一定是被禁止的。它们共享相同的基类,但它们根本不同,具有不同的目的......它们似乎只是共享一些功能。

将Container定义为接口是不可能的,因为它包含数据成员(并且不能解决问题)。将Container定义为mixin,因为我们也有this()功能(或者这将如何解决?)。 mixin中函数的可见性属性也不起作用。另外,我不能传递Widget类的this参数,因为它需要是平面数组的第一个元素。

我想给Container一个模板参数,告诉它它是什么容器:

abstract class Container(T)
{
    ...

    T[] elements;
}

class Widget: Container!Widget
{
}

这给出了一个错误:类容器.__ unittest2.Widget基类由Container正向引用。

你会如何实现这个?我还可以在Container中添加检查,以确保在添加子项时,它与父项具有相同的类型。但是我如何检查呢?

abstract class Container
{
    void add(Container child)
    {
        // pseudo-code
        assert (is(getFirstDerivedType(this) == getFirstDerivedType(child))); 

        ...
    }

    ...

    Container[] elements;
}

修改 即使第一段代码没有发出错误信号,它仍然无法真正解决问题。我不能随意添加更多功能,因为只允许一个基类。其他人需要是接口,这是根本不同的东西。接口确保派生类中存在某些功能,它们本身不会添加功能。

这应该用(模板)mixins来解决。但是mixins不能向构造函数添加代码(只有在未定义时才替换),不能将代码添加到不变量(多次定义不变),不能指定成员函数可见性或使用其他类/结构特定关键字......

3 个答案:

答案 0 :(得分:2)

更新:我不确定您的示例与此问题有什么关系。 D不允许您在修改类之外向类添加功能。

继承不允许扩展基类的功能,只修改它。

Mixins只允许您“混合”预定义的功能。同样,您无法使用它修改现有类。

你没有看过的一个选择是“别名”。这会创建一个合成关系,同样不允许修改现有类甚至包含它的那个。


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

或许我们需要的是做一些代码生成并使用字符串mixins。

最后一个选择是改变你的设计。不是创建修改不变量/构造函数等的mixin,而是创建一个mixin,它提供可由类的实现者调用的函数。 “混合MyContainer模板,然后在不变量中调用TestMe。”

对陈述的评论

  

设计中存在一个错误:如果Foobar还继承了Container并且我们将Foobar项添加到Widget的容器​​中怎么办?这一定是被禁止的。

然后不要在Container中放置一个Container数组。

class Widget {
    Widget[] elements;
}

请注意,在您使用模板的示例中,即使您正在使用它(它应该),Container!(Widget)与Container不同(!SomeClass)。如此有效地解决您的问题:

class ContainerWidget {
    Widget[] elements;
}

class ContainerSomeClass {
    SomeClass[] elements;
}

要正确支持平面阵列,您必须创建一个标记的容器,类似于标记的联合的工作方式。

答案 1 :(得分:2)

我认为你这是错误的做法。您正试图从内部向各种类添加功能,而从外部添加它会更有意义,即扩展它们。

容器应该包含Widget,而不是相反。然后,您可以使用alias this将非容器调用转发到包含的Widget。

这样的事情:

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

通过调用w1Widget,请注意您仍然可以像w1.m_x一样使用w1.foo()

答案 2 :(得分:1)

如果我是你,我会把Container变成一个小工具。无论如何,这就是在许多现有的GUI工具包中完成的,并且确实如此。