Реклама

пятница, 19 июля 2013 г.

Порождающие шаблоны проектирования. Фабричный метод(Factory Method)

Для того, чтобы система оставалась независимой от различных типов объектов, паттерн «Фабричный метод» использует механизм полиморфизма - классы всех конечных типов наследуются от одного абстрактного базового класса, предназначенного для полиморфного использования. В этом базовом классе определяется единый интерфейс, через который пользователь будет оперировать объектами конечных типов.

Для обеспечения относительно простого добавления в систему новых типов паттерн локализует создание объектов конкретных типов в специальном классе-фабрике. Методы этого класса, посредством которых создаются объекты конкретных классов, называются фабричными. Существуют две разновидности паттерна Factory Method:
1. Обобщенный конструктор, когда в том же самом полиморфном базовом классе, от которого наследуют производные классы всех создаваемых в системе типов, определяется статический фабричный метод. В качестве параметра в этот метод должен передаваться идентификатор типа создаваемого объекта.
2. Классический вариант фабричного метода, когда интерфейс фабричных методов объявляется в независимом классе-фабрике, а их реализация определяется конкретными подклассами этого класса.

Где на диаграмме:
  • Product — определяет интерфейс объектов, создаваемых абстрактным методом;
  • ConcreteProduct — реализует интерфейс Product;
  • Creator — создатель
    • объявляет фабричный метод, который возвращает объект типа Product. Может также содержать реализацию этого метода «по умолчанию»;
    • может вызывать фабричный метод для создания объекта типа Product;
  • ConcreteCreator — переопределяет фабричный метод таким образом, чтобы он создавал и возвращал объект класса ConcreteProduct.
Рассмотрим примеры обоих вариантов.

Обобщенный конструктор:
Boat.java

1:  package ua.dp.allmycircuits.factorymethod;  
2:  /**  
3:   * Это лодка. Она плавает  
4:   * @author winkiller  
5:   */  
6:  public class Boat extends Vehicle{  
7:    public void info() {  
8:      System.out.println("Let's swim!");  
9:    }  
10:  }  
Car.java
1:  package ua.dp.allmycircuits.factorymethod;  
2:  /**  
3:   * Это машина. Она ездит  
4:   * @author winkiller  
5:   */  
6:  public class Car extends Vehicle{  
7:    public void info() {  
8:      System.out.println("Let's drive!");  
9:    }  
10:  }  
Plane.java
1:  package ua.dp.allmycircuits.factorymethod;  
2:  /**  
3:   * Это самолет. Он летает  
4:   * @author winkiller  
5:   */  
6:  public class Plane extends Vehicle{  
7:    public void info() {  
8:      System.out.println("Let's fly!");  
9:    }  
10:  }  
Vehicle.java
1:  package ua.dp.allmycircuits.factorymethod;  
2:  /**  
3:   * Базовый класс с фабричным методом  
4:   * @author winkiller  
5:   */  
6:  public class Vehicle {  
7:    /**  
8:     * Метод будет информировать нас о действиях экземпляра класса. У каждого потомка действие будет свое.  
9:     */  
10:    public void info(){  
11:      System.out.println("This is base class");  
12:    }  
13:    /**  
14:     * Фабричный метод  
15:     * @param type на вход передаем тип  
16:     * @return   
17:     */  
18:    public static Vehicle createVehicle(VehicleType type){  
19:      Vehicle ret;  
20:      // для пущего изврата, можно определение написать с помощью рефлексии.Можно и switch использовать, только учесть что type может быть null  
21:      if(type == VehicleType.BOAT){  
22:        ret = new Boat();  
23:      }else if(type == VehicleType.CAR){  
24:        ret = new Car();  
25:      }else if(type == VehicleType.PLANE){  
26:        ret = new Plane();  
27:      }else{  
28:        ret = new Vehicle();  
29:      }  
30:      return ret;  
31:    }  
32:  }  
VehicleType.java
1:  package ua.dp.allmycircuits.factorymethod;  
2:  /**  
3:   * Перечисление возможных типов. Можно без него, но с ним надежнее))  
4:   * @author winkiller  
5:   */  
6:  public enum VehicleType {  
7:    PLANE,  
8:    CAR,  
9:    BOAT  
10:  }  
FactoryMethod.java
1:  package ua.dp.allmycircuits.factorymethod;  
2:  import java.util.ArrayList;  
3:  import java.util.List;  
4:  /**  
5:   * Этот класс запускает все описанное безобразие  
6:   * @author winkiller  
7:   */  
8:  public class FactoryMethod {  
9:    /**  
10:     * @param args the command line arguments  
11:     */  
12:    public static void main(String[] args) {  
13:      //создаем список и заполняем его потомками базового класса Vehicle  
14:      List<Vehicle> vehicles = new ArrayList<Vehicle>();  
15:      vehicles.add(Vehicle.createVehicle(VehicleType.CAR));  
16:      vehicles.add(Vehicle.createVehicle(VehicleType.BOAT));  
17:      vehicles.add(Vehicle.createVehicle(VehicleType.PLANE));  
18:      vehicles.add(Vehicle.createVehicle(null));  
19:      //А теперь по очереди у каждого дергаем метод info()  
20:      for(Vehicle v: vehicles){  
21:        v.info();  
22:      }  
23:    }  
24:  }  


