- Баллы
- 98
- Имя в Minecraft
- hyndo
Данная теме написана, как быстрый ответ, тем кто спрашивает "Шо за хрень, так getInstance удобней использовать и быстрее, гыг)00)) Шо за всякие DI и Ioc а?".
Данная часть темы требует от вас по-сути только самых минимальных знаний явы, попробую разжевать все, чтоб было понятно. Так же все примеры будут основаны на бакит апию
Для начала следовало бы расшифровать всякие непонятные слова.
DI - Dependency Injection (Инжект зависимостей)
IoC - Inversion of Control (Инверсия контроля)
Все примеры связанные с ними мы рассмотрим ниже.
Давайте вначале попробуем сделать, класс как многие делают через getInstance, потом попробуем его немного изменить, поиграться, и понять, чем же getInstance плох на собственном опыте.
Создадим класс BadMain:
Ну вообщем-то ничего необычного, самая дефолтная картина. Давайте добавим несколько новых классов, интерфейсов, и немного функционала.
Определим какой-нибудь сервис в нашем классе, который будет что-то нибудь делать, например отправлять в консоль сообщением.
Окей, пока что у нас есть одна реализация интерфейса SomeDependencyService.
Попробуем в нашем BadMainклассе сделать ссылку на нее и заюзаем например в PlayerJoinEvent.
Сейчас наш BadMain выглядит так:
Ну в принципе со стороны если не думать, все выглядит довольно круто и понятно, так ведь?
Ну конечно же, щас попробуем добавить чуть-чуть функционала и сразу же накроемся костылями
Создадим вторую имплементацию интерфейса:
Ну и создадим похожий листенер.
Ну вообщем-то сейчас имеем два класса которые используют одинаковые интерфейсы, но разную имплементацию. Ну вот тут вот, пишет заказчик нам и говорит: "Слушай у нас тут требования поменялись, надо переделывать все", вообщем то теперь в ChatListener у нас должна юзаться не вторая имплементация SomeDependencyService, а первая та же которая в JoinListener идет. Теперь представьте, что у вас код кнч не такой как у нас щас на скрине с одним вызовом до BadMain.getInstance().getService(), ну а если там 100 вызовов? И вот это вот все заменять? А если оно еще и не в одном классе а во всем проекте так? Ну тут работы сразу на день кнч. А что если еще и BadMain.getInstance().getService() должен для разных классов возвращать разные имплементации? Будем добавлять boolean переменные как индикатор, нужно или не нужно?)) Ну тут это сразу жесткие костыли пойдут.
Ну вообщем-то один минус мы уже сами вывели: Закрепощенность архитектуры вашего приложения, ничего нельзя сдвинуть или поменять, времени на это уходит в разы больше чем если вы бы сразу сделали правильно.
Так ну идем дальше, попробуем протестировать наш код. Вы же пишите тесты, под свои приложения и не пускаете все на самотек, верно? Ну давайте попробуем для наших тестов выберем дефолтный быстрый стак из Mockito и JUnit. Ну предположим мы хотим, в нашем тесте убедиться, что doJob() из нашего service вызывается. Ну мы кнч мокним наш BadMain, мокнем Service, сделаем чтоб BadMain возвращал наш Service, ток вот дальше мы обсренькиваемся, когда понимаем, что мы в наш ChatListener новый мокнутый BadMain никак не впихнем, ну ладно запихнем в BadMain наш мокнутый instance рефлексией, ну а че, мы же крутые поцаны знаем все. Ну мы типо радуемся, работат же все. Ну захотим мы там второй тест уже написать, там уже без этого мока, а с реальным BadMain, лол а потом мы видим неявный баг, что у нашего BadMain, который статик инстенс, почему-то какой-то левый instance, именно мокнутый. Ну мы вообщем-то просераем еще час ища багу, потом понимаем ее, и решаем добавить tearDown метод в наш тест чтоб обнулял инстенс. Ладно, это сработает уже более менее нормально, но это намного усложняет написание тестов, делает их менее читаемыми, ухудшает их архитектуру, делает более хрупкими, добавляет неявное поведение. Ну а тип разработки по TDD довольной крутой, а с такой архитектурой как getInstance, вы не сможете эффективно ему следовать
Вот и второй минус, почти невозможность адекватно это тестировать.
Ну минусов конечно, гораздо больше, но на них надо приводить намного большие куски кода, чтобы отчетливо понять.
Сейчас попробуем переписать над BadMain в GoodMain:
Окей, что изменилось? Мы вместо прямого обращения к GoodMain.getInstance() вообще его убрали и сделали его прием в конструкторе. Теперь в наших тестах мы можем легко передавать какие угодно реализации нашего интерфейса, не зависимо от ссылки в GoodMain. Так же мы избавились от того, что наши листенеры напрямую зависили от GoodMain и переиспользовать их было никак нельзя, тоесть таким образом мы легко избавились от одной лишней зависимости, которая довольно сильно портила нам архитектуру. Такой класс будет очень легко тестируемый, все наши компоненты практически полностью decoupled друг от друга. Ну и кстати, если после прочтения этой статьи, вы все таки решили поменять у себя архитектуру и когда начали менять, увидели то, что в каком-то из ваших классов надо передавать огромную тучу аргументов, из 10 классов, то вы смело можете идти читать про SOLID принципы, потому что ваш класс не соответствует первому из них - SRP (Единая ответственность класса). Ну и впринципе если вам уж так лень создавать конструкторы и передавать в них зависимости - это хороший повод почитать про IoC container по типу spring и guice.
Ну а что на счет других плагинов, как WorldGuard, в которых есть getInstance ? Данные методы в посторонних плагинах предназначены для вызова только один раз в вашем onEnable.
Пример:
Если вы прочитали статью выше, то я думаю у вас не осталось вопросов почему в примере выше мы передали экземпляр плагина через конструктор, а не вызвали напрямую WorldGuardPlugin.inst().
Вроде бы все, возможно чего-то не досказал.
Данная часть темы требует от вас по-сути только самых минимальных знаний явы, попробую разжевать все, чтоб было понятно. Так же все примеры будут основаны на бакит апию
Для начала следовало бы расшифровать всякие непонятные слова.
DI - Dependency Injection (Инжект зависимостей)
IoC - Inversion of Control (Инверсия контроля)
Все примеры связанные с ними мы рассмотрим ниже.
Давайте вначале попробуем сделать, класс как многие делают через getInstance, потом попробуем его немного изменить, поиграться, и понять, чем же getInstance плох на собственном опыте.
Создадим класс BadMain:
PHP:
public class BadMain extends JavaPlugin {
private static BadMain instance;
@Override
public void onEnable() {
instance = this;
}
public static BadMain getInstance() {
return instance;
}
}
Ну вообщем-то ничего необычного, самая дефолтная картина. Давайте добавим несколько новых классов, интерфейсов, и немного функционала.
Определим какой-нибудь сервис в нашем классе, который будет что-то нибудь делать, например отправлять в консоль сообщением.
PHP:
public interface SomeDependencyService {
void doJob();
}
public class SomeDependencyServiceImpl implements SomeDependencyService {
@Override
public void doJob() {
System.out.println("Working in SomeDependencyServiceImpl");
}
}
Попробуем в нашем BadMainклассе сделать ссылку на нее и заюзаем например в PlayerJoinEvent.
Сейчас наш BadMain выглядит так:
PHP:
public class BadMain extends JavaPlugin {
private static BadMain instance;
private SomeDependencyService service;
@Override
public void onEnable() {
instance = this;
service = new SomeDependencyServiceImpl();
Bukkit.getPluginManager().registerEvents(new ListenerThatDependOnMain(), this);
}
public static BadMain getInstance() {
return instance;
}
public SomeDependencyService getService() {
return instance.service;
}
}
public class ListenerThatDependOnMain implements Listener {
@EventHandler
public void onJoin(PlayerJoinEvent event) {
BadMain.getInstance().getService().doJob();
}
}
Ну в принципе со стороны если не думать, все выглядит довольно круто и понятно, так ведь?
Ну конечно же, щас попробуем добавить чуть-чуть функционала и сразу же накроемся костылями
Создадим вторую имплементацию интерфейса:
Код:
public class AnotherServiceImpl implements SomeDependencyService {
@Override
public void doJob() {
System.out.println("Working in AnotherServiceImpl");
}
}
Код:
public class ChatListener implements Listener {
@EventHandler
public void onJoin(AsyncPlayerChatEvent event) {
BadMain.getInstance().getAnotherService().doJob();
}
}
Ну вообщем-то один минус мы уже сами вывели: Закрепощенность архитектуры вашего приложения, ничего нельзя сдвинуть или поменять, времени на это уходит в разы больше чем если вы бы сразу сделали правильно.
Так ну идем дальше, попробуем протестировать наш код. Вы же пишите тесты, под свои приложения и не пускаете все на самотек, верно? Ну давайте попробуем для наших тестов выберем дефолтный быстрый стак из Mockito и JUnit. Ну предположим мы хотим, в нашем тесте убедиться, что doJob() из нашего service вызывается. Ну мы кнч мокним наш BadMain, мокнем Service, сделаем чтоб BadMain возвращал наш Service, ток вот дальше мы обсренькиваемся, когда понимаем, что мы в наш ChatListener новый мокнутый BadMain никак не впихнем, ну ладно запихнем в BadMain наш мокнутый instance рефлексией, ну а че, мы же крутые поцаны знаем все. Ну мы типо радуемся, работат же все. Ну захотим мы там второй тест уже написать, там уже без этого мока, а с реальным BadMain, лол а потом мы видим неявный баг, что у нашего BadMain, который статик инстенс, почему-то какой-то левый instance, именно мокнутый. Ну мы вообщем-то просераем еще час ища багу, потом понимаем ее, и решаем добавить tearDown метод в наш тест чтоб обнулял инстенс. Ладно, это сработает уже более менее нормально, но это намного усложняет написание тестов, делает их менее читаемыми, ухудшает их архитектуру, делает более хрупкими, добавляет неявное поведение. Ну а тип разработки по TDD довольной крутой, а с такой архитектурой как getInstance, вы не сможете эффективно ему следовать
Вот и второй минус, почти невозможность адекватно это тестировать.
Ну минусов конечно, гораздо больше, но на них надо приводить намного большие куски кода, чтобы отчетливо понять.
Ну а как тогда надо делать правильно?
Сейчас попробуем переписать над BadMain в GoodMain:
Код:
public class GoodMain extends JavaPlugin {
private SomeDependencyService service;
private SomeDependencyService anotherService;
@Override
public void onEnable() {
service = new SomeDependencyServiceImpl();
anotherService = new AnotherServiceImpl();
Bukkit.getPluginManager().registerEvents(new ListenerThatDependOnMain(service), this);
Bukkit.getPluginManager().registerEvents(new ChatListener(anotherService), this);
}
public static class ListenerThatDependOnMain implements Listener {
private SomeDependencyService service;
public ListenerThatDependOnMain(SomeDependencyService service) {
this.service = service;
}
@EventHandler
public void onJoin(PlayerJoinEvent event) {
service.doJob();
}
}
public static static class ChatListener implements Listener {
private SomeDependencyService service;
public ChatListener(SomeDependencyService service) {
this.service = service;
}
@EventHandler
public void onJoin(AsyncPlayerChatEvent event) {
service.doJob();
}
}
public interface SomeDependencyService {
void doJob();
}
public static class SomeDependencyServiceImpl implements SomeDependencyService {
@Override
public void doJob() {
System.out.println("Working in SomeDependencyServiceImpl");
}
}
public static class AnotherServiceImpl implements SomeDependencyService {
@Override
public void doJob() {
System.out.println("Working in AnotherServiceImpl");
}
}
}
Ну а что на счет других плагинов, как WorldGuard, в которых есть getInstance ? Данные методы в посторонних плагинах предназначены для вызова только один раз в вашем onEnable.
Пример:
Код:
public class WorldGuardEx extends JavaPlugin {
@Override
public void onEnable() {
new GoodClass(WorldGuardPlugin.inst());
}
}
class GoodClass {
private WorldGuardPlugin worldGuardPlugin;
public GoodClass(WorldGuardPlugin worldGuardPlugin) {
this.worldGuardPlugin = worldGuardPlugin;
}
boolean canBuild(Player player, Block block) {
return worldGuardPlugin.canBuild(player, block);
}
}
Вроде бы все, возможно чего-то не досказал.
Последнее редактирование:

