Интерфейс и наследование классов. Это шаблон, который можно избежать? - PullRequest
3 голосов
/ 24 мая 2011

Буду признателен за любые советы о том, как действовать в следующем сценарии. Давайте посмотрим, смогу ли я объяснить это ясно (английский не является моим родным языком, поэтому все может запутаться, извините).

Предположим, у меня есть следующие интерфейсы:

internal interface IBlah
{
     int Frob();
}

internal interface IBlahOnSteroids: IBlah
{
     double Duh();
}

Теперь у нас есть класс Foo с отношением «имеет» с объектом IBlah:

public class Foo
{
     IBlah blah;

     internal Foo(IBlah blah)
     {
          this.blah = blah;
     }

     public int Frob()
     {
          ....
          return this.blah.Frob();
     }
}

Теперь нам также нужен класс FooOnSteroids, который имеет отношение «имеет» с объектом IBlahOnSteroids. Вопрос в том, что зная, что часть IBlahOnSteroids уже реализована в Foo, что произойдет, если мы создадим FooOnSteroids наследуется от Foo?

Мы бы получили что-то вроде этого:

public class FooOnSteroids: Foo
{
    IBlahOnSteroids blah;

    internal FooOnSteroids(IBlahOnSteroids blah)
        :base(blah) 
    {
         this.blah = blah;
    }

    public double Duh()
    {
         return this.blah.Duh();
    }
}

Это рекомендуемый шаблон? Мы передаем по цепочке наследования один и тот же объект «бла» и на каждом «уровне» мы храним его в частном поле с «полезным» типом. Я не вижу, чтобы я мог хранить в BlahBase защищенное свойство, которое раскрыл одну общую ссылку IBlah на все нисходящие классы, поскольку она должна иметь тип IBlah, который не будет полезен BlahOnSteroids. Этот сценарий даже рекомендуемые? Или мы должны просто реализовать Foo и FooOnSteroids как независимые классы без наследования (это приведет к дублированию кода)? Может быть, это абсолютно нормально, но это похоже на взлом. Это 1014 *

Возможность использования непатентованных средств, которые могли бы решить проблему в кратчайшие сроки, невозможна, поскольку, да, я знаю, что это отстой, эта библиотека должна предназначаться для платформ .Net 1.x.

Возможность простой реализации BlahOnSteroids также возможна, но это будет означать, что в зависимости от вызывающей стороны, мы должны были бы выдать исключение, если любой из Участники IBlahOnSteroids был вызван. Мне это не нравится.

Большое спасибо за любые советы!

Ответы [ 4 ]

2 голосов
/ 24 мая 2011

Вы можете уменьшить дублирование, сделав доступным базовое поле:

IBlah blah;
protected IBlah Blah { get { return blah; } }

и приведя его к подтипу (поскольку вы ожидаете, что ваш выбор blah будет соблюдаться):

public double Duh() {
    return ((IBlahOnSteroids)Blah).Duh();
}

Вы также можете сделать что-то с дженериками базового типа (чтобы избежать приведения), но я не уверен, что оно того стоит.Обратите внимание, однако, что это может взорваться, если базовый класс решит ввести декоратор в большом количестве blah (если декоратор не предоставляет второй интерфейс).

2 голосов
/ 24 мая 2011

Один альтернативный шаблон -

public class Foo
{
    protected IBlah Blah { get; private set; }
    ...
}
public class FooOnSteroids : Foo 
{
    private new IBlahOnSteroids Blah { get { return (IBlahOnSteroids)base.Blah; } }
    ...
}

Однако, это не сильно отличается от вашего кода; если вы не можете использовать дженерики, они оба в порядке.

1 голос
/ 24 мая 2011

Учитывая, что вы не можете использовать дженерики (которые могут или не могут облегчить ваш конкретный случай использования), я бы лично выбрал blah член Foo. Кастинг в C # относительно безболезненный:

public double Duh()
{
    return (this.blah as IBlahOnSteroids).Duh();
}

Ключевое слово as в C # будет иметь значение null, если объект не может быть преобразован в запрошенный вами тип. В приведенном выше примере, если this.blah не является экземпляром IBlahOnSteroids, вы получите NullReferenceException. Вы можете проверить, является ли объект экземпляром типа, подобного так:

public double Duh()
{
    if (this.blah is IBlahOnSteroids)
        return (this.blah as IBlahOnSteroids).Duh();
    else
        throw new InvalidTypeException("Blah is not an instance of IBlahOnSteroids");
}

Хотя в коде вашего исходного примера невозможно , чтобы blah не был экземпляром IBlahOnSteroids, поскольку он назначен в конструкторе, что делает это утверждение во время компиляции для вас.

1 голос
/ 24 мая 2011

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

Итак, если концептуально ваши клиенты делают это:

Foo foo = new FooOnSteroids();
foo.Frob()

Вы должны сохранить наследство.

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

...