Что означает развязка двух классов на уровне интерфейса?

Допустим, что мы имеем класс A в пакете A и классе B в пакете B. Если объект класса A имеет ссылку на класс B, то говорят, что два класса имеют связь между ними.

Чтобы обратиться к муфте, рекомендуется определить интерфейс в пакете A, который реализуется классом в пакете B. Тогда объект класса A может ссылаться на интерфейс в пакете A. Это часто является примером в "инверсии зависимостей".

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

Ответы

Ответ 1

Давайте создадим вымышленный пример.

Класс A в упаковке packageA:

package packageA;

import packageB.B;

public class A {
    private B myB;

    public A() {
        this.myB = new B();
    }

    public void doSomethingThatUsesB() {
        System.out.println("Doing things with myB");
        this.myB.doSomething();
    }
}

Класс B в упаковке packageB:

package packageB;

public class B {
    public void doSomething() {
        System.out.println("B did something.");
    }
}

Как видим, A зависит от B Без B нельзя использовать A Но что, если мы хотим заменить B в будущем на BetterB? Для этого мы создаем Интерфейс Inter в packageA:

package packageA;

public interface Inter {
    public void doSomething();
}

Чтобы использовать этот интерфейс, мы

  • import packageA.Inter; и пусть B implements Inter в B и
  • замените все вхождения B в A на Inter.

Результатом является модифицированная версия A:

package packageA;

public class A {
    private Inter myInter;

    public A() {
        this.myInter = ???; // What to do here?
    }

    public void doSomethingThatUsesInter() {
        System.out.println("Doing things with myInter");
        this.myInter.doSomething();
    }
}

Мы уже видим, что зависимость от A до B исчезла: import packageB.B; больше не нужен. Есть только одна проблема: мы не можем создать экземпляр интерфейса. Но Инверсия управления приходит на помощь: вместо инстанцирования что - то типа Inter wihtin A конструктор, конструктор будет требовать то, что implements Inter в качестве параметра:

package packageA;

public class A {
    private Inter myInter;

    public A(Inter myInter) {
        this.myInter = myInter;
    }

    public void doSomethingThatUsesInter() {
        System.out.println("Doing things with myInter");
        this.myInter.doSomething();
    }
}

С этим подходом мы теперь можем изменить конкретную реализацию Inter в A по желанию. Предположим, мы пишем новый класс BetterB:

package packageB;

import packageA.Inter;

public class BetterB implements Inter {
    @Override
    public void doSomething() {
        System.out.println("BetterB did something.");
    }
}

Теперь мы можем создать экземпляр A с другим Inter -implementations:

Inter b = new B();
A aWithB = new A(b);
aWithB.doSomethingThatUsesInter();

Inter betterB = new BetterB();
A aWithBetterB = new A(betterB);
aWithBetterB.doSomethingThatUsesInter();

И нам не нужно было ничего менять внутри A Теперь код отделен, и мы можем изменить конкретную реализацию Inter по своему желанию, если контракт Inter удовлетворен (и). В частности, мы можем поддерживать код, который будет написан в будущем и реализует Inter.


Adendum

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

В литературе этот подход известен как принцип разделения интерфейсов и относится к SOLID -principles. На YouTube есть хорошая беседа от дяди Боба (интересный бит длиной около 15 минут), показывающий, как полиморфизм и интерфейсы можно использовать, чтобы позволить зависимости времени компиляции указывать на поток управления (рекомендуется на усмотрение зрителя, дядя Боб будет мягко рассуждаю про Java). Это, в свою очередь, означает, что реализация высокого уровня не должна знать о реализациях более низкого уровня, когда они сегрегируются через интерфейсы. Таким образом, более низкие уровни можно менять по желанию, как мы показали выше.

Ответ 2

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

Класс A нуждается в функции ведения журнала B, но это не важно, где записывается журнал. Он не заботится о DB, но поскольку он зависит от B, он также зависит от DB. Это не очень желательно.

Итак, что вы можете сделать, это разбить класс B на два класса: абстрактный класс L, описывающий функциональность ведения журнала (и не зависящий от DB), и реализацию в зависимости от DB.

Затем вы можете отделить класс A от B, потому что теперь A будет зависеть только от L. B теперь также зависит от L, поэтому он называется инверсией зависимостей, потому что B предоставляет функциональность, предлагаемую в L.

Так как A теперь зависит только от худшего L, вы можете легко использовать его с другим механизмом каротажа, не зависящим от DB. Например. вы можете создать простой консольный логгер, реализующий интерфейс, определенный в L.

Но так как теперь A не зависит от B, но (в источниках) только от абстрактного интерфейса L во время выполнения, он должен быть настроен для использования некоторой конкретной реализации L (B например). Поэтому должен быть кто-то еще, который сообщает A использовать B (или что-то еще) во время выполнения. И это называется инверсией управления, потому что до того, как A решил использовать B, но теперь кто-то еще (например, контейнер) сообщает A использовать B во время выполнения.

Ответ 3

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