Почему интерфейсы упрощают жизнь?

283
19 января 2017, 03:21

Ранее никогда не писал собственные интерфейсы для "упрощения жизни". Почему? Что в них такого интересного?

В комментариях попросили добавить в вопрос мои знания и область работы. Пишу android приложения и... Все. Об интерфейсах не знаю ничего, только то, что можно класс от интерфейса наследовать и переопределить методы. Но зачем? Смысл интерфейсов?

Answer 1

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

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

Допустим у меня есть сферический в вакууме метод, делающий какую-то работу с коллекцией:

/*Здесь может быть любой тип коллекции, который реализует соотв. интерфейс.
Причем я использую интерфейс самого "низкого" уровня, т.к.
он полностью покрывает данный функционал (т.е. проход по элементам в цикле)
Также я могу написать любую свою реализацию для этого интерфейса
*/
public void showCollectionAtConsole(Iterable<?> col){
   col.forEach(object -> System.out.println(object));
}

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

Теперь у меня есть другой метод, реализующий какой-то функционал:

public boolean removeOrAddMyObject (Collection<MyObject> col, MyObject object){
    //Если удалил объект - true, иначе - false
    boolean isRemove = true;
    if (col!=null){
        if (col.isEmpty() || !col.remove(object)) {
            col.add(object);
            isRemove = false;
        }
    }else throw new IllegalStateException("Коллекция = null");
    return isRemove;
}

Здесь мне приходится использовать три метода коллекции: isEmpty(), add(), remove(). Однако все эти методы описаны в интерфейсе Collection и нет нужды в качестве аргумента передавать более "высокий" интерфейс или класс с реализацией. Как я уже писал выше, данное решение позволит мне с чистой совестью переиспользовать неоднократно данный метод для других реализаций.

Также, как мне кажется, отличным примером использования интерфейсов являются Callback'и:

public final class MyClass {
    static class MyObject {
        int id;
        String name;
        public MyObject(int id, String name) {
            this.id = id;
            this.name = name;
        }
        public void setId(int id) {
            this.id = id;
        }
        public void setName(String name){
            this.name = name;
        }
        @Override
        public String toString(){
            return "My Object: id = "+id+", name = "+name;
        }
    }
    //Интерфейс коллбэка
    interface MyCallback<T extends MyObject> {
        void doSome(T myObject);
    }

    static void doSomeAwesome(MyObject object, MyCallback<MyObject> callback) {
        System.out.println(object);
        callback.doSome(object);
        System.out.println(object);
    }
    public static void main(String[] args) {
        MyObject myObject = new MyObject(1,"someName");
        MyCallback<MyObject> callback = new MyCallback<MyObject>() {
            @Override
            public void doSome(MyObject myObject) {
                myObject.setId(2);
                myObject.setName("someOtherName");
            }
        };
        doSomeAwesome(myObject,callback);
    }
}

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

Если выполнить код на выходе будет:

My Object: id = 1, name = someName

My Object: id = 2, name = someOtherName

UPD: Также следует понимать, что есть и другие контексты для использования интерфейсов:

  • контекст множественного наследования (в Java нельзя наследоваться от более 1 класса, но можно имплементировать более 1 интерфейса)
  • интерфейс как прослойка между взаимодействующими сущностями. Т.е. есть два модуля в программе которые выполняют разные функции и при этом взаимодействуют друг с другом. В идеальной ситуации оба модуля должны представлять друг для друга "черный ящик" с необходимым минимумом методов, описываемых интерфейсами. Таким образом куда проще избежать сложностей при последующей отладке кода.
  • контекст проектирования архитектуры приложения. Как правило, на начальном этапе проектирования архитектуры будущего приложения, очень удобно "прикидывать" взаимодействие между сущностями с помощью интерфейсов. Это как будто черновой вариант, который не требует "заморачиваться" с реализацией, и позволяет относительно безболезненно модифицировать архитектуру на ранних этапах.
  • контекст тестирования. Как уже упоминали в одном из ответов, использование интерфейсов позволяет: "использовать Mock-объекты вместо настоящих". Поверьте, в перспективе это очень облегчает жизнь.
Answer 2

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

На практике это приводит к следующему:

При использовании интерфейсов появляется возможность заменять один класс, реализующий интерфейс, на другой класс, реализующий этот же интерфейс, без переписывания всего кода. Например, если методу передаётся Map:

public void handle(Map map) { ... }

то не придётся менять описание метода, если потребуется использовать TreeMap вместо HashMap.
Аналогично если метод возвращает Map: он может начать возвращать TreeMap вместо HashMap, и трагедии библейских масштабов при этом не случится, так как код, работающий с возвращенным этим методом значением, имеет дело с Map.
Это увеличивает гибкость кода: проще переключаться с одного источника данных на другой и с одной реализации на другую.
Также это полезно при тестировании, так как позволяет использовать Mock-объекты вместо настоящих.

Если нужно одинаково обрабатывать коллекции элементов (например, ArrayList и Set, возвращаемый методом keySet() у HashMap), то достаточно описать метод так:

public <T> void handle(Collection<T> elements) { ... }

Использование generics-типа здесь для большей реалистичности. Без использования интерфейса пришлось создавать два метода:

public <T> void handle(ArrayList<T> elements) { ... }
public <T> void handle(Set<T> elements) { ... }

И либо дублировать код, либо, например, во втором методе создавать ArrayList, добавлять в него все элементы из Set и вызывать первый метод.

Также использование интерфейсов позволяет объединить разные объекты, реализующий один и тот же интерфейс, в один список и одинаково их обрабатывать:

public interface Animal
{
    public void feed();
}
public class Dog implements Animal
{
    public void feed() { ... }
}
public class Cat implements Animal
{
    public void feed() { ... }
}
List<Animal> animals = new ArrayList<>();
animals.add(new Cat());
animals.add(new Dog());
for (Animal animal : animals)
{
    animal.feed();
}

Без использования интерфейса пришлось бы городить "if-else" (или "switch-case") с отдельной реализацией логики для каждого типа животных.

Answer 3

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

Answer 4

Интерфейсы и в правду упрощают жизнь .Допустим в android для навигации gps Google api использую интерфейс, для слушателя используют интерфейс (Интерфейсы используются почти везде даже в потоках )А начиная с Java-8 интерфейсы реализуют и тело, так что можно уже писать не только условие но и тело интерфейса .Но дело каждого использовать их или нет .Не всегда программист обязан реализовывать свои интерфейсы.Правила "написание хорошего кода" гласит:сперва составь интерфейсы абстракцию потом реализуй))

READ ALSO
Передать значение ключа в массив

Передать значение ключа в массив

Доброе утроПишу функцию календаря, где есть 2 аргумента месяц и год

290
Как получить все View из ViewGroup?

Как получить все View из ViewGroup?

ЗдравствуйтеПросьба помочь с написанием первого виджета

334
Android. Не спрашивать чем читать NFC метку, если программа уже запущена

Android. Не спрашивать чем читать NFC метку, если программа уже запущена

Доброго времени сутокПишу программу для чтения и записи данных через NFC

314