Временная связь происходит между последовательными вызовами методов, когда они должны оставаться в определенном порядке. Это неизбежно в императивном программировании, но мы можем уменьшить его негативное влияние, просто превратив эти статические процедуры в функции. Посмотрите на этот пример:
class Foo {
public List<String> names() {
List<String> list = new LinkedList();
Foo.append(list, "Jeff");
Foo.append(list, "Walter");
return list;
}
private static void append(
List<String> list, String item) {
list.add(item.toLowerCase());
}
}
Что ты об этом думаешь? Я считаю, что понятно, что names()
делает — создание списка имен. Чтобы избежать дублирования, существует дополнительная процедура , append()
которая преобразует элемент в нижний регистр и добавляет его в список.
Это плохой дизайн.
Это процедурный дизайн, и в методе есть временная связь между строками names()
.
Позвольте мне сначала показать вам лучший (хотя и не лучший!) Дизайн:
class Foo {
public List<String> names() {
return Foo.with(
Foo.with(
new LinkedList(),
"Jeff"
),
"Walter"
);
}
private static List<String> with(
List<String> list, String item) {
list.add(item.toLowerCase());
return list;
}
}
Идеальная схема для метода with()
создаст новый экземпляр List
, заполнит его addAll(list)
, затем перейдет add(item)
к нему и, наконец, вернет. Это было бы совершенно неизменным , но медленно.
Итак, что не так с этим:
List<String> list = new LinkedList();
Foo.append(list, "Jeff");
Foo.append(list, "Walter");
return list;
Выглядит идеально чисто, не так ли? Создайте список, добавьте к нему два элемента и верните его. Да, это чисто — пока. Потому что мы помним, что append()
делаем. Через несколько месяцев мы вернемся к этому коду, и он будет выглядеть так:
List<String> list = new LinkedList();
// 10 more lines here
Foo.append(list, "Jeff");
Foo.append(list, "Walter");
// 10 more lines here
return list;
Это так ясно сейчас, что append()
на самом деле добавляет "Jeff"
к list
? Что будет, если я уберу эту строку? Повлияет ли это на результат, возвращаемый в последней строке? Я не знаю. Мне нужно проверить тело метода, append()
чтобы убедиться.
А как насчет того, чтобы вернуться list
сначала и позвонить append()
потом? Вот что возможный «рефакторинг» может сделать с нашим кодом:
List<String> list = new LinkedList();
if (/* something */) {
return list;
}
// 10 more lines here
Foo.append(list, "Walter");
Foo.append(list, "Jeff");
// 10 more lines here
return list;
Прежде всего, мы возвращаемся list
слишком рано, когда он еще не готов. Но кто-нибудь сказал мне, что эти два вызова append()
должны произойти раньше return list
? Во-вторых, мы изменили порядок append()
звонков. Опять же, кто-нибудь сказал мне, что важно называть их именно в этом порядке?
Никто. Нигде. Это называется временной связью .
Наши линии связаны между собой. Они должны оставаться в этом конкретном порядке, но знания об этом порядке скрыты. Разрушить порядок легко, и наш компилятор не сможет нас поймать.
Наоборот, у этого дизайна нет никакого «заказа»:
return Foo.with(
Foo.with(
new LinkedList(),
"Jeff"
),
"Walter"
);
Он просто возвращает список, который создается несколькими вызовами with()
метода. Это одна строка вместо четырех.
Как обсуждалось ранее , идеальный метод в ООП должен иметь только одно утверждение, и это утверждение есть return
.
То же самое относится и к валидации. Например, этот код плохой:
list.add("Jeff");
Foo.checkIfListStillHasSpace(list);
list.add("Walter");
Хотя этот намного лучше
list.add("Jeff");
Foo.withEnoughSpace(list).add("Walter");
Увидеть разницу?
И, конечно, идеальным подходом было бы использовать составные декораторы вместо этих уродливых статических методов. Но если по какой-то причине это невозможно, просто не делайте эти статические методы похожими на процедуры. Убедитесь, что они всегда возвращают результаты, которые становятся аргументами для дальнейших вызовов.