С точки зрения "чистоты" объектно-ориентированного кода у этого варианта есть следующие недостатки:
  • Так как код по созданию объектов всех возможных типов сосредоточен в статическом фабричном методе класса Vehicle, то базовый класс Vehicle обладает знанием обо всех производных от него классах, что является нетипичным для объектно-ориентированного подхода.
  • Подобное использование оператора if-else или будь то switch (как в коде фабричного метода createVehicle()) в объектно-ориентированном программировании также не приветствуется.
Указанные недостатки отсутствуют в классической реализации паттерна Factory Method.
Vehicle.java
1:  package ua.dp.allmycircuits.factorymethod;  
2:  /**  
3:   * Интерфейс, описывающий транспорт  
4:   * @author winkiller  
5:   */  
6:  public interface Vehicle {  
7:    /**  
8:     * Метод будет информировать нас о действиях реализаций этого интерфейса.  
9:     */  
10:    public void info();  
11:  }  
VehicleFactory.java
1:  package ua.dp.allmycircuits.factorymethod;  
2:  /**  
3:   * Интерфейс, описывающий фабрики  
4:   * @author winkiller  
5:   */  
6:  public interface VehicleFactory {  
7:    public Vehicle createVehicle();  
8:  }  
Boat.java
1:  package ua.dp.allmycircuits.factorymethod;  
2:  /**  
3:   * Это лодка. Она плавает  
4:   * @author winkiller  
5:   */  
6:  public class Boat implements Vehicle{  
7:    public void info() {  
8:      System.out.println("Let's swim!");  
9:    }  
10:  }  
Car.java
1:  package ua.dp.allmycircuits.factorymethod;  
2:  /**  
3:   * Это машина. Она ездит  
4:   * @author winkiller  
5:   */  
6:  public class Car implements Vehicle{  
7:    public void info() {  
8:      System.out.println("Let's drive!");  
9:    }  
10:  }  
Plane.java
1:  package ua.dp.allmycircuits.factorymethod;  
2:  /**  
3:   * Это самолет. Он летает  
4:   * @author winkiller  
5:   */  
6:  public class Plane implements Vehicle{  
7:    public void info() {  
8:      System.out.println("Let's fly!");  
9:    }  
10:  }  
BoatFactory.java
1:  package ua.dp.allmycircuits.factorymethod;  
2:  /**  
3:   * Фабрика для лодок  
4:   * @author winkiller  
5:   */  
6:  public class BoatFactory implements VehicleFactory{  
7:    @Override  
8:    public Vehicle createVehicle() {  
9:      return new Boat();  
10:    }  
11:  }  
CarFactory.java
1:  package ua.dp.allmycircuits.factorymethod;  
2:  /**  
3:   * Фабрика для машин  
4:   * @author winkiller  
5:   */  
6:  public class CarFactory implements VehicleFactory{  
7:    @Override  
8:    public Vehicle createVehicle() {  
9:      return new Car();  
10:    }  
11:  }  
PlaneFactory.java
1:  package ua.dp.allmycircuits.factorymethod;  
2:  /**  
3:   * Фабрика для самолетов  
4:   * @author winkiller  
5:   */  
6:  public class PlaneFactory implements VehicleFactory{  
7:    @Override  
8:    public Vehicle createVehicle() {  
9:      return new Plane();  
10:    }  
11:  }  
FactoryMethodClassic.java
1:  package ua.dp.allmycircuits.factorymethod;  
2:  import java.util.ArrayList;  
3:  import java.util.List;  
4:  /**  
5:   *  
6:   * @author winkiller  
7:   */  
8:  public class FactoryMethodClassic {  
9:    /**  
10:     * @param args the command line arguments  
11:     */  
12:    public static void main(String[] args) {  
13:      //инициируем фабрики. Можно в цикле инициировать. Боже, храни полиморфизм!  
14:      BoatFactory bf = new BoatFactory();  
15:      CarFactory cf = new CarFactory();  
16:      PlaneFactory pf = new PlaneFactory();  
17:      //создаем список и заполняем его потомками базового класса Vehicle  
18:      List<Vehicle> vehicles = new ArrayList<Vehicle>();  
19:      vehicles.add(bf.createVehicle());  
20:      vehicles.add(cf.createVehicle());  
21:      vehicles.add(pf.createVehicle());  
22:      //А теперь по очереди у каждого дергаем метод info()  
23:      for(Vehicle v: vehicles){  
24:        v.info();  
25:      }  
26:    }    
27:  }  

Классический вариант паттерна Factory Method использует идею полиморфной фабрики. Специально выделенный для создания объектов полиморфный интерфейс VehicleFactory объявляет интерфейс фабричного метода createVehicle(), а производные классы его реализуют.
Один из примеров использования в Java это LogManager.getLogManager(), Calendar.getInstance(), etc, которые используют фабричный метод чтобы создать инстанс соответствующего класса

Достоинства паттерна Factory Method

  • Создает объекты разных типов, позволяя системе оставаться независимой как от самого процесса создания, так и от типов создаваемых объектов.

Недостатки паттерна Factory Method

  • В случае классического варианта паттерна даже для порождения единственного объекта необходимо создавать соответствующую фабрику

2 комментария: