Подскажите правильный путь внедрения зависимостей
Всем привет! Изучаю 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. Но видно есть подвох... :) Помогите, пожалуйста, разобраться, как правильно внедрять зависимости, чтобы немного "проветрить" код.
Дополнение: Вынесу отдельно мои вопросы:
- Куда правильнее передавать зависимость в конструктор объекту или в метод, если эта зависимость не описывает свойства объекта.
- В примере 3, с использованием локального объекта, я вижу ещё одну проблему: невозможность создать мок локальной переменной для изоляции логики метода от логики отправки сообщений - прав ли я? Это нарушает SRP?
- В 4 примере, зависимость передается через интерфейс. Но так же невозможно создать мок вызова send message(). Корме невозможности тестирования, есть ли изъяны в этом способе?
Спасибо! Мира и здоровья всем в наступающем году!