设计模式之工厂模式!深入解析简单工厂模式,工厂方法模式和抽象工厂模式

779 阅读16分钟

这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战

工厂模式

  • 创建型模式:
    • 对类的实例化过程进行抽象,能够将对象的创建和对象的使用分离开来
      • 为了使得软件的结构更加清晰,外界对于这些对象使用只需要知道共同的接口,而不在意具体实现的细节,这样使得整个系统更加符合单一职责的原则
      • 创建型模式隐藏了类的实例的创建细节,通过隐藏对象创建和组合过程从而使得整个系统相互独立的目的
    • 创建型模式在创建什么,由谁创建,何时创建更加灵活
    • 工厂模式是一个重要的创建型模式,主要功能就是实例化对象
  • 工厂模式: 负责将有共同接口的类实例化
    • 主要解决接口选择问题
    • 在不同的条件下需要创建不同的实例时使用
    • 工厂模式是一种创建型模式,提供了创建对象的最佳方式
    • 使用工厂模式创建对象不会对客户端暴露创建逻辑,并且使用一个共同的接口来指向新创建的对象
    • 工厂模式在子类中实现工厂接口,创建过程在子类中执行
  • 工厂模式的分类:
    • 简单工厂模式Simple Factory
    • 工厂方法模式Factory Method
    • 抽象工厂模式Abstract Factory
  • 工厂模式优点:
    • 可以使得代码结构清晰,有效地封装变化
    • 对调用者屏蔽具体的产品类
    • 降低代码的耦合度
  • 工厂模式的使用场景:
    • 在任何需要生成复杂对象的地方,都可以使用工厂方法模式.只有复杂的对象才适用于工厂方法模式.对于简单的只要通过new就可以完成创建的对象,无需使用工厂模式.如果简单对象使用工厂模式,需要引入一个工厂类,增加系统的复杂度
    • 工厂模式是一种典型的解耦模式,当类之间需要增加依赖关系时,可以使用工厂模式降低系统之间的耦合度
    • 工厂模式是依靠抽象架构的,将实例化的任务交给子类实现,扩展性好.当系统需要较好的扩展性时,可以使用工厂模式,不同的产品使用不同的工厂来实现组装

简单工厂模式

  • 简单工厂模式Simple Factory Pattern:
    • 定义一个类用于负责创建其余类的实例,根据自变量的不同返回不同类的实例,被创建的实例通常都有一个共同的父类
    • 简单工厂模式中用于创建实例的方法时静态static方法,因此又称作是静态工厂方法模式
  • 简单工厂模式的角色:
    • 工厂类Factory : 简单工厂模式核心类. 负责创建所有产品的内部逻辑,工厂类可以被外部调用,创建所需对象
    • 抽象产品类Product : 工厂类创建的所有对象的父类,封装产品的共有方法.提高系统的灵活性.使得工厂类只需要定义一个通用的工厂方法,因为所有创建的具体产品都是这个子类对象
    • 具体产品类ConcorrectProduct: 所有被创建的对象都是这个类的具体实例,需要实现抽象产品中声明的抽象方法 在这里插入图片描述
  • 简单工厂模式代码实现
  • 简单工厂模式优点:
    • 简单工厂模式提供了专门的类用于创建对象,实现了对责任的分割. 工厂类Factory中含有必要的判断逻辑,决定创建具体产品类ConcreteProduct的实例,客户端只需要消费产品
    • 客户端不需要知道需要创建的具体产品类ConcreteProduct的类名,只需要知道具体产品类ConcreteProduct对应的参数即可
    • 通过引入配置文件,可以在不修改客户端的情况下修改和增加新的产品类ConcreteProduct, 提高了系统的灵活性
  • 简单工厂模式缺点:
    • 工厂类Factory集中了所有产品的创建逻辑,如果发生异常,整个系统都会发生故障
    • 简单工厂模式中增加了系统中类的个数,增加了系统的复杂度和理解难度
    • 简单工厂模式中如果需要添加新的产品需要修改工厂逻辑,违背了开闭原则,不利于系统的扩展和维护
    • 简单工厂模式使用了静态方法,无法形成基于继承的等级结构
  • 简单工厂模式的使用场景:
    • 工厂类中负责创建的对象比较少时
    • 客户端只需要知道传入工厂类的参数,不关心创建对象的参数

