Шаблон проектирования для использования вместо множественного наследования - PullRequest
23 голосов
/ 25 марта 2010

Исходя из фона C ++, я привык к множественному наследованию. Мне нравится ощущение дробовика, прямо нацеленного на мою ногу. В настоящее время я больше работаю в C # и Java, где вы можете наследовать только один базовый класс, но реализовать любое количество интерфейсов (правильно ли я понял терминологию?).

Например, давайте рассмотрим два класса, которые реализуют общий интерфейс, но разные (но обязательные) базовые классы:

public class TypeA : CustomButtonUserControl, IMagician
{
    public void DoMagic()
    {
        // ...
    }
}

public class TypeB : CustomTextUserControl, IMagician
{
    public void DoMagic()
    {
        // ...
    }
}

Оба класса UserControl s, поэтому я не могу заменить базовый класс. Оба должны реализовать функцию DoMagic. Моя проблема сейчас в том, что обе реализации функции идентичны. И я ненавижу копировать и вставлять код.

(возможные) решения:

  1. Я, естественно, хочу, чтобы TypeA и TypeB разделяли общий базовый класс, где я могу написать одно и то же определение функции только один раз. Однако из-за ограничения только одного базового класса я не могу найти место в иерархии, где оно подходит.
  2. Можно также попытаться реализовать своего рода составной шаблон . Помещение функции DoMagic в отдельный вспомогательный класс, но функция здесь требует (и модифицирует) довольно много внутренних переменных / полей. Отправка их всех в качестве (справочных) параметров будет выглядеть плохо.
  3. Моя интуиция говорит мне, что шаблон адаптера может иметь здесь место, некоторый класс для преобразования между двумя при необходимости. Но это также кажется счастливым.

Я пометил это как независимый от языка, поскольку он применяется ко всем языкам, в которых используется подход «один базовый класс - много интерфейсов».

Также, пожалуйста, укажите, возможно, я неправильно понял какой-либо из названных мной шаблонов.

В C ++ я просто создал бы класс с закрытыми полями для реализации этой функции и поместил бы его в список наследования. Какой правильный подход в C # / Java и тому подобное?

Ответы [ 8 ]

10 голосов
/ 25 марта 2010

Вы можете использовать шаблон стратегии или что-то подобное, чтобы использовать имеет (состав) вместо является (наследование):

public class TypeA : CustomButtonUserControl, IMagician {
    IMagician magicObj = new Magical();
    public void DoMagic() {
        magicObj.DoMagic();
    }
}

public class TypeB : CustomButtonUserControl, IMagician {
    IMagician magicObj = new Magical();
    public void DoMagic() {
        magicObj.DoMagic();
    }
}

public class Magical : IMagician {
    public void DoMagic() {
        // shared magic
    }
}

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

9 голосов
/ 25 марта 2010
  • В .Net вы можете применять методы расширения к интерфейсам. Это действительно аккуратно, когда это возможно, и применимо для вас, потому что это редкий способ применить общую реализацию к интерфейсу. Конечно, подумайте об этом, но это может не сработать для вас, поскольку вы говорите, что DoMagic работает со многими частными пользователями. Можете ли вы упаковать эти частные переменные internal возможно? Таким образом, метод расширения может получить к ним доступ.
  • Иметь общий функционал в другом классе. Если есть логичное место для размещения этой общей функциональности, передайте свои объекты этому другому методу класса (возможно, это функциональность пользовательского интерфейса, и у вас уже есть помощник пользовательского интерфейса ...). Опять же, можете ли вы предоставить личные данные внутренней / публичной собственности? (Конечно, безопасность / инкапсуляция - это проблема всего этого. Я не знаю, предназначены ли ваши уроки только для внутреннего использования или будут опубликованы публично.)
  • В противном случае передайте отдельный класс функциональности (или указатель определенной функции) в метод, определенный интерфейсом. Вам понадобится немного дублированного кода для передачи ваших личных переменных в эту ссылку на внешнюю функцию, но, по крайней мере, это будет немного, и ваша реализация будет в одном месте.
  • Мы могли бы сделать это слишком сложным. Это не заставит вас чувствовать себя полностью объектно-ориентированным, когда вы ложитесь спать сегодня вечером, но не могли бы вы иметь статическую подпрограмму в своей библиотеке, где бы ее ни вызывали все разработчики IMagician?
  • В конце концов, адаптер действительно может быть тем, что вы ищете. Менее вероятным, но все же заслуживающим рассмотрения является шаблон Decorator .

Если ничего не кажется особенно хорошим, выберите то, что вам лучше, используйте его пару раз и переставьте завтра. :)

3 голосов
/ 25 марта 2010

Ваш кишечник в этом случае правильный. Шаблон адаптера - это то, что вы ищете.

DoFactory имеет хорошие .NET-примеры (которые также должны быть близки к их Java-аналогам):

Шаблон проектирования адаптера в C # и VB.NET

3 голосов
/ 25 марта 2010

Заменить наследование композицией.

Переместите «обычную» функцию в отдельный класс, создайте экземпляр этого класса и вставьте его в объект TypeA и в объект TypeB.

1 голос
/ 25 марта 2010
public interface  IMagician{ /* declare here all the getter/setter methods that you need; they will be implemented both in TypeA and TypeB, right? */ }

public static class MyExtensions {
  public static void doMagic(this IMagician obj)
  { 
           // do your magic here
  }
}   

Теперь проблема в том, что если вам ДЕЙСТВИТЕЛЬНО необходимо использовать частные свойства / методы (в отличие от "внутренних"), этот подход не будет работать. Ну, на самом деле, вы можете творить чудеса, если вы можете прочитать эти свойства с помощью отражения, но даже если это работает, это довольно уродливое решение:)

[Обратите внимание, что doMagic автоматически станет частью TypeA и TypeB просто потому, что вы внедрили IMagician - там не нужно никакой реализации]

1 голос
/ 25 марта 2010

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

0 голосов
/ 25 марта 2010

Вы можете использовать состав, чтобы иметь волшебника как свойство типа A и типа B

class Magician : IMagician
{
    public void DoMagic()
    {}
}

Class TypeA : CustomButtonUserControl
{
   //property
   Magician magicianInTypeA
}

Class TypeB : CustomTextUserControl
{
    //property
    Magician magicianInTypeB
}
0 голосов
/ 25 марта 2010
abstract class Magical: CustomButtonUserControl
{
    public void DoMagic()
    {
        // ...
    }
}

public class TypeA : Magical
{

}

public class TypeB : Magical
{

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...