设计模式之工厂方法模式

87 阅读3分钟

案例:女娲造人,人分为3种,黄种人、白种人和黑种人

类图:

代码:

先定义“人类”接口规范及其3个实现类:

/**
 * @Description: 定义一个"人类"的规范
 */
public interface Human {
  
  // 人会笑
  void laugh();
  
  // 人会哭
  void cry();
  
  // 人会说话
  void talk();
  
}

/**
 * @Description: 黄种人
 */
public class YellowHuman implements Human {

  @Override
  public void laugh() {
    System.out.println("黄种人会笑...");
  }

  @Override
  public void cry() {
    System.out.println("黄种人会哭...");
  }

  @Override
  public void talk() {
    System.out.println("黄种人说话...");
  }

}

/**
 * @Description: 白种人
 */
public class WhiteHuman implements Human {

  @Override
  public void laugh() {
    System.out.println("白种人会笑...");
  }

  @Override
  public void cry() {
    System.out.println("白种人会哭...");
  }

  @Override
  public void talk() {
    System.out.println("白种人说话...");
  }

}

/**
 * @Description: 黑种人
 */
public class BlackHuman implements Human {

  @Override
  public void laugh() {
    System.out.println("黑种人会笑...");
  }

  @Override
  public void cry() {
    System.out.println("黑种人会哭...");
  }

  @Override
  public void talk() {
    System.out.println("黑种人说话...");
  }

}

造人的工厂以及客户端女娲:

**
 * @Description: 造人的工厂
 */
public class HumanFactory {

  public static Human createHuman(Class c) {
    Human human = null;
    
    try {
      // 使用反射,创建一个Human接口的实现类实例
      human = (Human) Class.forName(c.getName()).newInstance();
    } catch (Exception e) {
      e.printStackTrace();
      System.out.println("请指定正确的人种,支持YellowHuman、WhiteHuman和BlackHuman"); 
    }
    return human;
  }
  
}

public class NvWa {

  public static void main(String[] args) {
    
    System.out.println("第一批人:白种人-----------------");
    Human whiteHuman = HumanFactory.createHuman(WhiteHuman.class);
    whiteHuman.cry();
    whiteHuman.laugh();
    whiteHuman.talk();
    
    System.out.println("第一批人:黑种人-----------------");
    Human blackHuman = HumanFactory.createHuman(BlackHuman.class);
    blackHuman.cry();
    blackHuman.laugh();
    blackHuman.talk(); 
    
    System.out.println("第一批人:黄种人-----------------");
    Human yellowHuman = HumanFactory.createHuman(YellowHuman.class);
    yellowHuman.cry();
    yellowHuman.laugh();
    yellowHuman.talk();
  }
  
}

使用以上的代码创建Human,客户端的调用会很麻烦,女娲觉得很累,于是她想随便甩一些泥点儿,就可以造出一批人,肤色随便。

修改类图:

修改代码:

import java.util.List;
import java.util.Random;

/**
 * @Description: 造人的工厂
 */
public class HumanFactory {

  /**
   * 创造指定肤色的人
   */
  public static Human createHuman(Class c) {
    Human human = null;
    
    try {
      // 使用反射,创建一个Human接口的实现类实例
      human = (Human) Class.forName(c.getName()).newInstance();
    } catch (Exception e) {
      e.printStackTrace();
      System.out.println("请指定正确的人种,支持YellowHuman、WhiteHuman和BlackHuman"); 
    }
    return human;
  }
  
  /**
   * 增加方法:创造随机肤色的人
   */
  public static Human createHuman() {
   
    Human human = null; 

    // 首先是获得有Human接口有多少个实现类,即多少个人种
    List<Class> concreteHumanList = ClassUtil.getAllClassByInterface(Human.class);
    // 随机造人
    Random random = new Random();
    int rand = random.nextInt(concreteHumanList.size());
    human = createHuman(concreteHumanList.get(rand));
    return human;
  }
  
}

public class NvWa {

