Java - объявление из интерфейса типа вместо класса - PullRequest
37 голосов
/ 02 августа 2010

В своем стремлении правильно усвоить лучшие практики интерфейса я заметил такие объявления, как:

List<String> myList = new ArrayList<String>();

вместо

ArrayList<String> myList = new ArrayList<String>();

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

С этой логикой я настроил пример:

public class InterfaceTest {

    public static void main(String[] args) {

        PetInterface p = new Cat();
        p.talk();

    }

}

interface PetInterface {                

    public void talk();

}

class Dog implements PetInterface {

    @Override
    public void talk() {
        System.out.println("Bark!");
    }

}

class Cat implements PetInterface {

    @Override
    public void talk() {
        System.out.println("Meow!");
    }

    public void batheSelf() {
        System.out.println("Cat bathing");
    }

}

У меня вопрос, яне может получить доступ к методу batheSelf (), потому что он существует только для Cat.Это наводит меня на мысль, что я должен объявлять из интерфейса только в том случае, если я собираюсь использовать только методы, объявленные в интерфейсе (а не дополнительные методы из подкласса), в противном случае я должен объявить непосредственно из класса (в данном случае Cat).Я прав в этом предположении?

Ответы [ 7 ]

33 голосов
/ 02 августа 2010

Когда есть выбор между ссылкой на объект по его interface или class, предпочтение следует отдавать первому, но , только если существует соответствующий тип .

Рассмотрим StringimplementsCharSequence в качестве примера. Вы не должны просто слепо использовать CharSequence вместо String во всех случаях, потому что это лишит вас простых операций, таких как trim(), toUpperCase() и т. Д.

Однако метод, которому требуется String только для заботы о его последовательности char значений , должен использовать вместо этого CharSequence, поскольку в данном случае это подходящий тип. Это на самом деле имеет место с replace(CharSequence target, CharSequence replacement) в классе String.

Другим примером является java.util.regex.Pattern и его метод Matcher matcher(CharSequence). Это позволяет создать Matcher из Pattern не только для String, но и для всех остальных CharSequence.

Отличный пример в библиотеке того, где должен был использоваться interface, но, к сожалению, его не было, также можно найти в Matcher: его appendReplacement и appendTail методы принимают только StringBuffer. Этот класс был в значительной степени заменен его более быстрым кузеном StringBuilder начиная с 1.5.

A StringBuilder не является StringBuffer, поэтому мы не можем использовать первое с методами append… в Matcher. Однако оба они implementsAppendable (также введено в 1.5). В идеале метод Matcher append… должен принимать любой Appendable, и тогда мы сможем использовать StringBuilder, а также все другие Appendable доступные!

Итак, мы можем видеть, как , когда существует соответствующий тип , ссылающийся на объекты через их интерфейсы, может быть мощной абстракцией, но только если эти типы существуют. Если тип не существует, то вы можете определить свой собственный, если это имеет смысл. В этом примере Cat вы можете определить, например, interface SelfBathable. Тогда вместо ссылки на Cat вы можете принять любой SelfBathable объект (например, Parakeet)

Если нет смысла создавать новый тип, тогда вы обязательно можете обратиться к нему по его class.

Смотри также

  • Effective Java 2nd Edition, Item 52: Ссылка на объекты по их интерфейсам

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

Ссылки по теме

11 голосов
/ 02 августа 2010

Да, вы правы.Вы должны объявить как наиболее общий тип, предоставляющий используемые вами методы.

Это понятие полиморфизм .

4 голосов
/ 02 августа 2010

Вы правы, но вы можете кастовать с интерфейса на нужного питомца, если вам нужно. Например:

PetInterface p = new Cat();
((Cat)p).batheSelf();

Конечно, если вы попытаетесь привести своего питомца к собаке, вы не сможете вызвать метод batheSelf (). Это даже не скомпилируется. Таким образом, чтобы избежать проблем, у вас может быть такой метод:

public void bathe(PetInterface p){
    if (p instanceof Cat) {
        Cat c = (Cat) p;
        c.batheSelf();
    }
}

