Дизайн API библиотеки

от
Совершенный код    api, library

На написание данной статьи меня натолкнула библиотека EasyCamera, которая упрощает сложный вызов Android Camera API (статья о ней).
Часто приходится сталкиваться с тем, что для работы той или иной библиотеки необходимо прочитать документацию или поискать подробные примеры её использования. Например, чтобы заставить ту же Android Camera API показать экран видоискателя, нужно вызвать сначала метод setPreviewDisplay(holder), а затем уже startPreview(). Это не всегда очевидно и не очень удобно.
Поэтому при проектировании библиотеки следует учитывать некоторые параметры, а именно:
1. Классы и методы должны быть понятны пользователю (в данном случае пользователем является программист).
2. Не должно быть "подводных камней", когда вызов двух методов в различной последовательности приводит к ошибке.
3. Библиотека должна быть максимально простой, чтобы программисту не доводилось каждый раз читать документацию или лезть в код.

В качестве примера возьмём пример работы алгоритма Маркова.

Открыть спойлер

Сразу оговорюсь, это вообще-то не библиотека, это просто пример. Но мы сейчас сделаем из него удобную библиотеку.
Поехали!

Первым делом, следует обратить внимание на то, что исходные параметры (правила подстановки) добавляются в список таким образом:
  1. rules.add(new Replacement("~0", "00~"));
  2. rules.add(new Replacement("~1", "01~"));
  3. rules.add(new Replacement("~2", "10~"));
Это не очень удобно.
Поэтому давайте сделаем свой класс Rules, в котором добавлять правила будет проще.
  1. public final class Rules implements Iterable {
  2.  
  3.     private final List<Replacement> rules;
  4.  
  5.     public static Rules create() {
  6.         return new Rules();
  7.     }
  8.  
  9.     private Rules() {
  10.         rules = new ArrayList<>();
  11.     }
  12.  
  13.     public Rules add(String from, String to) {
  14.         return add(from, to, false);
  15.     }
  16.  
  17.     public Rules add(String from, String to, boolean endless) {
  18.         rules.add(new Replacement(from, to, endless));
  19.         return this;
  20.     }
  21.  
  22.     @Override
  23.     public Iterator iterator() {
  24.         return rules.iterator();
  25.     }
  26. }
Код вроде бы небольшой, но на парочку моментов следует обратить внимание.
Во-первых, я запретил вызов конструктора класса, сделав его приватным. Это значит, что вызвать new Rules() я уже не смогу из другого класса.
Второе, что я сделал, это добавил "фабрику" - статический метод create(), который создаёт объект класса Rules.
Эти два шага позволяют создать объект вот такой строчкой кода:
Rules rules = Rules.create();
Конечно, в данном случае можно было этого не делать и пользоваться конструктором класса, но в больших классах/библиотеках фабрика позволяет красиво и понятно создать объект. Вот небольшой пример использования фабрики:
В Java SE есть несколько видов границ элементов (кнопок, текстовых полей и т.д) - пустая граница, граница в виде линии, "выдавленная" граница и т.д. Мы можем явно создать объект через конструктор класса:
  1. Border border = new LineBorder(Color.BLACK);
  2. Border empty = new EmptyBorder(0, 0, 0, 0);
а можем воспользоваться фабрикой:
  1. BorderFactory.createLineBorder(Color.BLACK)
  2. BorderFactory.createEmptyBorder();
Если после вызова конструктора нужно ещё как-нибудь подготовить объект, то фабрика поможет в этом.

Идём дальше, в каждый метод я добавил return this; зачем?
А затем, чтобы получился вот такой красивый вызов:
  1. Rules rules4to2 = Rules.create()
  2.     .add("~0", "00~")
  3.     .add("~1", "01~")
  4.     .add("~2", "10~")
А теперь сравните это с тем, что было в первоначальном варианте. Думаю, очевидно, какой код красивее и понятнее.

Точно также сделаем класс Markov.
Открыть спойлер
В этот раз у нас два фабричных метода: create() - просто инициализирует класс и with(Rules rules) - инициализирует класс с набором правил.
Конструктор всё также приватный, чтобы пользователь не мог вмешаться в отлаженную работу библиотеки.

Вот теперь работа с нашей библиотекой будет выглядеть так:
Открыть спойлер
Сначала мы создаём правила для преобразования из четверичного числа в двоичное, потом из HEX в BIN, а затем одним махом получаем результат для нужных нам чисел.

Проект с исходниками (Java SE)
  • +4
  • views 4155