Буду добавлять функциональность к классам в несвязанном виде - PullRequest
4 голосов
/ 01 января 2012

Я написал класс с именем Container, который обрабатывает иерархии (видимые для пользователя класса) и конвертирует их внутренне в плоский массив.Поэтому для внешней части контейнера он выглядит как иерархия контейнеров, каждый из которых имеет родительские и дочерние узлы.

Эту функцию я хочу добавить к определенным классам.Например, класс Widget, он должен иметь ту же функциональность, определенную Container.

Я мог бы позволить Widget наследовать от Container.Контейнер теперь определен как класс с this (), членом данных, функциями-членами, инвариантом и тестами модулей.Контейнер содержит массив Контейнеров, поэтому в дизайне есть одна ошибка: что если Foobar также наследует Контейнер и мы добавим элементы Foobar в контейнер Виджета?Это должно быть запрещено.Они действительно используют один и тот же базовый класс, но это принципиально разные вещи с разными целями ... просто они, кажется, разделяют некоторые функции.

Определение контейнера как интерфейса невозможно, так как он содержит элементы данных (ине решает проблему).Определить контейнер как миксин тоже нельзя, поскольку у нас тоже есть функция this () (или как это сработает?).Атрибуты видимости для функций в миксинах тоже не работают.Кроме того, я не могу передать ему аргумент this класса Widget, так как это должен быть первый элемент плоского массива.

Я подумал о том, чтобы передать Container аргумент шаблона, сообщив ему, чтоКонтейнер имеет:

abstract class Container(T)
{
    ...

    T[] elements;
}

class Widget: Container!Widget
{
}

Это приводит к ошибке: class container .__ unittest2.Widget базовый класс напрямую ссылается Container.

Как бы вы это реализовали?Я также мог бы добавить проверки в Контейнер, который гарантирует, что при добавлении дочернего элемента он будет иметь тот же тип, что и родительский.Но как мне это проверить?

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

        ...
    }

    ...

    Container[] elements;
}

РЕДАКТИРОВАТЬ: Даже если первый фрагмент кода не сигнализирует об ошибке, он все равно не решает проблему.Я не могу произвольно добавить больше функциональности, поскольку разрешен только один базовый класс.Другие должны быть интерфейсами, которые принципиально разные вещи.Интерфейсы гарантируют, что в производном классе есть определенные функциональные возможности, они сами не добавляют функциональность.

Это должно быть решено с помощью (шаблона) миксинов.Но миксины не могут добавлять код в конструктор (только заменить, если не определен), не могут добавлять код в инвариант (определен несколько раз инвариант), не могут задавать видимость функции-члена или использовать другие ключевые слова, специфичные для класса / структуры ...

Ответы [ 3 ]

2 голосов
/ 04 января 2012

Я думаю, вы поступаете неправильно.Вы пытаетесь добавить функциональность к различным классам изнутри, тогда как было бы гораздо разумнее добавить их извне, то есть расширить их.

Контейнер должен содержать виджет, а не наоборотвокруг.Затем вы можете использовать alias 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());
}

Обратите внимание, как вы все еще можете использовать w1 как Widget позвонив w1.m_x и w1.foo().

2 голосов
/ 04 января 2012

Обновление : Я не уверен, что ваши примеры связаны с вопросом.D не позволяет вам добавлять функциональность к классу вне его модификации.

Наследование не позволяет расширять функциональные возможности базового класса, только изменять его.

Миксины позволяют только «смешивать» предопределенные функциональные возможности.Опять же, вы не можете изменить существующий класс с ним.

Одна опция, на которую вы не смотрели, это «псевдоним это».Это создает композиционные отношения, которые, опять же, не позволяют изменять существующий класс или даже тот, который его содержит.


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

Или, может быть, нам нужно выполнить генерацию кода ииспользуйте строковые миксины.

И последний вариант - изменить ваш дизайн.Вместо создания миксина, который изменяет инварианты / конструкторы и тому подобное, создайте миксин, который предоставляет функции, которые могут быть вызваны разработчиком класса.«Смешайте шаблон MyContainer, а затем вызовите TestMe в своем инварианте».

Комментарии к утверждениям :

В проекте есть одна ошибка: что если Foobarтакже наследует контейнер, и мы добавляем элементы Foobar в контейнер виджета?Это должно быть запрещено.

Тогда не помещайте массив Контейнеров в Контейнер.

class Widget {
    Widget[] elements;
}

Обратите внимание, что в вашем примере используются шаблоны, даже если вы работали с ним(и это должно быть), Container! (Widget) - это не тот же класс, что и Container! (SomeClass).Чтобы эффективно решить вашу проблему:

class ContainerWidget {
    Widget[] elements;
}

class ContainerSomeClass {
    SomeClass[] elements;
}

Для правильной поддержки плоского массива вам потребуется создать контейнер с тегами, аналогично тому, как будет работать теговый союз.

1 голос
/ 05 января 2012

На вашем месте я бы сделал Контейнер виджетом.Так или иначе это делается во многих существующих GUI-инструментах, и это имеет смысл.

...