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

Абстрактные базовые классы, реализующие интерфейс

Скажем, что у меня есть абстрактный базовый класс, что-то простое, например

abstract class Item : IDisplayable
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public abstract void Print();

    }

и у меня есть класс, который наследуется от того, что нравится

  class Chair: Item
 {
    public int NumberOfLegs {get;set;}

    public void Print()
    {
     Console.WriteLine("Here is a simple interface implementation");
    }
 }

interface IDisplayable
{
void Print();
}

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

4b9b3361

Ответ 1

Если мы явно добавим интерфейс к дочерним классам, программа будет работать одинаково (по крайней мере, насколько я могу судить в моих простых примерах).

Программа не обязательно будет работать одинаково; ваших примеров недостаточно, чтобы проиллюстрировать разницу.

Явно ли реализация интерфейса была бы хорошей или плохой идеей или это было бы строго вопросом предпочтения?

Это плохая идея, если вы не намерены обеспечить семантику повторной реализации интерфейса.

Позвольте мне вкратце проиллюстрировать. Что делает эта программа?

using System;
interface IFoo { void Bar(); void Baz(); }
class Alpha : IFoo
{ 
    void IFoo.Bar() 
    {
        Console.WriteLine("Alpha.Bar");
    }
    void IFoo.Baz()
    {
        Console.WriteLine("Alpha.Baz");
    }
}
class Bravo : Alpha
{
    public void Baz()
    {
        Console.WriteLine("Bravo.Baz");
    }
}
class CharlieOne : Bravo
{
    public void Bar() 
    {
        Console.WriteLine("CharlieOne.Bar");
    }
}
class CharlieTwo : Bravo, IFoo
{
    public void Bar() 
    {
        Console.WriteLine("CharlieTwo.Bar");
    }
} 
class Program
{
    static void Main()
    {
        IFoo foo = new Alpha();
        foo.Bar();
        foo.Baz();
        foo = new Bravo();
        foo.Bar();
        foo.Baz();
        foo = new CharlieOne();
        foo.Bar();
        foo.Baz();
        foo = new CharlieTwo();
        foo.Bar();
        foo.Baz();
     }
}

Прежде чем читать, серьезно: попытайтесь предсказать вывод этой программы.

Теперь запустите его. Вы получили ожидаемый результат? Где ваша интуиция была неправильной?

Теперь вы видите разницу между CharlieOne и CharlieTwo? Повторная реализация IFoo в CharlieTwo может привести к привязке интерфейса для получения Bravo.Baz, хотя Bravo не выполняет повторное выполнение IFoo!

И с другой стороны: если вы ожидали, что Bravo.Baz будет назначаться слоту интерфейса только потому, что он существует, то вы видите, что отказ от повторного внедрения интерфейса приводит к неправильному коду. Для Bravo.Baz для замены Alpha.IFoo.Baz, Bravo должен повторно реализовать IFoo.

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

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

Для получения дополнительной информации см. мою статью по теме:

http://blogs.msdn.com/b/ericlippert/archive/2011/12/08/so-many-interfaces-part-two.aspx

Ответ 2

Так как Item наследует от IDisplayable, все, что происходит от Item, должно также реализовывать IDisplayable. Таким образом, явное добавление IDisplayable к этим классам является избыточным.

EDIT: @EricLippert делает превосходную оценку ниже о влиянии повторного внедрения интерфейсов. Я оставляю этот ответ, чтобы сохранить обсуждение в комментариях.

Ответ 3

Когда вы говорите "явно реализуя интерфейс", я предполагаю, что вы подразумеваете включение интерфейса в объявление класса. Существует другое конкретное значение для явного внедрения интерфейса. При этом заявляя, что производный класс реализует интерфейс, реализуемый базовым классом, неверен, хотя компилятор ему это разрешит, потому что производный класс не содержит реализации этого интерфейса. Подумайте, что произойдет, если базовый класс явно реализует интерфейс -

abstract class Item : IDisplayable
{
    void IDisplayable.Print() { ... }
}

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