Development

Упущеные возможности полиморфизма

(В оригинале – Missing Opportunities for Polymorphism)

Полиморфизм – одна из фундаментальных идей объектно-ориентированного подхода. Перевод этого слова с греческого – «множество форм». В контексте программирования речь идет о множестве форм конкретного класса, объекта или метода. Но полиморфизм – это не только альтернативные реализации. Будучи правильно использованным, полиморфизм позволяет обходиться без конструкций if-then-else. Нахождение внутри контекста позволяет нам выполнить нужный код непосредственно, тогда как нахождение вне контекста вынуждает переделать код так, чтобы сделать это правильно. Аккуратно используя альтернативные реализации, мы сможем сделать код более компактным, а значит, более сопровождаемым. Лучше всего это продемонстрировать на примере, скажем, упрощенной корзины покупок:

public class ShoppingCart {
    private ArrayList cart = new ArrayList(); 

    public void add(Item item) { 
        cart.add(item); 
    }
    
    public Item takeNext() { 
        return cart.remove(0); 
    } 

    public boolean isEmpty() { 
        return cart.isEmpty(); 
    }
}

Пусть наш интернет-магазин предлагает два вида товаров – которые можно скачать, и которые надо посылать почтой. Создадим объект, который поддерживает следующие операции:

public class Shipping {
    public boolean ship(Item item, SurfaceAddress address) { ... } 
    public boolean ship(Item item, EMailAddress address { ... }
}

После завершения покупки необходимо выполнить доставку:

while (!cart.isEmpty()) { 
   shipping.ship(cart.takeNext(), ???);
}

«???» здесь вовсе не какой-то новый супероператор, а всего лишь вопрос, ответ на который указывает, надо ли послать этот товар по электронной почте или же по обычной. Однако контекст, необходимый для ответа на этот вопрос, уже не существует. Мы могли бы использовать флаг или enum для этого и применить конструкцию if-then-else.

Другое же решение – создать два класса, расширяющих функциональность класса item. Назовем их для примера DownloadableItem и SurfaceItem. Теперь напишем код. Сделаем Item интерфейсом с единственным методом ship. Чтобы выполнить доставку, нужно будет вызвать item.ship(). Реализация метода ship будет в классах DownloadableItem и SurfaceItem.

public class DownloadableItem implements Item { 
    public boolean ship(Shipping shipper) {
        shipper.ship(this, customer.getEmailAddress()); 
    }
}
public class SurfaceItem implements Item { 
    public boolean ship(Shipping shipper) {
        shipper.ship(this, customer.getSurfaceAddress()); 
    }
}

В этом примере мы делегировали ответственность за правильную доставку объекту Item. Поскольку каждый объект Item знает как его нужно доставить, это позволяет нам обойтись без конструкции if-then-else. Данный код также демонстрирует использование двух паттернов проектирования, часто работающих в паре: Command и Double Dispatch. Эффективное использование этих паттернов основано на правильном использовании полиморфизма.

Такое использование приведет к уменьшению количества конструкций if-then-else в коде.

И хотя бывают случаи, когда использование if-then-else оправдано более, чем применение полиморфизма, чаще всего полиморфизм дает в результате более компактный и более ясный для понимания код. Количество упущенных возможностей – это просто количество конструкций if-then-else в вашем коде.

Автор оригинала – Kirk Pepperdine