Интерфейс против Базового класса - PullRequest
743 голосов
/ 11 сентября 2008

Когда я должен использовать интерфейс и когда я должен использовать базовый класс?

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

Если у меня класс собак и кошек. Почему я хотел бы реализовать IPet вместо PetBase? Я могу понять наличие интерфейсов для ISheds или IBarks (IMakesNoise?), Потому что они могут быть размещены на основе питомца за питомцем, но я не понимаю, какой использовать для обычного питомца.

Ответы [ 39 ]

3 голосов
/ 13 сентября 2008

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

Public Inheritance в OOD используется слишком часто и выражает гораздо больше, чем большинство разработчиков осознают или хотят соответствовать. См. принцип замещения Лискова

Короче говоря, если A "представляет собой" B, то A требует не более B и обеспечивает не менее B для каждого раскрываемого им метода.

3 голосов
/ 04 ноября 2008

Другой вариант, о котором следует помнить, - это использование отношения has-a, иначе говоря, реализуемого в терминах «состав». Иногда это более чистый и более гибкий способ структурирования, чем использование наследования "is-a".

Возможно, логически не имеет смысла говорить, что у Собаки и Кошки "есть" домашнее животное, но это позволяет избежать распространенных ошибок множественного наследования:

public class Pet
{
    void Bathe();
    void Train(Trick t);
}

public class Dog
{
    private Pet pet;

    public void Bathe() { pet.Bathe(); }
    public void Train(Trick t) { pet.Train(t); }
}

public class Cat
{
    private Pet pet;

    public void Bathe() { pet.Bathe(); }
    public void Train(Trick t) { pet.Train(t); }
}

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

Композиция предпочтительна, когда не требуется частный доступ, и вам не нужно ссылаться на «Собаку и кошку», используя общие ссылки / указатели на домашних животных. Интерфейсы дают вам такую ​​общую справочную возможность и могут помочь сократить многословность вашего кода, но они также могут запутывать вещи, когда они плохо организованы. Наследование полезно, когда вам нужен личный доступ, и при его использовании вы берете на себя обязательство связывать свои классы с собаками и кошками с вашими с питомцами, что требует больших затрат.

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

3 голосов
/ 16 сентября 2008

Концептуально интерфейс используется для формального и полуформального определения набора методов, которые будет предоставлять объект. Формально означает набор имен методов и сигнатур, полуформально означает удобочитаемую документацию, связанную с этими методами. Интерфейсы - это только описания API (в конце концов, API означает Application Programmer Interface ), они не могут содержать какую-либо реализацию, и невозможно использовать или запускать интерфейс. Они только делают явный договор о том, как вы должны взаимодействовать с объектом.

Классы предоставляют реализацию, они могут объявить, что они реализуют ноль, один или несколько интерфейсов. Если класс предназначен для наследования, соглашение заключается в добавлении префикса имени класса к «Base».

Существует различие между базовым классом и абстрактным базовым классом (ABC). ABC смешивают интерфейс и реализацию вместе. Аннотация за пределами компьютерного программирования означает «резюме», то есть «Аннотация == Интерфейс». Затем абстрактный базовый класс может описывать как интерфейс, так и пустую, частичную или полную реализацию, которая предназначена для наследования.

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

В динамических языках интерфейс к объекту часто неявно предполагается, либо путем интроспекции объекта и запроса его, какие методы он предоставляет (Look Before You Leap), либо, предпочтительно, просто пытаясь использовать требуемый интерфейс для объекта и перехватывая исключения если объект не предоставляет этот интерфейс (проще просить прощения, чем разрешения). Это может привести к «ложным срабатываниям», когда два интерфейса имеют одинаковое имя метода, но семантически различаются, однако компромисс заключается в том, что ваш код более гибок, так как вам не нужно заранее указывать заранее, чтобы предвидеть все возможные варианты использования вашего кода.

2 голосов
/ 23 февраля 2011

Что касается C #, то в некоторых отношениях интерфейсы и абстрактные классы могут быть взаимозаменяемыми. Однако различия заключаются в следующем: i) интерфейсы не могут реализовывать код; ii) из-за этого интерфейсы не могут далее вызывать стек до подкласса; и iii) только абстрактный класс может быть унаследован от класса, тогда как в классе может быть реализовано несколько интерфейсов.

2 голосов
/ 23 сентября 2014

По умолчанию интерфейс обеспечивает уровень для связи с другим кодом. Все открытые свойства и методы класса по умолчанию реализуют неявный интерфейс. Мы также можем определить интерфейс как роль, когда любой класс должен играть эту роль, он должен реализовать его, предоставляя ему различные формы реализации в зависимости от класса, реализующего его. Следовательно, когда вы говорите об интерфейсе, вы говорите о полиморфизме, а когда вы говорите о базовом классе, вы говорите о наследовании. Два понятия упс !!!

2 голосов
/ 11 сентября 2008

@ Джоэл: Некоторые языки (например, C ++) допускают множественное наследование.

2 голосов
/ 11 сентября 2008

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

Недостатком реализации базового класса является требование override (или new) существующих методов. Это делает их виртуальными методами, что означает, что вы должны быть осторожны с тем, как вы используете экземпляр объекта.

Наконец, единственное наследство .NET убивает меня. Наивный пример: скажем, вы делаете пользовательский элемент управления, поэтому вы наследуете UserControl. Но теперь вы не можете наследовать PetBase. Это вынуждает вас реорганизоваться, например, вместо этого сделать членом класса PetBase.

2 голосов
/ 11 сентября 2008

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

2 голосов
/ 04 марта 2016

Я обнаружил, что шаблон Interface> Abstract> Concrete работает в следующем сценарии использования:

1.  You have a general interface (eg IPet)
2.  You have a implementation that is less general (eg Mammal)
3.  You have many concrete members (eg Cat, Dog, Ape)

Абстрактный класс определяет общие атрибуты по умолчанию для конкретных классов, но обеспечивает интерфейс. Например:

public interface IPet{

    public boolean hasHair();

    public boolean walksUprights();

    public boolean hasNipples();
}

Теперь, поскольку у всех млекопитающих есть волосы и соски (AFAIK, я не зоолог), мы можем свернуть это в абстрактный базовый класс

public abstract class Mammal() implements IPet{

     @override
     public walksUpright(){
         throw new NotSupportedException("Walks Upright not implemented");
     }

     @override
     public hasNipples(){return true}

     @override
     public hasHair(){return true}

И тогда конкретные классы просто определяют, что они идут прямо.

public class Ape extends Mammal(){

    @override
    public walksUpright(return true)
}

public class Catextends Mammal(){

    @override
    public walksUpright(return false)
}

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

В этом случае реферат также может быть конкретным; однако, абстрактное обозначение помогает подчеркнуть, что этот образец используется.

1 голос
/ 11 сентября 2008

Наследник базового класса должен иметь отношение «есть». Интерфейс представляет отношение «реализует». Поэтому используйте базовый класс только тогда, когда ваши наследники будут поддерживать отношения is.

...