Когда реализовать интерфейс и когда расширить суперкласс?

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

Таким образом, когда Вы реализуете интерфейс и когда Вы расширяете суперкласс?

37
задан Neil N 22 July 2010 в 18:42
поделиться

11 ответов

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

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

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

42
ответ дан 27 November 2019 в 04:34
поделиться

Никто?

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

EDIT: Я должен предоставить больше, чем ссылку

Вот ситуация. В развитие приведенного ниже примера с автомобилем рассмотрим следующее

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);
    }
}
3
ответ дан 27 November 2019 в 04:34
поделиться

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

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

7
ответ дан 27 November 2019 в 04:34
поделиться

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

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

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

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

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

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

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

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

3
ответ дан 27 November 2019 в 04:34
поделиться

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

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

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

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

2
ответ дан 27 November 2019 в 04:34
поделиться

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

1
ответ дан 27 November 2019 в 04:34
поделиться

Наверное, я приведу классический пример с автомобилем.

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

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

1
ответ дан 27 November 2019 в 04:34
поделиться

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

1
ответ дан 27 November 2019 в 04:34
поделиться

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

0
ответ дан 27 November 2019 в 04:34
поделиться

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

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

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

12
ответ дан 27 November 2019 в 04:34
поделиться

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

2
ответ дан 27 November 2019 в 04:34
поделиться
Другие вопросы по тегам:

Похожие вопросы: