Когда реализовать интерфейс, а когда расширить суперкласс? - PullRequest
35 голосов
/ 22 июля 2010

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

Итак, когда вы реализуете интерфейс и когда расширяете суперкласс?

Ответы [ 11 ]

39 голосов
/ 22 июля 2010

Используйте интерфейс , если вы хотите определить контракт .Т.е. X должен взять Y и вернуть Z. Ему все равно как делает код.Класс может реализовывать несколько интерфейсов.

Используйте абстрактный класс , если вы хотите определить поведение по умолчанию в неабстрактных методах, чтобыконечные пользователи могут повторно использовать его, не переписывая его снова и снова.Класс может расширяться от только до одного другого класса.Абстрактный класс с only абстрактными методами может быть определен так же хорошо, как интерфейс.Абстрактный класс без какого-либо абстрактного метода распознается как шаблон Template Method (см. этот ответ для некоторых примеров из реального мира).

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

12 голосов
/ 23 июля 2010

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

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

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

7 голосов
/ 22 июля 2010

Используйте интерфейс для определения поведения. Пользовательские (абстрактные) классы (и подклассы) для обеспечения реализации . Они не являются взаимоисключающими; все они могут работать вместе.

Например, допустим, вы определяете объект доступа к данным. Вы хотите, чтобы ваш DAO мог загружать данные. Поэтому поместите метод загрузки на интерфейс. Это означает, что все, что хочет называть себя DAO, должно реализовывать нагрузку. Теперь предположим, что вам нужно загрузить A и B. Вы можете создать универсальный абстрактный класс, который параметризован (generics), чтобы обеспечить общую схему работы нагрузки. Затем вы создаете подкласс этого абстрактного класса для предоставления конкретных реализаций для A и B.

3 голосов
/ 22 июля 2010

Основная причина использования абстрактных классов и интерфейсов различна.

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

Это может быть плохой пример, но наиболее очевидное использование абстрактных классов в среде Java - в классах java.io. OutputStream это просто поток байтов. То, куда направляется этот поток, полностью зависит от того, какой подкласс OutputStream вы используете ... FileOutputStream, PipedOutputStream, выходной поток создается методом java.net.Socket getOutputStream ...

Примечание. Java.io также использует шаблон Decorator для переноса потоков в другие потоки / программы чтения / записи.

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

Наиболее очевидное использование интерфейсов - в рамках коллекций.

Мне все равно, как List добавляет / удаляет элементы, если я могу вызывать add(something) и get(0), чтобы помещать и получать элементы. Он может использовать массив (ArrayList, CopyOnWriteArrayList), связанный список (LinkedList) и т. Д. ...

Другое преимущество использования интерфейсов состоит в том, что класс может реализовывать более одного. LinkedList является реализацией List и Deque.

3 голосов
/ 22 июля 2010

Никто?

http://mindprod.com/jgloss/interfacevsabstract.html

РЕДАКТИРОВАТЬ: я должен предоставить больше, чем ссылка

Вот ситуация. Чтобы построить на примере автомобиля ниже, рассмотрим это

interface Drivable {
    void drive(float miles);
}

abstract class Car implements Drivable { 
    float gallonsOfGas;
    float odometer;
    final float mpg;
    protected Car(float mpg) { gallonsOfGas = 0; odometer = 0; this.mpg = mpg; }
    public void addGas(float gallons) { gallonsOfGas += gallons; }
    public void drive(float miles) { 
        if(miles/mpg > gallonsOfGas) throw new NotEnoughGasException();
        gallonsOfGas -= miles/mpg;
        odometer += miles;
    }
}

class LeakyCar extends Car { // still implements Drivable because of Car
    public addGas(float gallons) { super.addGas(gallons * .8); } // leaky tank
}

class ElectricCar extends Car {
    float electricMiles;
    public void drive(float miles) { // can we drive the whole way electric?
         if(electricMiles > miles) {
             electricMiles -= miles;
             odometer += miles;
             return;                 // early return here
         }
         if(electricMiles > 0) { // exhaust electric miles first
             if((miles-electricMiles)/mpg > gallonsOfGas) 
                 throw new NotEnoughGasException();
             miles -= electricMiles;
             odometer += electricMiles;
             electricMiles = 0;
         }
         // finish driving
         super.drive(miles);
    }
}
2 голосов
/ 23 июля 2010

Одним из способов выбора между interface и base class является рассмотрение владения кодом.Если вы контролируете весь код, тогда базовый класс является жизнеспособным вариантом.Если, с другой стороны, многие разные компании захотят производить сменные компоненты, то есть определяют контракт , тогда интерфейс - это ваш единственный выбор.

2 голосов
/ 22 июля 2010

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

Например, подумайте о сопоставимых. Если вы хотите создать класс Comparable, который будет расширен другими классами, он должен быть очень высоким в дереве наследования, возможно сразу после Object, и свойство, которое он выражает, состоит в том, что два объекта этого типа можно сравнивать, но есть нет способа определить это вообще (у вас не может быть реализации CompareTo непосредственно в классе Comparable, это отличается для каждого класса, который его реализует).

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

Таким образом, классы работают, когда вам нужно определить конкретный объект, такой как человек или автомобиль, и интерфейсы работают лучше, когда вам нужно более абстрактное поведение, которое слишком общее, чтобы принадлежать к любому дереву наследования, например, возможность сравниваться ) или для запуска (Runnable).

1 голос
/ 22 июля 2010

Вы можете подумать о расширении из суперкласса, если производный класс относится к одному и тому же типу. Я имею в виду, что когда класс расширяет абстрактный класс, они оба должны быть одного типа, с той лишь разницей, что суперклассимеет более общее поведение, а подкласс имеет более конкретное поведение.Интерфейс - это совершенно другое понятие.Когда класс реализует интерфейс, он либо представляет некоторый API (контракт), либо получает определенное поведение.Чтобы привести пример, я бы сказал, что Car - это абстрактный класс.Вы можете расширить множество классов, например, Ford, Chevy и т. Д., Которые относятся к типу автомобилей.Но затем, если вам нужно определенное поведение, например, вам нужен GPS в автомобиле, тогда конкретный класс, например, Ford должен реализовать интерфейс GPS.

1 голос
/ 22 июля 2010

Полагаю, я приведу пример классического автомобиля.

Если у вас есть автомобильный интерфейс, вы можете создать Ford, Chevy и Oldsmobile.Другими словами, вы создаете различные типы автомобилей из интерфейса автомобиля.

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

1 голос
/ 22 июля 2010

Я нашел несколько статей, особенно те, которые описывают, почему вы не должны использовать наследование реализации (например, суперклассы):

...