Давным-давно (думаю, в 2000 году) все классы в Java имели интерфейс. Вы впервые начали с MyInterface
затем добавил MyInterfaceImpl
.
Это вызвало много проблем с шаблоном и отладкой. Раньше у меня был генератор кода, чтобы упростить это.
Зачем мы это делали?
По двум причинам. Плохой и хороший.
Плохая причина – это развязка
Идея заключалась в том, что если вы зависите от интерфейса, вы можете поменять реализацию местами, если это когда-либо понадобится.
Это проблема типа “вам это может понадобиться позже”. Каждое предложение с “возможно” и “позже” в нем следует перефразировать как “Мне все равно”. Потому что в большинстве случаев “позже” никогда не наступает, и вы просто тратите время и энергию прямо сейчас на всякий случай. Что бы ни случилось позже, с этим следует разобраться позже.
Тем не менее, вы можете возразить, что “да, но разбираться с этим позже будет гораздо больнее”. Хорошо. Давайте проверим.
Допустим, у вас есть немного сыра
public class Cheese { private final String name; public Cheese(String name) { this.name = Objects.requireNonNull(name); } public String getName() { return name; } }
Затем вы хотите извлечь сыр из базы данных.
public class CheeseDao { private final Database database; public Cheese findByName(String name) { return database.names() .filter(name::equals) .reduce((a, b) -> { throw new IllegalStateException("More than one entry found for " + name); }) .map(Cheese::new) .orElse(null); } }
И тогда у вас есть ресурс REST, зависящий от CheeseDAO
.
public class CheeseResource { private final CheeseDAO cheeseDAO; public CheeseResource(CheeseDAO cheeseDAO) { this.cheeseDAO = cheeseDAO; } public Cheese get(String name) { return cheeseDAO.findByName(name); } }
Поскольку вы эффективный человек, вы решили, что для CheeseDAO не нужен интерфейс
. Пока у него есть только одна реализация, и вы не создали новую библиотеку с открытым исходным кодом. Весь этот код находится в вашем маленьком приложении для сыра.
Но однажды появляются некоторые требования, и вам действительно нужна другая реализация “. Позже” действительно произошло.
Итак, теперь вы превращаете CheeseDAO
в интерфейс.
public interface CheeseDao { Cheese findByName(String name); } public class CheeseDatabaseDao implements CheeseDao { private final Database database; public Cheese findByName(String name) { return database.names() .filter(name::equals) .reduce((a, b) -> { throw new IllegalStateException("More than one entry found for " + name); }) .map(Cheese::new) .orElse(null); } }
А теперь приступайте к исправлению ошибок компиляции во всех классах, зависящих от CheeseDAO
.
Например, вы изменяете Сырный ресурс
к этому:
public class CheeseResource { private final CheeseDAO cheeseDAO; public CheeseResource(CheeseDAO cheeseDAO) { this.cheeseDAO = cheeseDAO; } public Cheese get(String name) { return cheeseDAO.findByName(name); } }
Я оставлю тебя на 5 секунд. 1, 2, 3, 4, 5.
Да, я издеваюсь над тобой. Ничего не изменилось. Ни одного персонажа.
Превращение класса в интерфейс “позже”, в конце концов, не было болезненным.
Вот почему я называю это плохой причиной. Делать это сейчас больно и потом не принесет никакой пользы.
Итак, веская причина: Тестирование
Проблема с конкретным классом заключается в том, что вам нужно создать экземпляр. В контексте тестирования вы хотите имитировать зависимости. Для того чтобы смоделировать конкретный класс, вам нужны две вещи
- Расширьте класс, чтобы иметь возможность имитировать поведение
- Создайте экземпляр класса
Первое требование простое, второе сложнее. Если класс прост и имеет простой конструктор для вызова, все в порядке. Если создание экземпляра класса довольно раздражает, у вас есть проблема.
Вот тут-то я и вмешиваюсь. Самым крутым трюком было бы создать экземпляр класса без вызова какого-либо конструктора.
К счастью, Java позволяет это. Потому что сериализация делает это постоянно. Тебе просто нужно немного прокрасться под капот.
Первоначально я занялся открытым исходным кодом, чтобы конкретно решить эту проблему. Большинство издевательских фреймворков сегодня используют Objenesis для выполнения этой задачи. Я немного говорил об этом в предыдущем посте .
Итак, начиная с 2003 года, вам не нужно бояться использовать конкретные классы в качестве зависимостей. Вы можете издеваться над ними так же, как над любым интерфейсом.
Оригинал: “https://dev.to/henritremblay/pragmatism-applied-avoid-single-implementation-interface-kll”