Подтвердить что ты не робот

Какие хорошие альтернативы множественному наследованию в .NET?

У меня возникла проблема с моей иерархией классов в приложении WPF. Это одна из тех проблем, при которой у вас есть два дерева наследования, объединяющихся друг с другом, и вы не можете найти логический способ сделать ваше наследование гладким без множественного наследования. Мне интересно, есть ли у кого-нибудь яркие идеи для работы такой системы, не делая этого невозможным для отслеживания или отладки.

Я инженер низкого уровня, поэтому моя первая мысль всегда: "О, я просто напишу некоторые из этих классов в родном С++ и ссылаюсь на них извне! Тогда я могу получить всю мою старую школу OO весело!" Увы, это не поможет, когда вам нужно наследовать от управляемых элементов управления...

Позвольте мне показать фрагмент моей текущей проектной диаграммы классов:

 ____________________________________      _____________________________________
| CustomizableObject                 |    | System.Windows.Controls.UserControl |
|____________________________________|    |_____________________________________|
|   string XAMLHeader()              |                        ▲
|   string XAMLFooter()              |◄--┐                    |
|   CustomizableObject LoadObject()  |   \                    |
|   <Possible other implementations> |    \                   |
|____________________________________|     \                  |
         ▲                      ▲           \                 |
         |                      |            \                |
         |                      |             \               |
 _________________    ______________________   \    _____________________
| SpriteAnimation |  | SpriteAnimationFrame |  └---| CustomizableControl |
|_________________|  |______________________|      |_____________________|
                                                      ▲             ▲
                                                      |             |
                                                      |             |
                                                  ________    _____________
                                                 | Sprite |  | SpriteFrame |
                                                 |________|  |_____________|

Проблема довольно ясна: разделение дерева объектов CustomizableObject и CustomizableControl --- и вставка UserControl в один, но не оба из деревьев.

Не имеет смысла перемещать реализацию CustomizableObject в свои производные классы, поскольку реализация не зависит от класса. Кроме того, было бы очень сложно использовать его несколько раз. Поэтому я действительно не хочу, чтобы интерфейс CustomizableObject. Решение для интерфейса не имеет для меня никакого смысла. (Интерфейсы никогда действительно имели для меня смысл, если честно...)

Итак, я говорю еще раз, у кого есть какие-то яркие идеи? Это настоящий рассол. Я хотел бы узнать больше о том, как заставить интерфейсы работать с моим деревом объектов, а не против него. Я делаю этот простой сценарий sprite с помощью WPF и С# в качестве прочного упражнения, больше всего на свете. Это так легко решить на С++, но мне нужно выяснить, как решить эти проблемы в управляемой среде, а не бросать мои руки в воздух и бежать обратно к Win32 всякий раз, когда становится жестким.

4b9b3361

Ответ 1

Один из подходов заключается в использовании методов расширения с интерфейсом для реализации реализации "производного класса", подобно System.Linq.Queryable:

interface ICustomizableObject
{
    string SomeProperty { get; }
}

public static class CustomizableObject
{
    public static string GetXamlHeader(this ICustomizableObject obj)
    {
        return DoSomethingWith(obj.SomeProperty);
    }

    // etc
}

public class CustomizableControl : System.Windows.Controls.UserControl, ICustomizableObject
{
    public string SomeProperty { get { return "Whatever"; } }
}

Использование. До тех пор, пока у вас есть директива using (или находится в том же пространстве имен как) пространство имен, где определены методы расширения:

var cc = new CustomizableControl();
var header = cc.GetXamlHeader();

Ответ 2

У вас есть два варианта: использовать интерфейсы или использовать композицию. Честно говоря, интерфейсы очень мощные, и после прочтения этой строки

Решение интерфейса не имеет для меня никакого смысла. (Интерфейсы никогда не имели для меня особого смысла, если честно...)

Я думаю, что вы должны научиться правильно их использовать. Тем не менее, если есть просто какая-то логика, которой нужен несколько классов, но для этих классов не имеет смысла наследовать от одного и того же базового класса, просто создайте класс для инкапсуляции этой логики и добавьте переменную-член этого класса в ваши классы которые дают вам проблемы. Таким образом, все классы содержат логику, но могут быть раздельными в своих иерархиях наследования. Если классы должны реализовывать общий интерфейс, используйте интерфейсы.

Ответ 3

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

