2009-11-06 2 views
45

Я не могу найти ответ на это и просто хочу убедиться, что это стандарт кодирования. У меня есть интерфейс А, который используется многими разными классами и не требует изменения интерфейса А. Я столкнулся с новым требованием, которое потребует перечисления, которое потребуется многим из классов, реализующих интерфейс A, но не всем классам требуется это перечисление. Я не хочу, чтобы классы, которые не нуждаются в этом новом перечислении для реализации этой новой функции. Поэтому я создал интерфейс B, содержащий новое перечисление, которое мне нужно было добавить. Затем я сделал интерфейс B Inherit Interface A, и это мое беспокойство. Является ли в порядке для одного интерфейса для наследования другого интерфейса? Чтобы продолжить мои изменения, я изменил классы, которые нуждались в новом перечислении для реализации интерфейса B вместо интерфейса A, поскольку он был унаследован интерфейсом B. Я думал об использовании обоих интерфейсов в своих классах, которые им были нужны, но я использую Интерфейс по всему коду и хотел бы просто использовать один интерфейс для просмотра классов, а не двух.Если один интерфейс наследует другой интерфейс

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

Спасибо!

ответ

60

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

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

public interface ICollection<T> : IEnumerable<T>, IEnumerable 
+2

Первоначально предполагалось, что принцип замещения Лискова должен применяться. Оглядываясь назад, это не имеет особого значения. Большинство требований LSP (сохранение инвариантов, ограничений на предварительные и пост-условия) действительно применимы только к конкретным реализациям, а не к интерфейсам. Сказав это, общий принцип взаимозаменяемости должен по-прежнему направлять решения наследования интерфейсов. –

+0

Принцип замены Лискова: важно убедиться, что интерфейс B может полностью заменить интерфейс A. В противном случае вы получите функциональность, которую вы должны реализовать, которую вы не хотите. Это приводит к дополнительному коду, который вы не хотите, что делает программное обеспечение менее стабильным. –

2

ИМО это именно правильный подход, я не вижу никаких проблем с ним.

6

Конечно, возможно иметь дерево наследования интерфейсов и даже «множественное наследование» с интерфейсами. Правильно ли это делать или нет, зависит от рассматриваемых интерфейсов. Если на самом деле интерфейс B является расширением или уточнением интерфейса A, то наследование имеет смысл, но если новое перечисление в значительной степени не связано с понятием, выраженным интерфейсом A, я бы сделал их двумя отдельными интерфейсами и имел классы которые должны реализовать оба интерфейса.

+0

Хорошая точка и да, в этой ситуации они связаны. – ajrawson

5

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

interface IBar 
{ 
    void DoBar(); 
} 

interface IFoo : IBar 
{ 
    void DoFoo(); 
} 

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

+3

Даже с учетом описанного вами поведения (подробно описанного в недавней колонке Фила Хаака, http://haacked.com/archive/2009/11/10/interface-inheritance-esoterica.aspx), я думаю, что это чрезмерно запутывает сказать, что «интерфейсы не наследуются друг от друга» на C#, когда справочная документация на C# даже использует эту терминологию, как в «Интерфейс может наследовать один или несколько базовых интерфейсов». (http://msdn.microsoft.com/en-us/library/87d83y5b%28VS.80%29.aspx) Я бы предпочел просто сказать, что в C# наследование интерфейса ведет себя иначе, чем наследование класса при отражении унаследованных членов , –

+1

Достаточно честный. Я думаю, это зависит от того, как определяется «inherit». Конечно, синтаксис C# тот же, будь то классы или интерфейсы. Но если вы думаете о наследовании как о включении членов родителя, то ваша ментальная модель не соответствует действительности, когда вы говорите об интерфейсах. –

1

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

IMySqlDatabase : IDatabase 
MySqlDatabase : IMySqlDatabase 

IMsSqlDatabase : IDatabase 
MsSqlDatabase : IMsSqlDatabase 

MySqlDatabase ЯВЛЯЕТСЯ IMySqlDatabase и IMySqlDatabase ЯВЛЯЕТСЯ IDatabase.

Теперь, если вам нужно внести изменения в свой интерфейс IDatabase, это внуки (классы базы данных для конкретных классов) могут воспользоваться преимуществами, но вам не придется расширять MySQL и MsSQL (или, возможно, даже больше интерфейсов СУБД).В то же время у вашего среднего человека (IMsSqlDatabase) вы все еще можете иметь интерфейсные функции, которые не поддерживали бы MySQL или Oracle DB.

13

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

Давайте рассмотрим пример;

public interface IScanner 
{ 
    void Scan(); 
} 

public interface IPrinter 
{ 
    void Print(); 
} 

Принтеры и сканеры часто являются отдельными объектами, каждый со своей собственной функциональностью, однако эти два устройства часто сопряженным в том же самом устройстве;

public interface IPhotocopier : IScanner, IPrinter 
{ 
    void Copy(); 
} 

Это имеет смысл, что IPhotocopier должно наследовать от IScanner и IPrinter, так как это позволяет теперь копировальный быть использовано как сканер или принтер (который она содержит) в дополнении к своему основному рулону в качестве копировального аппарата.

Теперь рассмотрим еще один интерфейс;

public interface IBlender 
{ 
    void Blend(); 
} 

Это не имело бы смысла, чтобы позволить IBlender наследоваться любым из предыдущих интерфейсов (что бы вы их называете? IBlendingScanner?).

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

Плохая идея наследовать некоторые интерфейсы, такие как IDisposable, поскольку это заставляет все реализации вашего нового интерфейса реализовывать шаблон размещения, даже если у них нет доступных ресурсов.

Смежные вопросы