Рубрики
Без рубрики

Применяемый прагматизм: Избегайте единого интерфейса реализации

Давным-давно (думаю, в 2000 году) все классы в Java имели интерфейс. Вы впервые начали с М… Помеченный как java, прагматизм.

Давным-давно (думаю, в 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.

Да, я издеваюсь над тобой. Ничего не изменилось. Ни одного персонажа.

Превращение класса в интерфейс “позже”, в конце концов, не было болезненным.

Вот почему я называю это плохой причиной. Делать это сейчас больно и потом не принесет никакой пользы.

Итак, веская причина: Тестирование

Проблема с конкретным классом заключается в том, что вам нужно создать экземпляр. В контексте тестирования вы хотите имитировать зависимости. Для того чтобы смоделировать конкретный класс, вам нужны две вещи

  1. Расширьте класс, чтобы иметь возможность имитировать поведение
  2. Создайте экземпляр класса

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

Вот тут-то я и вмешиваюсь. Самым крутым трюком было бы создать экземпляр класса без вызова какого-либо конструктора.

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

Первоначально я занялся открытым исходным кодом, чтобы конкретно решить эту проблему. Большинство издевательских фреймворков сегодня используют Objenesis для выполнения этой задачи. Я немного говорил об этом в предыдущем посте .

Итак, начиная с 2003 года, вам не нужно бояться использовать конкретные классы в качестве зависимостей. Вы можете издеваться над ними так же, как над любым интерфейсом.

Оригинал: “https://dev.to/henritremblay/pragmatism-applied-avoid-single-implementation-interface-kll”