Задумывались ли вы, как FOR влияют на ваш код? Как они ограничивают ваш дизайн и более важно, как они преобразуют ваш код в количество строк без какого-либо человеческого смысла?
В этом посте мы увидим, как преобразовать простой пример for (предоставленный Francesco Cirillio — кампания против if) в нечто более читабельное и хорошо разработанное.
Итак, начнем с исходного кода, использующего FOR :
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
public class Department { private List<Resource> resources = new ArrayList<Resource>(); public void addResource(Resource resource) { this .resources.add(resource); } public void printSlips() { for (Resource resource : resources) { if (resource.lastContract().deadline().after( new Date())) { System.out.println(resource.name()); System.out.println(resource.salary()); } } } } |
Смотрите метод printSlips . Этот простой метод, всего 10 строк, считающий белые линии, но нарушающий одно из самых важных правил, этот метод делает внутри себя более чем одну вещь, смешивая разные уровни абстракций.
Как отмечает Роберт К. Мартин в своей книге «Функции должны делать одно». Они должны делать хорошо. Они должны делать это только […]. Если функция выполняет только шаги, которые находятся на один уровень ниже указанного имени функции, то функция выполняет одно действие […]. ‘ ,
Итак, с заданным определением того, как должен выглядеть метод, давайте вспомним предыдущий метод и посмотрим, как много вещей происходит.
метод printSlips ? Конкретно четыре .
01
02
03
04
05
06
07
08
09
10
|
public void printSlips() { for (Resource resource : resources) { #Cycle if (resource.lastContract().deadline().after( new Date())) { #Selection #Media System.out.println(resource.name()); #Content System.out.println(resource.salary()); } } } |
Метод заключается в цикле, выборе ресурсов, доступе к контенту и доступу к медиа. Обратите внимание, что каждый из них относится к разному уровню абстракции, печать на консоли должна проходить на различном уровне проверки, если ресурс еще не истек свой контракт.
Давайте посмотрим на решение, предложенное Франческо .
Первое, что нужно сделать, это разделить основные функции на три класса и два интерфейса, один для итерации ресурсов, другой для выбора того, что ресурс еще не истек, и еще один для печати ресурса. Используя этот подход, мы создаем решение, которое должно быть расширено, а также улучшаем читаемость.
И теперь пришло время для кода:
Интерфейс предиката будет использоваться для реализации, если ресурс соответствует внедренному условию.
1
2
3
4
5
|
public interface Predicate { boolean is(Resource each); } |
Например, в нашем случае реализация интерфейса будет выглядеть так:
1
2
3
4
5
6
|
public class InForcePredicate implements Predicate { public boolean is(Resource each) { return each.lastContract().deadline().after( new Date()); } } |
Мы условно переместились в класс InForcePredicate . Обратите внимание, что если мы хотим создать класс, который проверяет, истек ли срок действия контракта, мы создадим новый класс, реализующий Predicate с чем-то вроде return each.lastContract (). Deadline (). Before (new Date ()) ;
Блок интерфейса будет следующим интерфейсом, который будет реализовывать доступ к медиа. В этом случае для консоли:
1
2
3
4
5
|
public interface Block { void evaluate(Resource resource); } |
И его реализация:
1
2
3
4
5
6
7
8
|
public class PrintSlip implements Block { public void evaluate(Resource resource) { System.out.println(resource.name()); System.out.println(resource.salary()); } } |
Еще раз отметим, что изменение места отправки информации (консоли, файла, сети и т. Д.) — это просто вопрос реализации интерфейса блока .
И последний класс — это тот, который содержит итератор над ресурсами, а также предоставляет методы для вызова каждого интерфейса, созданного ранее:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
public class ResourceOrderedCollection { private Collection<Resource> resources = new ArrayList<Resource>(); public ResourceOrderedCollection() { super (); } public ResourceOrderedCollection(Collection<Resource> resources) { this .resources = resources; } public void add(Resource resource) { this .resources.add(resource); } public void forEachDo(Block block) { Iterator<Resource> iterator = resources.iterator(); while (iterator.hasNext()) { block.evaluate(iterator.next()); } } public ResourceOrderedCollection select(Predicate predicate) { ResourceOrderedCollection resourceOrderedCollection = new ResourceOrderedCollection(); Iterator<Resource> iterator = resources.iterator(); while (iterator.hasNext()) { Resource resource = iterator.next(); if (predicate.is(resource)) { resourceOrderedCollection.add(resource); } } return resourceOrderedCollection; } } |
Смотрите следующие три важных момента:
- Во-первых, конструктор получает список ресурсов.
- Вторым является то, что метод select получает предикат, который выполняется в итераторе, чтобы узнать, является ли ресурс выбранным для печати или нет. Наконец, возвращаем новый экземпляр ResourceOrderedCollection с ресурсами без истекшего контракта.
- Третий метод forEachDo получает интерфейс Block, который вызывается каждым элементом списка ресурсов.
И, наконец, модифицированный класс Department с использованием ранее разработанных классов:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
public class Department { private List<Resource> resources = new ArrayList<Resource>(); public void addResource(Resource resource) { this .resources.add(resource); } public void printSlips() { new ResourceOrderedCollection( this .resources).select( new InForcePredicate()).forEachDo( new PrintSlip()); } } |
Обратите внимание, что теперь метод printSlips содержит одну читаемую строку с тем же уровнем абстракции.
Обратите внимание, что имена классов и интерфейсов взяты из примера Франческо , но если бы я сделал то же самое, я бы выбрал более репрезентативные имена. Подход Сирилло хорош, но имеет некоторые незначительные аспекты, которые следует учитывать. Например, у него есть « вертикальная проблема »: экземпляр InForcePredicate из интерфейса Predicate использует пять строк исходного кода для инкапсуляции одного оператора.
Мы исследовали два возможных решения проблемы, являясь последним предложенным Сирилио. Также есть много других возможных и правильных решений этой проблемы, например, использование шаблона метода шаблона или смешивание использования Lambdaj с (или без) замыканиями ( синтаксис Lambdaj может быть немного запутанным). У всех них есть свои плюсы и минусы, но все они делают ваш код читабельным и более важным, все функции делают одно, они делают хорошо, и они делают это только.
В качестве заключительных замечаний к этому посту, JDK 8 будет изначально обеспечивать поддержку замыканий, а также предоставит множество функций, которые теперь предоставляет Lambdaj . Между тем JDK 8 не является стабильным (запланировано на середину финала 2013 года) или для вашего унаследованного кода (с точки зрения JDK 8), Lambdaj — действительно хороший попутчик.
Мы продолжаем учиться.
Ссылка: Избежание FORS — кампания Anti-If от нашего партнера JCG Алекса Сото в блоге One Jar To Rule All .