Вы можете добиться этого через делегатов. Я не уверен, какие именно члены вас беспокоят, но, возможно, что-то вроде этого:

 ____________________________________      _____________________________________
| ICustomizable                      |    | System.Windows.Controls.UserControl |
|                                    |    |_____________________________________|
|   Func<string> XAMLHeader;         |                        ▲
|   Func<string> XAMLFooter          |◄--┐                    |
|   ICustomizabl LoadObject() |   \                    |
|   <Possible other implementations> |    \                   |
|____________________________________|     \                  |
         ▲                      ▲           \                 |
         |                      |            \                |
         |                      |             \               |
 _________________    ______________________   \    _____________________
| SpriteAnimation |  | SpriteAnimationFrame |  └---| CustomizableControl |
|_________________|  |______________________|      |_____________________|
                                                      ▲             ▲
                                                      |             |
                                                      |             |
                                                  ________    _____________
                                                 | Sprite |  | SpriteFrame |
                                                 |________|  |_____________|

Кроме того, у вас, вероятно, есть некоторая логика, которая по-настоящему статична, что вы чувствуете, что она действительно принадлежит вашему типу CustomizableObject. Но это, вероятно, неверно: вы создали тип с намерением использовать этот тип в конкретной ситуации. Например, из контекста, похоже, вы создадите эти элементы управления и анимации и используйте их в форме Windows. Дело в том, что у вас есть собственная форма, которая наследуется от базового System.Windows.Form, и этот новый тип формы должен знать об ICustomizableObject и как его использовать. То, куда будет идти ваша статическая логика.

Это кажется немного неудобным, но оно оказалось точным, когда вы решили изменить механизмы представления. Что произойдет, если вы переносите этот код в WPF или Silverlight? Скорее всего, они будут использовать ваш код реализации немного иначе, чем Windows Forms, и вам все же, вероятно, придется изменить свои реализации CustomizableControl. Но ваша статическая логика теперь находится в правильном месте.

Наконец, метод LoadObject(), который вы используете, выделяется мне как не в том месте. Вы говорите, что хотите, чтобы каждый настраиваемый тип предоставлял метод, который вы можете назвать, который знает, как загрузить/построить сам. Но это действительно что-то другое. Возможно, вам понадобится еще один интерфейс с именем IConstructable<T> и у вас будет реализован ваш тип реализации, совместимый с ICustomizable (IConstructable<ICustomizable>).

Ответ 4

Похоже, вам придется прибегать к интерфейсам и композиции объектов.

Ответ 5

Я использую mixins в таком случае. mixins - это мощная концепция, используемая на многих языках для добавления функциональности в класс во время выполнения. mixins хорошо известны на многих языках. Рамка re-mix - это среда, которая привносит технологию mixin в .NET.

Идея проста: украсьте свой класс атрибутом класса, представляющим mixin. Во время выполнения эта функциональность будет добавлена ​​в ваш класс. Таким образом, вы можете эмулировать множественное наследование.

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

[Использование (CustomizableObjectMixin)] CustomizableControl: UserControl

Подробнее см. remix.codeplex.com.

Ответ 6

Я думаю, это одно из решений.

public interface IClassA
{
    void Foo1();
    void Foo2();
}

public interface IClassB
{
    void Foo3();
    void Foo4();
}

public class ClassA :IClassA
{
    #region IClassA Members

    public void Foo1()
    {
    }

    public void Foo2()
    {
    }

    #endregion
}

public class ClassB :IClassB
{
    #region IClassB Members

    public void Foo3()
    {
    }

    public void Foo4()
    {
    }

    #endregion
}

public class MultipleInheritance :IClassA, IClassB
{
    private IClassA _classA;
    private IClassB _classB;

    public MultipleInheritance(IClassA classA, IClassB classB)
    {
        _classA = classA;
        _classB = classB;
    }

    public void Foo1()
    {
        _classA.Foo1();
    }

    public void Foo2()
    {
        _classA.Foo2();
        AddedBehavior1();
    }

    public void Foo3()
    {
        _classB.Foo3();
        AddedBehavior2();
    }

    public void Foo4()
    {
        _classB.Foo4();
    }

    private void AddedBehavior1()
    {

    }

    private void AddedBehavior2()
    {

    }
}

Класс MultipleInheritance добавляет новое поведение к двум различным объектам, не влияя на поведение этих объектов. MultipleInheritance имеют такое же поведение, как и поставленные объекты (наследует оба поведения). Сори для моего английского. С наилучшими пожеланиями.