简单工厂类的实现方式

直接传入判断参数key

  • Factory:
public class Factory {
	public static Product produce(String concreteProductType) {
		switch (concreteProductType) {
			case "A" :
				return new ConcreteProductA();
				break;
			case "B" :
				return new ConcreteProductB();
				break;
			default :
				throw new Exception("没有对应的产品类型");
				break;
		}
	}
}
  • 问题:
    • 如果新增产品类,需要在工厂类中新增case
    • 违背了开闭原则,这种方法是不建议使用的

利用反射

  • Factory:
public Class Factory {
	public static Product produce(String concreteProductClassPathName) throw Exception {
		try {
			Product product = (Product)Class.forName(concreteProductClassPathName).newInstance();
			return product;
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		throw new Exception("没有对应的产品");
	}
}
  • 问题:
    • 如果新增产品类,需要传入具体产品类的类路径名称
    • 可以通过配置文件进行优化,将具体产品类的类路径名称配置在properties文件中,通过加载配置文件将类路径名称传入工厂类的方法中
    • 这样新增产品类,只需要修改配置文件即可

反射和配置文件结合

  • product.properties:
A=com.oxford.factory.simple.ConcreteProductA
B= com.oxford.factory.simple.ConcreteProductB
  • PropertyReader: 增加一个配置文件读取类,将配置文件的信息读取到Map
public Class PropertyReader {
	public static Map<String, String> property = new HashMap<>();
	public Map<String, String> readProperty(String fileName) {
		Properties properties = new Properties();
		InputStream input = getClass.getResourceAsStream(fileName); 
		try {
			pro.load(input);
			Iterator<String> iterator = pro.StringPropertyNames().iterator();
			while (iterator.hasNext()) {
				String key = iterator.next();
				String value = properties.getProperty(key);
				map.put(key, value);
			}
			input.close();
		} catch (IOException e) {
			e.printStacTrace();
		}
		return map;
	}
}
  • Factory:
public Class Factory {
	public static Product produce(String concreteProductType) throws Exception {
		PropertyReader reader = new PropertyReder();
		Map<String, String> property = reader.readProperty("property.properties");
		try {
			Product product = (Product)Class.forName(property.get(concreteProductType)).newInstance();
			return product; 
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		throw new Exception("没有对应的产品");
	}
}
  • 问题:
    • 每次调用的方法时,都要解析配置文件,增加系统开销
    • 可以在文件读取类在程序启动时就加载,就可以不用在每次调用时解析配置文件了

简单工厂模式总结

  • 工厂类是整个简单工厂模式的关键:
    • 工厂类中包含必要的判断逻辑,根据给定的参数来决定创建哪一个具体的产品类
    • 通过使用工厂类,客户端只需要消费具体产品即可,而不用关注具体产品对象的创建过程
    • 通过使用简单工厂模式明确了各个类的职责,有利于整个软件体系结构的优化
  • 工厂类中集中了所有具体对象的创建逻辑,违背了高内聚的责任分配原则.这样工厂类中创建的类只能是事先考虑到的,如果需要添加新的类,则需要修改工厂类的逻辑,这违背了开闭原则
  • 当系统中的具体产品类不断增多时,就会出现要求工厂类根据不同的条件创建不同的实例的需求.这种对条件的判断和对具体产品类型的判断交错在一起,不利于对系统的扩展和维护.这样的问题可以通过使用工厂方法模式进行优化

工厂方法模式

  • 工厂方法模式Factory Method Pattern:
    • 定义一个创建对象的接口,通过实现这个接口的类来决定实例化具体的类
    • 工厂方法模式让具体的类的实例化延迟到子类中进行
  • 工厂方法模式的角色:
    • 工厂类Factory:
      • 工厂方法接口,通常返回一个抽象产品类型Product的实例对象
      • 这个类是工厂方法模式的核心,与客户端程序无关. 任何在模式中创建的具体产品都需要实现这个接口
    • 工厂实现类ConcreteFactory:
      • 工厂类接口实现,覆写工厂类Factory定义的工厂方法,返回具体产品类ConcreteProduct的抽象产品类型Product类型的实例
      • 工厂实现类ConcreteFactory中包含与客户端密切相关的逻辑,并且被客户端调用来创建具体的产品实例
    • 抽象产品类Product:
      • 工厂方法模式创建的具体产品类的父类,定义类具体产品中共有的方法
    • 具体产品类ConcreteProduct:
      • 具体产品实现类,实现了抽象产品类Product中的方法
      • 工厂模式创建的每一个对象都是具体产品类ConcreteProduct的一个实例 在这里插入图片描述
  • 工厂方法模式代码实现
  • 工厂方法模式优点:
    • 在工厂方法模式中,通过工厂方法来创建客户端需要的产品ConcreteProduct, 用户只需要关心需要具体产品ConcreteProduct对应的工厂实现ConcreteFactory. 不需要关心具体产品ConcreteProduct的创建细节和具体产品类ConcreteProduct的名称
    • 基于工厂类Factory和抽象产品类Product的多态性设计是工厂方法模式的关键. 这样工厂类Factory可以自主确定需要创建何种产品ConcreteProduct的对象,并且创建具体产品ConcreteProduct对象的具体实现封装在具体工厂ConcreteFactory的内部. 具体工厂类ConcreteFactory都具有同一父类接口Factory, 因此工厂方法模式又称为多态工厂模式
    • 工厂方法模式完全符合开闭原则,有利于系统的扩展和维护. 工厂方法模式在系统中添加新产品时,只需要添加一个具体工厂类ConcreteFactory和具体产品类ConcreteProduct即可
  • 工厂方法模式缺点:
    • 工厂模式在系统中添加新产品时,需要添加具体产品类ConcreteProduct和具体工厂类ConcreteFactory, 系统中类的个数成对增加,一定程度上增加了系统复杂度以及系统编译运行的开销
  • 工厂方法模式的使用场景:
    • 一个类不需要知道所需要的对象的类: 工厂方法模式中,客户端不知道具体产品类的类名,只知道具体的产品对象由哪一个具体工厂实现来创建. 这时,客户端需要知道创建具体产品的具体工厂实现类
    • 一个类通过子类来指定创建哪一个对象: 工厂方法模式中,工厂类中只需要一个创建产品的的接口,由子类来确定具体要创建的对象,通过利用多态和里氏代换原则,可以在程序运行时,通过子类对象覆盖父类对象,从而使得系统得以扩展
    • 通过将创建具体产品的任务交由工厂类的具体工厂实现来完成,客户端不需要关心具体产品类的创建, 需要的时候动态指定产品的具体工厂实现即可. 可以将具体工厂类的类名存储在配置文件或者数据库中
    • 工厂方法模式的使用场景示例:
      • 日志记录器: 日志可以记录到本地磁盘,系统事件,远程服务器等等,用户可以选择日志记录的位置
      • 数据库访问: 当用户不知道最后系统采用哪一类数据库时,以及数据库可能会发生变化时
      • 服务器框架设计: 设计一个连接服务器的框架时,可能会用到三个协议POP3, IMAP, HTTP时,可以将三个协议看作是具体产品类,使用工厂方法模式实现

工厂方法模式总结

  • 工厂方法模式是简单工厂模式的抽象和拓展,通过多态,工厂方法模式保持了简单工厂模式的优点,改善了简单工厂模式的缺点
  • 工厂方法模式中,核心的工厂类仅仅给出具体工厂实现必须实现的接口,不再负责具体产品的创建,具体产品的创建交由具体的工厂实现完成.这样使得系统可以在不修改核心的工厂类时进行具体产品实现的扩展
  • 优点:
    • 客户端想要创建对象,只需要知道具体工厂实现即可
    • 系统的扩展性高,如果新增产品,只需要一个具体工厂实现类和具体产品类即可,符合开闭原则
    • 对客户端隐藏了具体实现,客户端只需要关心具体的工厂实现即可
  • 缺点:
    • 每次增加一个产品,都需要增加一个具体工厂实现类和具体产品类,这样使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,也增加了系统具体类的依赖,同时类的增加也增加了编译和运行时的系统开销

抽象工厂模式

  • 抽象工厂模式Abstract Factory Pattern:
    • 提供接口或者抽象类用于创建一组相关或者相互依赖的具体产品对象,不需要指定具体的类
  • 抽象工厂模式的基本思想:
    • 工厂方法模式通过引入工厂等级结构,解决了简单工厂模式中工厂类的职责过大的问题.但是由于工厂方法模式中每个工厂只生产一类产品,这样可能会导致存在大量的工厂类的问题,这样会增加系统的开销
    • 可以将一些相关的产品组成一个产品族,由同一个工厂来统一生产
      • 产品族: 位于不同产品等级结构中功能相关联的产品组成的家族
    • 抽象工厂模式与工厂方法模式区别:
      • 抽象工厂模式:
        • 抽象工厂模式是针对多个产品的等级结构
        • 抽象工厂模式的具体产品实现或者继承于不同的接口或者抽象类
      • 工厂方法模式:
        • 工厂方法模式是针对一个产品的等级结构
        • 工厂方法模式的具体产品实现或者继承于同一个接口或者抽象类
  • 抽象工厂模式的角色:
    • 抽象工厂类AbstractFactory: 抽象工厂模式的核心,与应用的业务逻辑无关. 通常使用接口或者抽象类实现,所有具体工厂类ConcreteFactory必须实现接口或者抽象类
    • 具体工厂类ConcreteFactory: 实现工厂定义的方法,包含创建具体产品实例的业务逻辑
    • 抽象产品AbstractProduct: 定义一类产品对象的接口或者抽象类,这个类是工厂方法模式创建的对象的父类
    • 具体产品ConcreteProduct: 实现业务逻辑的具体的产品,抽象工厂中创建的每一个产品对象都是一个具体产品的实例 在这里插入图片描述
  • 抽象工厂模式代码实现
  • 抽象工厂模式优点:
    • 抽象工厂模式分隔了具体类的生成,客户端不需要知道具体创建的类
    • 当一个产品族中的对象设计成一起工作时,能够保证客户端只使用同一个产品族的对象
  • 抽象工厂模式缺点:
    • 如果添加新的产品对象时,难以对产品等级结构进行扩展
  • 抽象工厂模式的使用场景:
    • 一个系统中不依赖于产品类的具体实例的创建,组合以及表达的细节
    • 系统中有多个产品族,并且每次只使用其中一种产品族
    • 同一个产品族的产品会在一起使用
    • 系统中提供一个产品类的库,客户端不依赖具体产品的实现,所有产品以同样的接口出现
    • 系统结构稳定,不会频繁增加产品族
  • 抽象工厂模式问题: 开闭原则的倾斜性
    • 抽象工厂模式中开闭原则的倾斜性是指在抽象工厂模式中,增加新的产品方便,但是增加新的产品族很麻烦
    • 开闭原则要求系统对修改关闭,对扩展开放.对于多个产品族和多个产品等级结构的系统的功能扩展增强包括:
      • 增加产品: 对于增加新的产品,只需要增加一个对应的具体工厂即可,不需要修改已有的代码
      • 增加产品族: 对于增加新的产品族的产品等级结构,需要修改所有的工厂角色,包括抽象工厂类,在所有的工厂类中都需要增加生产新产品族产品的方法,这是违背了开闭原则的
    • 因为抽象工厂模式存在开闭原则的倾斜性,因此要求在系统设计之初就要考虑整个系统的所有产品族,不会在设计完成之后再增加新的产品族,也不会删除已有的产品族.否则会导致系统有大量的修改,难以维护

抽象工厂模式总结

  • 抽象工厂模式是工厂方法模式的进一步拓展,提供了更为强大的工厂类用于系统的扩展
  • 抽象工厂模式分隔了具体类的生成,客户端无需了解产品的创建过程,这样使得很容易切换具体工厂.因为所有的具体工厂都实现了抽象工厂中定义的公共接口,因此只需要改变具体工厂的实例,就可以改变整个系统的行为
  • 当一个产品族中的多个产品对象一起工作时,可以保证客户端始终只使用同一个产品族中的对象
  • 增加新的产品很方便,无需修改已有的系统,符合开闭原则
  • 但是增加系统新的产品族的产品等级结构很麻烦,需要对原有的系统大量修改,甚至需要修改抽象层代码,违背了开闭原则