  public static void main(String[] args) {
    
    for(int i = 0; i < 1000000; i++) {
      System.out.println("产生随机肤色人种...");
      Human human = HumanFactory.createHuman();
      human.cry();
      human.laugh();
      human.talk();
    }
  }
  
}

以上代码改动增加了一个核心类:ClassUtil,这个工具的作用:获取一个接口的所有实现类,代码如下:

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

public class ClassUtil {

  // 给一个接口,返回这个接口的所有实现类
  public static List<Class> getAllClassByInterface(Class c) {
    
    // 返回结果
    List<Class> returnClassList = new ArrayList<Class>();
    
    // 如果不是一个接口,则不做处理
    if(c.isInterface()) {
      // 获得类的包名
      String packageName = c.getPackage().getName();
      // 获得当前包以下以及子包下的所有类
      try {
        List<Class> allClass = getClasses(packageName);
        // 判断是否是同一个接口
        for(int i = 0; i < allClass.size(); i++) {
          if(c.isAssignableFrom(allClass.get(i))) {
            // 本身加不进去
            if(!c.equals(allClass.get(i))) {
              returnClassList.add(allClass.get(i));
            }
          }
        }
      } catch (ClassNotFoundException e) {
        e.printStackTrace();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
    
    return returnClassList;
    
  }
  
  /*
   * 从一个包中查找出所有的类,在jar包中不能查找
   */
  private static List<Class> getClasses(String packageName) throws IOException, ClassNotFoundException {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    String path = packageName.replace('.', '/');
    Enumeration<URL> resources = classLoader.getResources(path);
    List<File> dirs = new ArrayList<File>();
    while(resources.hasMoreElements()) {
      URL resource = resources.nextElement();
      dirs.add(new File(resource.getFile()));
    }
    List<Class> classes = new ArrayList<Class>();
    for(File directory : dirs) {
      classes.addAll(findClasses(directory, packageName));
    }
    return classes;
  }
  
  private static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException {
    List<Class> classes = new ArrayList<Class>();
    if(!directory.exists()) {
      return classes;
    }
    File[] files = directory.listFiles();
    for(File file : files) {
      if(file.isDirectory()) {
        assert !file.getName().contains(".");
        classes.addAll(findClasses(file, packageName + "." + file.getName()));
      }else if(file.getName().endsWith(".class")) {
        classes.add(Class.forName(packageName + "." + file.getName().substring(0, file.getName().length() - 6)));
      }
    }
    return classes;
  }
}

完整的类图如下:

增加了createHuman()方法后,HumanFactory这个工长类的扩展性更好了,如果需要创建更多肤色的人种,只需要添加Human接口的实现类即可,客户端的调用无需任何修改。

工厂方法模式的一个重要的应用是延迟初始化(Lazy Initialization),即一个对象初始化之后不释放,等到下次调用的时候直接从内存中使用,实现代码如下:

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

public class LazyHumanFactory {
  
  // 定义一个map,初始化过的Human对象都放在这里
  private static Map<String, Human> humans = new HashMap<>();
  
  /**
   * 创造指定肤色的人
   */
  public static Human createHuman(Class c) {
    Human human = null;
    
    try {
      if(humans.containsKey(c.getSimpleName())) {
        human = humans.get(c.getSimpleName());
      } else {
        human = (Human) Class.forName(c.getName()).newInstance();
        // 放到map中
        humans.put(c.getSimpleName(), human);
      }
    } catch (Exception e) {
      e.printStackTrace();
      System.out.println("请指定正确的人种,支持YellowHuman、WhiteHuman和BlackHuman"); 
    }
    return human;
  }
}

延迟初始化这个功能比较实用,当初始化一个类的实例比较耗资源或者为了初始化一个类的实例要准备很多条件的时候,就可以通过这种方式实现延迟初始化。

以上就是工厂方法模式的介绍,工厂方法模式也叫做简单工厂模式,因为其实现的功能比较简单,在本文案例中,只能实现创造不同肤色的人种,如果增加性别粒度,创造各种肤色的男人或者女人,就要使用抽象工厂模式了。

本文原书: 《您的设计模式》作者:CBF4LIFE