Слабая связь между слоями приложения MVC

Делаю учебное задание - консольное приложение, реализующее MVC и Repository

.controller         - обработка действий пользователя
    .action         - действия пунктов меню
.model              - бизнес-логика приложения  
    .entity         - сущности приложения
    .repository     - подпакеты с интерфейсами и классами-хранилищами
    .service        - бизнес-логика приложения
.view               - отображение данных пользователю

Создал интерфейс, который обобщает все задачи бизнес-логики и имплементировал его классу-фасаду. Хранилища и класс-фасад - Singleton, доступ к ним по getInstance.

При работе в action мне необходимо вызвать методы фасада.

IFasad fasad = Fasad.getInstance();

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

Аналогичная ситуация с репозиториями и сервисами сущностей.

IEntityReposytori reposytori = EntityReposytory.getInstance();

Подскажите правильный вариант реализации взаимодействия между слоями.


Ответы (1 шт):

Автор решения: Sergei Buvaka

Что значит слабая связь? Это когда один объект не знает ничего о другом объекте, только об интерфейсе.

  • Сильная связь

    К примеру вы можете написать такой код:

    ArrayList<String> strings = new ArrayList<>();
    strings.add("Hello");
    

    Вы создали конкретный класс ArrayList и теперь ваш класс знает о конкретной реализации ArrayList. Чем это чревато? К примеру завтра вы решили что вам больше не подходит ArrayList, а нужен LinkedList. Вам придется идти и менять код на

    LinkedList<String> anotherStrings = new LinkedList<>();
    anotherStrings.add("Hello");
    

    Казалось бы ничего страшного, поменяли 1 строку. Теперь представим что вы передаете этот ArrayList еще в 5-10 других методов как параметр и везде вы в функции указываете ArrayList как аргумент функции:

    public void soneMethod(ArrayList<String> list){
        // some code
    }
    

    Так вот когда вы захотите использовать LinkedList вам придется поменять его во всех функциях в которых есть ваш ArrayList.

  • Слабая связь

    List<String> anotherStrings = new ArrayList<>();
    anotherStrings.add("Hello");
    

    И пример ваших функций который принимают на вход ваш список

    public void soneMethod(List<String> list){
        // some code
    }
    

    Если завтра вам понадобится LinkedList то вам придется поменять только 1 строку кода и все остальное работает.

    List<String> anotherStrings = new LinkedList<>();
    

    Почему? Потому что весь ваш код зависит от только от интерфейса List. А это значит, что вашему коду все равно что вы там напишете лишь бы оно реализовывало интерфейс List: ArrayDeque<E>, ReversedList<T> и т.д.

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

  • Что еще не хватает вам? Объекты одного класса не должны создаваться внутри другого класса (но без фанатизма, какие-то вещи все же создаются внутри класса).

    Возьмем ваш случай. У вас условно есть class Controller

    class Controller { 
    
        IEntityReposytori reposytori = EntityReposytory.getInstance();
    
        // some code
    }
    

    Внутри вашего контроллера вы создаете явный экземпляр EntityReposytory, а значит, что ваш Controller зависит от EntityReposytory, вы не сможете подменить его в тестах или динамически при создании контроллера т.к. это внутрення реализация класса Controller.

    Что можно сделать? Можно передавать IEntityReposytori в конструкторе, например так:

    class Controller { 
    
        IEntityReposytori reposytori;
    
        Controller (IEntityReposytori reposytori){
            this.reposytori = reposytori;
        }
    
        // some code
    }
    

    Что нам это даст? Так вы можете подменять объект вашего репозитория в рантайме динамически. Пример:

    public static void main(String[] args) {
        Controller controller;
        if (debug){
            controller = Controller(DebugRepository.getInstance());
        } else {
            controller = Controller(ProductionRepository.getInstance());
        }
    }
    

    Внутри класс будет работать независимо от того, что вы ему передадите, главное чтоб это "что-то" реализовало интерфейс IEntityReposytori.

Почитайте эту статью. Там неплохо объясняется, что такое "Внедрение зависимостей".

Так же рекомендую почитать серию статей про Dagger. Вы можете не читать все, если слово Dagger для вас звучит, как что-то дикое, но вот первые 3 прочесть стоит, там общие понятия и примеры:

→ Ссылка