четверг, октября 06, 2016

If-less programming (java) Part №1

Господа, всем известно что нагромождение конструкций if-else есть признак плохого тона. Попробуем обойтись без их использования в некоторых ситуациях.

If-else в фабричном методе.

Для примера есть стандартная реализация паттерна "Фабричный метод", создающая объекты в зависимости от некоторого ключа.


package com.bssys.blog.example;

public class App {
 private Factory factory;

 public App() {
  factory = new Factory();
 }

 public void onMessage(String message) {
  Product product = factory.createProduct(message);
  product.action();
 }

 public static void main(String[] args) {
  App app = new App();
  app.onMessage("A");
  app.onMessage("B");
 }
}

interface Product {
 public void action();
}

class ConcreteProductA implements Product {

 public void action() {
  System.out.println("ConcreteProductA");
 }

}

class ConcreteProductB implements Product {

 public void action() {
  System.out.println("ConcreteProductB");
 }

}

class Factory {
 Product createProduct(String key) {
  Product product = null;
  if ("A".equals(key)) {
   product = new ConcreteProductA();
  } else if ("B".equals(key)) {
   product = new ConcreteProductB();
  }
  return product;
 }
}

Тут присутcnвует громоздкая конструкция if-else. Как избавиться от нее?

Использовать HashMap.

Фактически введем динамический if. Таким образом класс Factory будет выглядеть следующим образом:
class Factory {
 
 private Map<String, Product> factoryMap;
 
 public Factory() {
  factoryMap = new HashMap<String, Product>();
  factoryMap.put("A", new ConcreteProductA());
  factoryMap.put("B", new ConcreteProductB());
 }
 
 Product createProduct(String key) {
  Product product = factoryMap.get(key);
  return product;
 }
}
Тут мы немного сломали ожидаемое поведение, так как фабричный метод будет возвращать одни и теже объекты. Иногда это даже хорошо. Но тем не менее чтобы ожидаемое поведение было одинаковым введем интерфейс фабрики которая будет создавать конкретные продукты. И отрефакторим код:
package com.bssys.blog.example;

import java.util.HashMap;
import java.util.Map;

public class App {
 private CommonFactory factory;

 public App() {
  factory = new CommonFactory();
 }

 public void onMessage(String message) {
  Product product = factory.createProduct(message);
  product.action();
 }

 public static void main(String[] args) {
  App app = new App();
  app.onMessage("A");
  app.onMessage("B");
 }
}

interface Product {
 public void action();
}

class ConcreteProductA implements Product {

 public void action() {
  System.out.println("ConcreteProductA");
 }

}

class ConcreteProductB implements Product {

 public void action() {
  System.out.println("ConcreteProductB");
 }

}

interface Factory{
 Product createProduct();
}

class ConcreteProductAFactory implements Factory{

 public Product createProduct() {
  return new ConcreteProductA();
 }
}

class ConcreteProductBFactory implements Factory{

 public Product createProduct() {
  return new ConcreteProductB();
 }
}

class CommonFactory {
 
 private Map<String, Factory> factoryMap;
 
 public CommonFactory() {
  factoryMap = new HashMap<String, Factory>();
  factoryMap.put("A", new ConcreteProductAFactory());
  factoryMap.put("B", new ConcreteProductBFactory());
 }
 
 Product createProduct(String key) {
  Factory factory = factoryMap.get(key);
  return factory.createProduct();
 }
}
Уже лучше. Мы выделили интерфейс фабрики. Теперь у нас есть класс CommonFactory, который по сути является реестром всех объектов. Теперь если подключить мощь Spring мы могли бы вынести наш HashMap в конфиг, и управлять поведением не перекомпилируя наш код. Как вы видите мы нагенерили много кода. И хотя это правильный подход в java, есть немного другой способ избавиться от if.

Использовать enum с абстрактным методом.

enum Type {
 A {
  @Override
  public Product createProduct() {
   return new ConcreteProductA();
  }
 },
 B {
  @Override
  public Product createProduct() {
   return new ConcreteProductB();
  }
 };

 public abstract Product createProduct();
}

В таком случае наш фабричный метод переродится в следующее:
class Factory {
 
 public Product createProduct(String key) {
  Type valueOf = Type.valueOf(key);
  return valueOf.createProduct();
 }
}
Тут конечно тоже есть неявный динамический if при выполнении операции valueOf, но все равно выглядит элегантно.

Есть еще один способ обойтись без if используя reflection, но я не приветствую такой подход. Если хотите динамичности то берите наверное другой язык, а не java.

Комментариев нет: