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

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

Я низкоуровневый инженер, поэтому моя первая мысль всегда была: «О! Я просто напишу некоторые из этих классов на родном C ++ и буду ссылаться на них извне! Тогда я получу все свое старомодное ОО-веселье!» Увы, это не помогает, когда вам нужно наследовать от управляемых элементов управления ...

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

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

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

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

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


person Giffyguy    schedule 17.08.2009    source источник
comment
+1 за рисование всего этого :)   -  person Kamarey    schedule 17.08.2009
comment
Ха-ха, я рад, что мои работы можно оценить по достоинству.   -  person Giffyguy    schedule 18.08.2009
comment
Этот вопрос плохо вписывается в формат Q&A stackoverflows. Какие-то хорошие альтернативы уже предполагают, что нет единого правильного ответа. Есть много разных способов и мнений.   -  person Stefan Steinegger    schedule 04.04.2013


Ответы (6)


Один из подходов - использовать методы расширения с интерфейсом для обеспечения реализации вашего «производного класса», как и 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();
person dahlbyk    schedule 17.08.2009
comment
Добавлен пример использования для вас. Пока компилятор может их находить, методы расширения выглядят и работают так же, как методы экземпляра. К сожалению, нет свойств расширения, поэтому я переименовал метод в соответствии с соглашениями об именах методов. - person dahlbyk; 17.08.2009
comment
Также ознакомьтесь с этой статьей в MSDN: msdn.microsoft.com/en-us /vcsharp/bb625996.aspx - person dahlbyk; 17.08.2009
comment
Это решение отлично решает проблему !! Это чертовски круто. Бонус здесь в том, что я могу хранить реализацию в одном месте, в моем дереве объектов - вместо того, чтобы распространять ее полиморфно или с помощью интерфейсов - поэтому она отлично работает с менталитетом управления WPF. Я рекомендую это решение всем, у кого есть проблемы с множественным наследованием / интерфейсом в .NET. - person Giffyguy; 18.08.2009
comment
Рад помочь! В качестве дополнения вы можете рассмотреть возможность явной реализации интерфейса в CustomizableControl, чтобы эти члены были видны только тогда, когда элемент управления фактически используется в его настраиваемых возможностях. - person dahlbyk; 18.08.2009
comment
Я также добавлю, что это решение может быть реализовано чисто - качество, которое, как мне кажется, стало редкостью в некоторых более современных мышлениях .NET. - person Giffyguy; 18.08.2009
comment
Просто чтобы немного дождаться парада, у этого есть пара недостатков по сравнению с полным MI, как это могло бы быть в .NET. Во-первых, вы не можете развить интерфейс без нарушения бинарной совместимости (обычно это не проблема для библиотек с небольшим сообществом пользователей). Это может быть исправлено в какой-нибудь будущей версии CLR, где они могут позволить интерфейсным методам иметь реализацию по умолчанию. Во-вторых, в MI каждый из нескольких базовых классов может добавлять поля данных в объединенный класс. Вам нужно будет смоделировать это с помощью методов расширения с каким-то беспорядком статического словаря локального потока. - person Daniel Earwicker; 18.08.2009
comment
@Earwicker: Вы поднимаете несколько замечательных моментов. Ваш комментарий прав, учитывая, что этот вопрос касается общих альтернатив MI в .NET. Это решение имеет большой потенциал в определенных случаях, и я склонен в его пользу, потому что оно так хорошо решило мою конкретную проблему. Но пока мы обсуждаем это, я должен сказать, что я действительно разочарован Microsoft здесь. Фреймворк полностью поддерживает объектно-ориентированный подход, и они явно не заботятся о незначительных сбоях в производительности. Так почему мы не можем использовать MI там, где это имеет смысл? System.Object всегда имеет форму ромба, но виртуальное наследование решает эту проблему. - person Giffyguy; 18.08.2009
comment
Несколько замечательных моментов. Методы расширения действительно не работают, если вам требуется общее состояние, которое не предоставляется интерфейсом, так что планируйте заранее! Чтобы узнать больше о реализациях интерфейсов по умолчанию (и многом другом), посмотрите это видео на Channel 9: от эксперта к эксперту: Эрик Мейер и Андерс Хейлсберг - Будущее C # channel9.msdn.com/shows/Going+Deep/ - person dahlbyk; 18.08.2009

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

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

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

person Ed S.    schedule 17.08.2009
comment
Мне очень нравится, как вы это описываете. Кажется, я, вероятно, выберу композиционный маршрут. Это досадно усложнит ситуацию, но в меньшей степени, чем повсюду мои реализации. (Ха-ха, я такой упрямый ... Я набираюсь смелости, чтобы напрямую обратиться к СТО за помощью с интерфейсами, и Я ВСЕ ЕЩЕ не могу проглотить свою гордость и принять все советы о силе интерфейсов.) - person Giffyguy; 17.08.2009

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

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

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

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

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

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

person Joel Coehoorn    schedule 17.08.2009
comment
+1 Хмммм ... вы поднимаете очень интересный вопрос. Я действительно хотел бы, чтобы мои элементы управления спрайтами были автономными. В конце концов, это WPF, поэтому я, вероятно, не должен просто наследовать от базового типа окна, как мы это сделали в WinForms, - но я мог бы легко применить это решение, скажем, к моим элементам управления SpriteLayer или SpriteView. - person Giffyguy; 17.08.2009
comment
Должен сказать, мне тоже очень нравится делегатский подход. Я на самом деле потратил некоторое время на кодирование, пытаясь понять, как делегаты будут работать для этого. Я планировал принять ваш ответ, как только он будет скомпилирован и запущен - методы расширения стали работать лучше и, я думаю, чище. Однако это определенно отличное решение, которое точно заслуживает внимания. На самом деле, я готов поспорить, что существует множество случаев, когда делегаты решат проблему даже лучше, чем методы расширения. - person Giffyguy; 18.08.2009

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

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

Решением вашей проблемы может быть превращение CustomizableObject в миксин. Для этого вам понадобится фреймворк повторного микширования.

[Использует (CustomizableObjectMixin)] CustomizableControl: UserControl

Пожалуйста, посетите remix.codeplex.com для получения более подробной информации.

person Stefan Papp    schedule 24.03.2011

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

person xanadont    schedule 17.08.2009

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

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 имеет то же поведение, что и доставленные объекты (наследует оба поведения).

person user1785960    schedule 03.04.2013
comment
Я не считаю это шаблоном декоратора. - person John Saunders; 04.04.2013
comment
Я думаю, что это допустимый и возможный подход в некоторых контекстах, я не понимаю, почему вы были отклонены. Очевидно, это не общий подход, поскольку декораторы предназначены для расширения существующих классов. Хотя вы можете использовать тип декоратора напрямую и добавить к нему несколько совершенно новых методов. Я вижу, что this не является шаблоном декоратора, но он может работать как вариант композиции. - person julealgon; 07.01.2014