Множественное наследование в C#
Как известно множественное наследование в таких языках как Java и С# отсутствует, т.к. является потенциальным источником ошибок. Вместо него разработчику предлагается использовать механизм интерфейсов. Однако, интерфейсы могут хранить только прототипы методов, но не их код.
Как в таком случае они могут заменить множественное наследование? Допустим у меня есть два класса - A и B. Я хочу создать классы C, D и E. Каждый из этих классов должен включать в себя методы как класса A, так и класса B. На сколько я понял логику интерфейсов, для реализации моей задумки мне придётся описать два интерфейса - IA и IB. После чего объявить, что класс A реализует интерфейс IA, класс B - IB, а классы C, D и E оба этих интерфейса сразу. Всё бы хорошо, но только код функций каждого интерфейса придётся воспроизводить в каждом классе заново.
Я ничего не перепутал? :)
Я ничего не перепутал? :)
Не совсем. Стопроцентно можно избежать дублирования кода. Просто классы A, B, C, D, E немного абстрактны, для построения правильной иерархии. Если приведете конкретный пример, будет легче.
Эта гипотетическая ситуация является лишь гипотетической. В нормальной системе этого не нужно. Не забывайте, что тип - не только контейнер для данных, но и некий набор ответственностей над ними.
Угу. Кое чего можно сделать через генерики. Максимально эффективная связка в C# это генерики + интерфейсы. Хотя мне их выразительной возможности метами нехватает, приходится отражать.
{
public void ride()
{
// код
}
}
class CBird
{
public void fly()
{
// код
}
}
Теперь, как мне спарить этих двух зверушек, что бы получился Пегас, которой умеет скакать и летать, без дублирования кода соответствующих методов? На C++ это выглядело бы так:
{
}
А как это будет выглядеть при использовании интерфейсов?
{
public CPegas()
{
__Horse = new CHorse();
__Bird = new CBird();
}
private CHorse __Horse;
private CBird __Bird;
public void ride()
{
__Horse.ride();
}
public void fly()
{
__Bird.fly();
}
//...
}
Несовсем корректно.
Вот стандартный подход. Pegasus - композитный объект, содержит в себе Bird и Horse.
void Ride();
}
public interface IBird {
void Fly();
}
public class Horse : IHorse {
public void Ride() {
}
}
public class Bird : IBird {
public void Fly() {
}
}
public interface IPegasus : IHorse, IBird {
}
public class Pegasus : IPegasus {
private Horse _horse = new Horse();
private Bird _bird = new Bird();
public void Ride() {
_horse.Ride();
}
public void Fly() {
_bird.Fly();
}
}
Интерфейсы нам нужны для поддержки иерархии наследования.
Ещё раз: не забывайте об ответственностях. С концептуальной точки зрения, птица - не объект с крыльями, а целая система, в которой все элементы находятся во взаимосвязи. У пегаса взаимосвязь элементов (в частности, зависимость системы полёта от других его частей) может (и даже должна) быть иной.
Кстати, C# предлагает механизм явной реализации интерфейсов. Можно заставить пегаса летать как в своём стиле, так и в стиле птицы (пральней будет "как летать в стиле пегаса, так и просто летать").
Вспоминается тот самый Ping с его диспоузом :D
Мне тоже кажется, что эта возможность имеет сомнительный характер: ни разу мне не пригодилась. Но я допускаю, что могу просто чего-то не знать или банально не понимать. Как бы я вижу, что .NET умные дядьки сочиняли, и введение подобной двусмысленности должно иметь под собой какую-то почву, наверное. Можно и обсудить.
Предполагаемые мной предпосылки:
- принцип подстановки Барбары Лискофф в формулировке Роберта Мартина: методы, принимающие в качестве параметра указатели и ссылки на объекты базового класса, должны иметь возможность использовать эти объекты без необходимости знать, к какому классу (базовому или любому из производных) они принадлежат (один из базовых принципов ООП-дизайна);
- видимо, даже при тщательном планировании дизайна, средствами обычного полиморфизма (возможно именно полиморфизма .NET, хотя разницы не вижу) не всегда можно обеспечить выполнение вышеприведённого принципа;
- возможно, это имеет какое-то отношение к разрешению конфликтов имён методов реализуемых интерфейсов.
Лично мне кажется, что необходимость применения явной реализации интерфейсов в последних двух случаях обусловлена неверным дизайном. Кто-нибуть встречал инциденты подобного?
Пожалуй, это единственный убедительный аргумент.
К примеру, этот способ используется при реализации параметрического интерфейса IEnumerable<T>. При этом нужно реализовывать два метода GetEnumerator - строго типизированный и нестрогий.
С моей точки зрения, конкретно интерфейсов (в .NET-ной трактовке) недостаточно. Мне иногда остро недостает подобия контрактов - своего рода "интерфейсов времени компиляции". Когда мы декларируем компилятору, что вот данный объект должен поддерживать такие-то и такие-то свойства-методы. Обосновано это тем, что зачастую не важен конкретный тип объекта - важен сам факт наличия у объекта набора тех или иных свойств-методов. Замечу, что полиморфизм такого уровня невозможен в C#, но вполне реализуем в Nemerle.
К примеру, этот способ используется при реализации параметрического интерфейса IEnumerable<T>. При этом нужно реализовывать два метода GetEnumerator - строго типизированный и нестрогий.[/QUOTE]Я тоже сёдня об этом подумал, и именно об IEnumerable :) Да, например пара методов
{
}
и
double Method()
{
}
Шаблоны STL, например, на соглашениях о реализуемых классами методах зиждятся. Забыл констуктор копий - пеняй на себя :)
Есть базовый класс
public abstract Objectbase{}
public abstract ObjectManagerBase<T> where T : ObjectBase, new()
{
public abstract T Create(DataRow dr);
...
}
Наследники ObjectManagerBase<T>, например UserManager<User>, управляют объектами User, не имеющими языковой поддержки,
но имеются так же объекты типа Category, логика создания которых идетнична User, но для каждой страны у них своё имя и ещё кое какие специфичные для данной страны свойства.
Для создания объекта LanguageSupport торчащего через свойство Category.LanguageSupport есть соответсвующий метод,
который приходится дублировать во всех наследниках ObjectManagerBase<T>, управляющий объектами с языковой поддержкой.
По идее было бы замечательно создать ещё один базовый класс типа
public abstract LanguageSupportedOjectManagerBase<T> where T : LanguageSupport, new()
{
public T Create(DataRow dr)
{
T languageSupport = new T();
...
return languageSupport;
}
}
и создать класс CategoryManager, наследующий от ObjectManagerBase<T> и LanguageSupportedOjectManagerBase<T>, но так в .net нельзя.
Возможно ли добавть функционал базового класса LanguageSupportedOjectManagerBase<T> к некоторым наследникам ObjectManagerBase<T>?
По идее было бы замечательно создать ещё один базовый класс типа
public abstract LanguageSupportedOjectManagerBase<T> where T : LanguageSupport, new()
{
public T Create(DataRow dr)
{
T languageSupport = new T();
...
return languageSupport;
}
}
и создать класс CategoryManager, наследующий от ObjectManagerBase<T> и LanguageSupportedOjectManagerBase<T>, но так в .net нельзя.
Возможно ли добавть функционал базового класса LanguageSupportedOjectManagerBase<T> к некоторым наследникам ObjectManagerBase<T>?
Вам сюда: паттерн "Декоратор".
Для того чтобы можно было различать объекты с языковой поддержкой неплохо было бы завести интерфейс вроде ILanguageSupported.
з.ы. хотя возможно я чего-то не понял в вопросе.
Для того чтобы можно было различать объекты с языковой поддержкой неплохо было бы завести интерфейс вроде ILanguageSupported.
з.ы. хотя возможно я чего-то не понял в вопросе.
Да, слышал про такой паттерн, но не вникал, надо будет вникнуть.
По поводу интерфейса:
как я понимаю предполагается интерфейс типа такого
{
T CreateLanguageSupport(DataRow dt);
}
такой вариант не очень, так как придётся реализовывать его во всех классах с языковой поддержкой,
а реализация везде будет один в один.
как я понимаю предполагается интерфейс типа такого
{
T CreateLanguageSupport(DataRow dt);
}
а реализация везде будет один в один.
Зависит от размера кода:
Вариант 1: захардкодить вручную.
Вариант 2: сделать генератор (например посредством XSLT).
Вариант 3: сделать рантайм-генератор через Emit или CodeDom.
Вариант 4: использовать что-то типа PostSharp.
Вариант 5: использовать Nemerle и макроатрибуты.
Я использовал бы вариант №5 как наиболее простой и удобный.
Множественное наследование методов возможно.
Надеюсь, это не страшная тайна (потому что нигде ничего подобного не видел, и "изобрел" сам), но вещь действительно жуткая, к использованию не рекомендующаяся. Но мне вот понадобилась. Написал. Потом понял, что написал. Ужаснулся и решил поделиться. :)
{
}
public interface IBird
{
}
public interface IPegasus : IHorse, IBird
{
}
public static class InterfaceExtensions
{
public static void Ride(this IHorse horse)
{
Console.WriteLine("Я скачу!");
}
public static void Fly(this IBird bird)
{
Console.WriteLine("Я лечу!");
}
}
public class Pegas : IPegasus
{
}
class Program
{
static void Main(string[] args)
{
Pegas pegas = new Pegas();
pegas.Ride();
pegas.Fly();
Console.ReadLine();
}
}
В итоге получаем нечто монструозное, наподобие шаблонов из С++
Что ужасного в том, чтобы использовать метод расширения по назначению (т. е. для реализации кастрированных примесей)?
Однако методы расширения не имеют отношения ни к множественному наследованию, ни к шаблонам. Например потому, что нет возможности расширить набор полей/свойств/событий типа, а полиморфизм на уровне перегрузок и выбора между методом типа и методом расширения на этапе компиляции полиморфизмом не считается.
Покажите лучше, зачем вам понадобилось множественное наследование.
Однако методы расширения не имеют отношения ни к множественному наследованию, ни к шаблонам. Например потому, что нет возможности расширить набор полей/свойств/событий типа, а полиморфизм на уровне перегрузок и выбора между методом типа и методом расширения на этапе компиляции полиморфизмом не считается.
Покажите лучше, зачем вам понадобилось множественное наследование.
Рисую облочку над WinAPI для замены AutoIT. Поскольку все элементы являются окнами, но не все "окна" способны адекватно реагировать на различные сообщения, несколько из них я выделил в интерфейсы с расширенными методами. Тем не менее, уже отошел от них, сделав человеческое древовидное наследование, а недоступные методы перекрыл приватными загулшками. Так спокойнее. :)
hardcase, [COLOR="gray"]будь ты неладен со своими советами :),[/COLOR] не поделишься годными ссылками по варианту №2 - генерация кода посредством XSLT?
И почему в списке нет T4?