Паттерн Стратегия – один из самых простых поведенческих шаблонов проектирования, предназначенный для определения семейства алгоритмов, инкапсуляции каждого из них и обеспечения их взаимозаменяемости. Это позволяет модифицировать алгоритмы независимо от их использования на стороне клиента.
Реализация: допустим, нам необходимо написать программу для только что открывшегося автозавода. Заказчик пока собирается выпускать только одну модель машин и необходима программа, для хранения характеристик этой модели. Пусть это будет переднеприводный седан. Создадим два класса один абстрактный – Car с одним методом — void drive(), второй класс – AudiA3, который описывает единственную модель автомобиля нашего автогиганта.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
abstract class AudiCar{ public void drive(){ System.out.println("All cars can drive!"); } } class AudiA3 extends AudiCar{ } public class Example{ public static void main(String[] args) { AudiA3 audiA3 = new AudiA3(); audiA3.drive(); } } |
В результате выполнения данного кода получим:
All cars can drive!
Продажи нового автомобиля бьют все рекорды и наш автоконцерн решает наладить выпуск новой модели – полноприводного седана. Что же, придется изменят нашу программу, самый простой и очевидный вариант: сделать метод void drive() абстрактным и реализовать его в классах конкретных моделей.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
abstract class AudiCar{ abstract public void drive(); } class AudiA3 extends AudiCar{ public void drive(){ System.out.println("Rear wheel drive"); } } class AudiA4 extends AudiCar{ public void drive(){ System.out.println("Full wheel drive"); } } public class Example{ public static void main(String[] args) { AudiA3 audiA3 = new AudiA3(); audiA3.drive(); } } |
Вроде неплохо. Вот только ходят слухи, что разрабатывается прототип машины с отключаемым полным приводом. Это уже большая проблема, если мы и дальше будем следовать подобному шаблону, то потеряем гибкость нашей программы и запутаемся в огромном количестве классов машин с разной комплектацией. Приступим к рефакторингу. Воспользуемся двумя фундаментальными принципами проектирования, первый – выделить аспекты приложения, которые могут измениться и отделить их от тех, которые всегда остаются постоянными; второй – программировать на уровне интерфейсов (супертипов), а не на уровне реализаций.
программируйте на уровне интерфейсов, а не реализаций
Изменяется у нас метод void drive(), с него и начнем и создадим интерфейс Drivable c двумя классами, которые его реализуют: один для полноприводных машин, второй для переднеприводных.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
interface Drivable{ public void drive(); } class FullWheelDrive implements Drivable{ public void drive(){ System.out.println("Full wheel drive"); } } class RearWheelDrive implements Drivable{ public void drive(){ System.out.println("Rear wheel drive"); } } |
Теперь изменим наш главный класс AudiCar и классы, которые от него наследуются.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
abstract class AudiCar{ Drivable drivable; public void performDrive(){ drivable.drive(); } } class AudiA3 extends AudiCar{ public AudiA3(){ drivable = new RearWheelDrive(); } } class AudiA4 extends AudiCar{ public AudiA4(){ drivable = new FullWheelDrive(); } } |
Как видно из кода, метод void drive() теперь не зависит от конкретной реализации класса модели машины. Главный метод тоже подвергнется небольшим изменениям:
1 2 3 4 5 6 7 8 |
public class Example{ public static void main(String[] args) { AudiCar audiA3 = new AudiA3(); audiA3.performDrive(); AudiCar audiA4 = new AudiA4(); audiA4.performDrive(); } } |
Если Вы заметили, то теперь мы программируем на уровне супертипов (AudiCar audiA3 вместо AudiA3 audiA3). В результате выполнения этого кода получим:
Rear wheel drive
Full wheel drive
Вернемся к машинам с отключаемым полным приводом. При нашей переработанной архитектуре приложения это будет легко реализовать с помощью метода сеттера, который мы добавим в наш главный класс AudiCar, заодно улучшим инкапсуляцию сделав переменную drivable приватной.
1 2 3 4 5 6 7 8 9 10 11 12 |
abstract class AudiCar{ private Drivable drivable; public void setDrivable(Drivable drivable){ this.drivable = drivable; } public void performDrive(){ drivable.drive(); } } |
И немного изменим реализацию классов конкретных моделей машин:
1 2 3 4 5 6 7 8 9 10 11 |
class AudiA3 extends AudiCar{ public AudiA3(){ setDrivable(new RearWheelDrive()); } } class AudiA4 extends AudiCar{ public AudiA4(){ setDrivable(new FullWheelDrive()); } } |
Теперь мы можем изменять поведение нашей машины на ходу:
1 2 3 4 5 6 7 8 9 |
public class Example{ public static void main(String[] args) { AudiCar audiA4 = new AudiA4(); audiA4.performDrive(); audiA4.setDrivable(new RearWheelDrive()); audiA4.performDrive(); } } |
Все работает так как мы и задумывали:
Full wheel drive
Rear wheel drive
В последней реализации нашей программы был представлен паттерн проектирования Стратегия. Благодаря ему наша программа готова к любым изменениям в комплектации машин, возникающих в бурной фантазии топ менеджеров нашего автогиганта. В качестве закрепления материала можете добавить такую характеристику для наших машин как ручная/автоматическая коробка передач.
Паттерн Стратегия полный код примера:
1 2 3 4 5 6 7 8 9 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 41 42 43 44 45 46 47 48 49 50 51 52 |
package example; abstract class AudiCar{ private Drivable drivable; public void setDrivable(Drivable drivable){ this.drivable = drivable; } public void performDrive(){ drivable.drive(); } } interface Drivable{ public void drive(); } class FullWheelDrive implements Drivable{ public void drive(){ System.out.println("Full wheel drive"); } } class RearWheelDrive implements Drivable{ public void drive(){ System.out.println("Rear wheel drive"); } } class AudiA3 extends AudiCar{ public AudiA3(){ setDrivable(new RearWheelDrive()); } } class AudiA4 extends AudiCar{ public AudiA4(){ setDrivable(new FullWheelDrive()); } } public class Example{ public static void main(String[] args) { AudiCar audiA4 = new AudiA4(); audiA4.performDrive(); audiA4.setDrivable(new RearWheelDrive()); audiA4.performDrive(); } } |
Как Вы могли заметить при всех своих плюсах у паттерна Стратегия есть один существенный недостаток – обилие дополнительных классов.