При использовании instanceof вы убедитесь, что не будете пытаться заставить собаку купаться во время выполнения. Что бы выкинуть ошибку.

2 голосов
/ 17 ноября 2011

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

Все это связано с управлением зависимостями в вашем коде. Лучше полагаться только на очень абстрактные вещи (например, интерфейсы), потому что они также имеют тенденцию быть очень стабильными (см. http://objectmentor.com/resources/articles/stability.pdf). Поскольку у них нет кода, их нужно менять только при изменении API ... другими словами когда вы хотите, чтобы этот интерфейс представлял другое поведение миру, т. е. изменение дизайна.

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

Вы должны стремиться закрепить поведение ваших классов в соответствии с принципом Открыто-Закрыто (см. http://objectmentor.com/resources/articles/ocp.pdf),, чтобы существующие интерфейсы не менялись, даже когда вы добавляете функциональность, вы можете просто указать новый подинтерфейс.

Старый способ избежать использования нового оператора заключался в использовании шаблона абстрактной фабрики, но у него есть свои проблемы. Лучше использовать такой инструмент, как Guice, который выполняет внедрение зависимостей и предпочитает внедрение конструкторов. Убедитесь, что вы понимаете принцип обращения зависимостей (см. http://objectmentor.com/resources/articles/dip.pdf), прежде чем начать использовать внедрение зависимостей. Я видел, как многие люди вводят неподходящие зависимости, а потом жалуются, что инструмент им не помогает ... это не сделает вас хорошим программистом, вы все равно должны использовать его надлежащим образом.

Пример: вы пишете программу, которая помогает студентам изучать физику. В этой программе учащиеся могут поместить мяч в различные физические сценарии и посмотреть, как он себя ведет: выстрелить из пушки со скалы, положить его под воду, в дальний космос и т. Д. Вопрос: вы хотите включить что-нибудь о тяжести мяч в Ball API ... следует ли вам включить метод getMass () или метод getWeight ()?

Вес зависит от среды, в которой находится мяч. Для вызывающих абонентов может быть удобно вызывать один метод и получать вес шара, где бы он ни находился, но как вы пишете этот метод? Каждый экземпляр шара должен постоянно отслеживать, где он находится и какова текущая гравитационная постоянная. Поэтому вы должны предпочесть getMass (), потому что масса является внутренним свойством шара и не зависит от его окружения.

Подождите, а что если вы просто используете вместо этого getWeight (Environment)? Таким образом, экземпляр ball может просто получить свой текущий g из окружения и продолжить ... еще лучше, вы можете использовать Guice для добавления окружения в конструктор Ball! Такого рода злоупотребления я часто вижу, и в итоге люди обвиняют Guice в том, что он не в состоянии справиться с внедрением зависимостей так легко, как они надеялись.

Проблема здесь не в Guice, а в дизайне Ball API. Вес не является внутренним свойством мяча, поэтому это не свойство, которое должно быть доступно с мяча. Вместо этого Ball должен реализовать интерфейс MassiveObject с помощью метода getMass (), а в среде должен быть метод getWeightOf (MassiveObject). Природой для Окружающей среды является ее собственная гравитационная постоянная, так что это намного лучше. Среда теперь зависит только от простого интерфейса, MassiveObject ... но его задача состоит в том, чтобы содержать объекты, поэтому так и должно быть.

2 голосов
/ 02 августа 2010

Вы можете вызвать метод batheSelf из talk в Cat.

2 голосов
/ 02 августа 2010

Да, вы правы.Имея Cat, реализующий "PetInterface", вы можете использовать его в примере выше и легко добавлять больше видов домашних животных.Если вам действительно нужно быть привязанным к Cat, вам нужен доступ к классу Cat.

0 голосов
/ 19 марта 2013

Почему бы просто не сделать это!

Cat c = new Cat();
PetInterface p = (PetInterface)c;
p.talk();
c.batheSelf();

Теперь у нас есть один объект, которым можно манипулировать, используя 2 ссылки.
Ссылка p может использоваться для вызова функций, определенных в интерфейсе, а c может использоваться для вызова функций, определенных только в классе (или суперклассе).

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