Добавить новое поведение в существующий метод легко, но часто это бывает не совсем верным решением. Когда изначально создается какой-то метод, то он обычно делает строго одно действие. Любое добавление нового кода выглядит немного подозрительно. Скорее всего, вы добавляете новый код, потому что хотите, чтобы он выполнялся в одно время с уже существующим кодом.
Раньше, такой способ называли “временным связыванием”, код становится довольно некрасивым, если делать это слишком часто. Если группировать процедуры вместе только потому что они должны выполняться в одно и тоже время, то связь между ними весьма слаба. Позже может выясниться, что один метод не используется без другого, и с этой точки зрения они должны логически быть одним методом. Но не торопитесь склеивать их в один метод, потому что вернуть назад может быть нелегко.
Когда нужно добавить какое-то поведение, вы можете это сделать не таким запутанным путем. Один подход - это использовать “Ветвлящийся метод”, но существует и другой подход, который иногда очень полезен. Называется он - “метод обертка”. Пример:
{% highlight java %} public class Employee { … public void pay() { Money amount = new Money(); for (Iterator it = timecards.iterator(); it.hasNext(); ) { Timecard card = (Timecard)it.next(); if (payPeriod.contains(date)) { amount.add(card.getHours() * payRate); } } payDispatcher.pay(this, date, amount); } } {% endhighlight %}
В этом методе складывается время работы за день для одного работника и затем отправляется в PayDispatcher
. Давайте предположим, что требования изменились. Каждый раз, когда мы платим работнику, нам нужно обновлять файл с информацией о работнике для отправки в какую-либо систему отчетов. Самый простой вариант - это добавить код в метод pay()
. В конце концов наш новый код должен выполняться вместе с методом pay()
, правильно же? Давайте так и сделаем:
{% highlight java %} public class Employee { private void dispatchPayment() { Money amount = new Money(); for (Iterator it = timecards.iterator(); it.hasNext(); ) { Timecard card = (Timecard)it.next(); if (payPeriod.contains(date)) { amount.add(card.getHours() * payRate); } } payDispatcher.pay(this, date, amount); }
public void pay() {
logPayment();
dispatchPayment();
}
private void logPayment() {
...
}
} {% endhighlight %}
Мы переименовали метод pay()
в dispatchPayment()
и сделали его private
. Затем создали новый метод pay(), который бужет вызывать наш
dispatchPayment(). Новый метод
pay()сначала логирует момент оплаты, а потом отправляет ее на обработку. Те клиенты, которые используют метод
pay()не узнают об изменении, да им и не нужно. Они как обычно будут вызывать метод 'pay()', и все будет по-старому. Это один из возможных видов "метода обертки". Мы создаем метод с названием нашего старого метода, и в теле вызываем старый метод. Делается это для того, чтобы добавить новое поведение к существующему методу. Каждый раз, когда клиент вызывает метод
pay()` происходит логирование - и такой вид “метода обертки” оказался, как раз к месту.
Существуют также второй вариант этого подхода, который мы можем использовать, когда хотим только добавить новый метод, метод, который никто кроме нас не вызывает. В предыдущем примере, если мы хотим, чтобы логирование было более явным, мы можем добавить метод makeLoggedPayment()
в классе Employee
:
{% highlight java %} public class Employee { public void makeLoggedPayment() { logPayment(); pay(); }
public void pay() {
...
}
private void logPayment() {
...
}
} {% endhighlight %}
Метод обертка - это хороший способ добавить новый функционал, сохраняя при этом своеобразное разделение со старым кодом. Существует только несколько минусов. Первый - это то, что новый функционал не смешан с логикой старого. Подождите, разве я сказал это плохо? Вообще-то, нет, в общем - зависит от ситуации. Второй минус - это то, что вы должны придумать новое имя для старого метода.
В нашем случае, я переименовал pay()
в dispatchPayment()
. Это имя конечно, притянуто за уши. Мне не очень нравится, как заканчивается код в нашем примере. Метод dispatchPayment()
делает гораздо больше, чем просто “обрабатывает платеж”: он так же вычисляет сумму платежа. Если бы я хотел протестировать этот метод, то скорее всего я выделелил бы еще одну функцию - calculatePay()
и тогда бы метод pay()
выглядел как-то так:
{% highlight java %} public void pay() { logPayment(); Money amount = calculatePay(); dispatchPayment(amount); } {% endhighlight %}
Теперь, кажется, каждый метод делает только то - что должен.
Вот шаги, которые мы сделали в первой версии нашего метода - обертки: . Находим метод, который будем менять . Если изменение может быть сформулировано, как последовательность утверждений, то переименовываем этот метод, а рядом создаем новый метод с именем и сигнатурами предыдущего. . Из старого делаем вызов нового . Добавляем необходимый код в старый метод и покрываем тестами - если это возможно
Во второй версии метода обертки: . Находим метод, который будем менять . Если изменение может быть сформулированно, как последовательность утверждений, то создаем новый метод для этого . Создаем третий метод, который будет вызывать наш новый метод, а затем старый
Минусы
- Иногда приводит к менее понятным именам методов. Например метод
pay()
мы переименовали вdispatchPay()
, который менее интуитивно понятен, только потому что нам нужно какое-то новое имя метода. Конечно ,если у вас есть какие-то инструменты для рефакторинга, которые позволяют быстро переименовывать методы - это этот минус не такой болезненный.
Плюсы
- Не увеличивает размеры изначальных методов
- Добавляемый функционал не зависит от старого функционала