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

Всем привет! Изучаю java через написание пет проекта. Первая мысль, что то-то не так, появилась когла получил круговую зависимость. Но когда дошел до написания unit тестов, сомнений, что мой код "попахивает", уже не осталось... :) Помогите, пожалуйста разобраться, как правильно внедрять зависимости. Скажем, есть класс игра и метод, реализующий бизнес-логику игры. В процессе игры необходимо общаться с клиентами:

public class Game {
public void play() {
    //send message  with request player's cards to attack
    //if combination of cards is not correct send a warning
    //send message with request player's cards to defense
    //if combination of cards is not correct send a warning
    }
}

Создаю для этого условный MessageSender:

public class MessageSender {
    public void sendMessage(String message) {
        System.out.println(message);
    }
}

Вот есть очевидный путь - внедрение зависимости через конструктор:

public class Game {
    private final MessageSender messageSender;

    public Game(MessageSender messageSender) {
        this.messageSender = messageSender;
    }
    public void play() {
        //send message  with request player's cards to attack
        messageSender.sendMessage("Choose your cards to attack: ");
        //if combination of cards is not correct send a warning
        boolean isAttackCombinationCorrect = true;
        if (!isAttackCombinationCorrect) {
            messageSender.sendMessage("Your attack combination is not correct. Try again: ");
        }
        //send message with request player's cards to defense
        messageSender.sendMessage("Choose your cards to defense: ");
        //if combination of cards is not correct send a warning
        boolean isDefenseCombinationCorrect = true;
        if (!isDefenseCombinationCorrect) {
            messageSender.sendMessage("Your defense combination is not correct. Try again: ");
        }
    }
}

Но вроде бы, MessageSender - не свойство Game, тогда, может быть, правильнее передавать эту зависимость в метод:

public class Game {

    public void play(MessageSender messageSender) {
        //send message  with request player's cards to attack
        messageSender.sendMessage("Choose your cards to attack: ");
        //if combination of cards is not correct send a warning
        boolean isAttackCombinationCorrect = true;
        if (!isAttackCombinationCorrect) {
            messageSender.sendMessage("Your attack combination is not correct. Try again: ");
        }
        //send message with request player's cards to defense
        messageSender.sendMessage("Choose your cards to defense: ");
        //if combination of cards is not correct send a warning
        boolean isDefenseCombinationCorrect = true;
        if (!isDefenseCombinationCorrect) {
            messageSender.sendMessage("Your defense combination is not correct. Try again: ");
        }
    }
}

Однако, так я получал круговую зависимость. Тогда я попробовал другой подход - создание локального объекта:

public class Game {
    public void play() {
        MessageSender messageSender = new MessageSender();
        //send message  with request player's cards to attack
        messageSender.sendMessage("Choose your cards to attack: ");
        //if combination of cards is not correct send a warning
        boolean isAttackCombinationCorrect = true;
        if (!isAttackCombinationCorrect) {
            messageSender.sendMessage("Your attack combination is not correct. Try again: ");
        }
        //send message with request player's cards to defence
        messageSender.sendMessage("Choose your cards to defence: ");
        //if combination of cards is not correct send a warning
        boolean isDefenceCombinationCorrect = true;
        if (!isDefenceCombinationCorrect) {
            messageSender.sendMessage("Your defence combination is not correct. Try again: ");
        }
    }
}

А потом еще один другой путь - через имплементацию интерфейса MessageSender и его дефолтного метода sendMessage():

public class Game  implements MessageSender{
    public void play() {
        //send message  with request player's cards to attack
        sendMessage("Choose your cards to attack: ");
        //if combination of cards is not correct send a warning
        boolean isAttackCombinationCorrect = true;
        if (!isAttackCombinationCorrect) {
            sendMessage("Your attack combination is not correct. Try again: ");
        }
        //send message with request player's cards to defence
        sendMessage("Choose your cards to defence: ");
        //if combination of cards is not correct send a warning
        boolean isDefenceCombinationCorrect = true;
        if (!isDefenceCombinationCorrect) {
            sendMessage("Your defence combination is not correct. Try again: ");
        }
    }
}

...но стало совсем непонятно, как создать мок MessageHandler, чтобы отделить логику отправки сообщений при тестировании. При этом все эти примеры внедрения зависимостей встречаются в самом JDK. Но видно есть подвох... :) Помогите, пожалуйста, разобраться, как правильно внедрять зависимости, чтобы немного "проветрить" код.

Дополнение: Вынесу отдельно мои вопросы:

  1. Куда правильнее передавать зависимость в конструктор объекту или в метод, если эта зависимость не описывает свойства объекта.
  2. В примере 3, с использованием локального объекта, я вижу ещё одну проблему: невозможность создать мок локальной переменной для изоляции логики метода от логики отправки сообщений - прав ли я? Это нарушает SRP?
  3. В 4 примере, зависимость передается через интерфейс. Но так же невозможно создать мок вызова send message(). Корме невозможности тестирования, есть ли изъяны в этом способе?

Спасибо! Мира и здоровья всем в наступающем году!


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