1. 工厂模式
1.1 概述
工厂模式(Factory Pattern)是一种创建型设计模式,提供了一种将对象创建的过程与对象的使用分离的机制。它通过将实例化对象的逻辑封装到一个工厂类中,从而减少客户端和具体类的耦合。这使得代码更加灵活和易于维护,尤其是当我们需要创建不同种类的对象时。
在 Java
中,万物皆对象,这些对象都需要创建,如果创建的时候直接 new
该对象,就会对该对象耦合严重,假如我们要更换对象,所有 new
对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则。如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的。所以说,工厂模式最大的优点就是:解耦。
工厂模式主要分为以下几种类型:
- 简单工厂模式(Simple Factory)
- 工厂方法模式(Factory Method)
- 抽象工厂模式(Abstract Factory)
1.2 场景:操作不同类型的动物
我们假设有一个场景,程序需要对不同种类的动物进行操作,动物可以发出不同的声音。
1.2.1 不用工厂模式
1.2.1.1 分析
在这个例子中,客户端代码直接依赖于具体的动物类 ( Dog
, Cat
, Bird
),这意味着如果我们添加新的动物类型或者更改现有的创建逻辑,必须修改客户端代码。这种实现方式耦合度较高,不符合开闭原则。
类图:
// 动物接口
interface Animal {
void makeSound();
}
// 具体动物类:狗
class Dog implements Animal {
@Override
public void makeSound() {
System.out.println("Woof");
}
}
// 具体动物类:猫
class Cat implements Animal {
@Override
public void makeSound() {
System.out.println("Meow");
}
}
// 具体动物类:鸟
class Bird implements Animal {
@Override
public void makeSound() {
System.out.println("Tweet");
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
// 客户端直接实例化
Animal animal1 = new Dog();
// 输出:Woof
animal1.makeSound();
Animal animal2 = new Cat();
// 输出:Meow
animal2.makeSound();
Animal animal3 = new Bird();
// 输出:Tweet
animal3.makeSound();
}
}
1.2.1.1 优缺点
- 优点:
- 简单直接:客户端直接实例化对象,代码较为简单,适用于对象数量少、变动少的情况。
- 缺点:
- 高耦合性:客户端依赖具体类,任何对象的变化(如新增类、修改类)都需要修改客户端代码,违反开闭原则。
- 扩展性差:当新增或修改对象时,需要改动已有代码,且对不同条件的判断逻辑可能会扩散到多个地方。
1.2.2 简单工厂模式
简单工厂模式使用一个单独的类根据参数创建不同类型的对象。它是一种具体的工厂,不属于设计模式中的 23
种经典设计模式,但经常被用作学习的起点。
1.2.2.1 结构
简单工厂包含如下角色:
- 抽象产品 :定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品 :实现或者继承抽象产品的子类
- 具体工厂 :提供了创建产品的方法,调用者通过该方法来获取产品。
1.2.2.2 分析
- 在简单工厂模式中,
AnimalFactory
类封装了对象的创建逻辑,客户端只需要传递参数来指定创建的动物类型,而无需知道具体的类。 - 虽然解决了直接依赖具体类的问题,但如果我们想添加新的动物类型,仍需要修改
AnimalFactory
类的逻辑,这违反了开闭原则。
类图:
// 简单工厂类
class AnimalFactory {
public Animal createAnimal(String type) {
if ("Dog".equalsIgnoreCase(type)) {
return new Dog();
} else if ("Cat".equalsIgnoreCase(type)) {
return new Cat();
} else if ("Bird".equalsIgnoreCase(type)) {
return new Bird();
} else {
throw new IllegalArgumentException("Unknown animal type");
}
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
AnimalFactory factory = new AnimalFactory();
Animal animal1 = factory.createAnimal("Dog");
// 输出:Woof
animal1.makeSound();
Animal animal2 = factory.createAnimal("Cat");
// 输出:Meow
animal2.makeSound();
Animal animal3 = factory.createAnimal("Bird");
// 输出:Tweet
animal3.makeSound();
}
}
1.2.1.3 优缺点
- 优点:
- 创建逻辑集中:将对象的创建集中在一个工厂类中,客户端只需知道要创建的对象类型(传递字符串或参数),降低了对具体类的依赖。
- 简化客户端代码:客户端不再负责具体对象的创建,只需调用工厂方法。
- 缺点:
-
违反开闭原则:如果需要添加新的类型,需要修改工厂类的创建逻辑(
if-else
或switch-case
语句)。 - 复杂性增加:随着对象类型增多,工厂类的创建逻辑可能变得复杂,不利于维护。
1.2.1.4 扩展:静态工厂
在开发中也有一部分人将工厂类中的创建对象的功能定义为静态的,这个就是静态工厂模式,它也不是 23
种设计模式中的。
类图:
// 静态工厂类
class AnimalFactory {
public static Animal createAnimal(String type) {
if ("Dog".equalsIgnoreCase(type)) {
return new Dog();
} else if ("Cat".equalsIgnoreCase(type)) {
return new Cat();
} else if ("Bird".equalsIgnoreCase(type)) {
return new Bird();
} else {
throw new IllegalArgumentException("Unknown animal type");
}
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Animal animal1 = AnimalFactory.createAnimal("Dog");
// 输出:Woof
animal1.makeSound();
Animal animal2 = AnimalFactory.createAnimal("Cat");
// 输出:Meow
animal2.makeSound();
Animal animal3 = AnimalFactory.createAnimal("Bird");
// 输出:Tweet
animal3.makeSound();
}
}
1.2.3 工厂方法模式
工厂方法模式定义了一个创建对象的接口,但让子类决定实例化哪个类。工厂方法模式将对象的创建推迟到子类。
1.2.3.1 结构
工厂方法模式的主要角色:
- 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。抽象工厂既可以是接口也可以是抽象类,具体使用哪种形式取决于具体的设计需求和场景。
- 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
1.2.3.2 分析
- 在工厂方法模式中,每个具体的动物都有一个对应的工厂类,工厂类负责创建对应的动物实例。
- 这种模式遵循了开闭原则,添加新的动物类型只需新建一个工厂类和对应的动物类,而无需修改现有代码。
类图:
// 抽象工厂类
abstract class AnimalFactory {
public abstract Animal createAnimal();
}
// 具体工厂类:狗工厂类
class DogFactory extends AnimalFactory{
@Override
public Animal createAnimal() {
return new Dog();
}
}
// 具体工厂类:猫工厂类
class CatFactory extends AnimalFactory{
@Override
public Animal createAnimal() {
return new Cat();
}
}
// 具体工厂类:鸟工厂类
class BirdFactory extends AnimalFactory{
@Override
public Animal createAnimal() {
return new Bird();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
AnimalFactory factory1 = new DogFactory();
Animal animal1 = factory1.createAnimal();
// 输出:Woof
animal1.makeSound();
AnimalFactory factory2 = new CatFactory();
Animal animal2 = factory2.createAnimal();
// 输出:Meow
animal2.makeSound();
AnimalFactory factory3 = new BirdFactory();
Animal animal3 = factory3.createAnimal();
// 输出:Tweet
animal3.makeSound();
}
}
1.2.3.3 优缺点
- 优点:
- 符合开闭原则:工厂方法模式通过新增具体工厂类来扩展产品类型,而不修改已有代码,符合开闭原则。
- 解耦合:客户端不再依赖于具体类,而是依赖于抽象工厂接口,提升了代码的可扩展性和灵活性。
- 职责分离:每个工厂负责创建一种产品,工厂类和产品类的职责更加明确,便于维护。
- 缺点:
- 增加类的数量:每增加一种产品类型,都需要新建对应的工厂类,这会使类的数量增多,增加代码的复杂性。
- 适用场景有限:适合有多个类型的产品且产品类型较稳定的场景。如果产品类型非常多,工厂方法模式可能显得冗余。
1.2.4 抽象工厂模式
抽象工厂模式提供了一个接口,用于创建一系列相关或相互依赖的对象,而无需指定它们的具体类。它通常用于需要创建多个相关产品的场景。
1.2.4.1 结构
抽象工厂模式的主要角色如下:
- 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。抽象工厂既可以是接口也可以是抽象类,具体使用哪种形式取决于具体的设计需求和场景。
- 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它 同具体工厂之间是多对一的关系。
1.2.4.2 分析
- 在抽象工厂模式中,
AnimalFactory
提供了创建动物和食物的接口。不同的具体工厂(DogFactory
、CatFactory
、BirdFactory
)负责创建不同的动物及其食物。 - 这种模式适合在需要创建相关产品族时使用,比如同一类的动物和它们的食物。在不修改已有工厂的情况下,可以扩展新的动物和食物种类。
类图:
// 食物接口
interface Food {
void eat();
}
// 具体食物类:狗粮
class DogFood implements Food {
@Override
public void eat() {
System.out.println("Dog is eating dog food.");
}
}
// 具体食物类:猫粮
class CatFood implements Food {
@Override
public void eat() {
System.out.println("Cat is eating cat food.");
}
}
// 具体食物类:鸟粮
class BirdFood implements Food {
@Override
public void eat() {
System.out.println("Bird is eating bird seeds.");
}
}
// 抽象工厂类
interface AnimalFactory {
Animal createAnimal();
Food createFood();
}
// 具体工厂类:狗工厂类
class DogFactory implements AnimalFactory {
@Override
public Animal createAnimal() {
return new Dog();
}
@Override
public Food createFood() {
return new DogFood();
}
}
// 具体工厂类:猫工厂类
class CatFactory implements AnimalFactory {
@Override
public Animal createAnimal() {
return new Cat();
}
@Override
public Food createFood() {
return new CatFood();
}
}
// 具体工厂类:鸟工厂类
class BirdFactory implements AnimalFactory {
@Override
public Animal createAnimal() {
return new Bird();
}
@Override
public Food createFood() {
return new BirdFood();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
AnimalFactory factory1 = new DogFactory();
Animal animal1 = factory1.createAnimal();
Food food1 = factory1.createFood();
// 输出:Woof
animal1.makeSound();
// 输出:Dog is eating dog food.
food1.eat();
AnimalFactory factory2 = new CatFactory();
Animal animal2 = factory2.createAnimal();
Food food2 = factory2.createFood();
// 输出:Meow
animal2.makeSound();
// 输出:Cat is eating cat food.
food2.eat();
AnimalFactory factory3 = new BirdFactory();
Animal animal3 = factory3.createAnimal();
Food food3 = factory3.createFood();
// 输出:Tweet
animal3.makeSound();
// 输出:Bird is eating bird seeds.
food3.eat();
}
}
1.2.4.3 优缺点
- 优点:
- 产品族管理:能够创建相关联的一系列产品(即产品族),适合创建一组相互依赖的对象,如某一类产品和它的配件。
- 解耦产品族与客户端:客户端不关心产品的具体实现,而是通过抽象工厂统一获取产品,降低了代码的耦合度。
- 符合开闭原则:可以通过扩展工厂来增加新的产品族,而不修改已有代码,符合开闭原则。
- 缺点:
- 复杂度高:抽象工厂模式的结构复杂,增加了类和接口的数量,导致系统变得臃肿,尤其是在不需要创建产品族的情况下。
- 扩展产品等级结构困难:虽然易于扩展产品族(增加一组新产品),但如果需要为已有的产品族增加新的产品等级结构(如在已有的产品类中再增加一个功能),需要修改所有的工厂接口和具体工厂类。
1.2.5 抽象工厂模式和工厂方法模式的区别与联系
抽象工厂模式 是 工厂方法模式 的扩展。抽象工厂模式解决了 多个相关产品 一起创建的需求,而工厂方法模式只处理单一产品的创建
1.2.5.1 联系
工厂方法模式:主要用于创建 单个产品。每个具体的工厂类负责创建一种具体的产品,工厂方法模式的重点是为某个类型的产品提供创建方法,客户端通过调用工厂接口来创建产品对象。
抽象工厂模式:不仅仅创建单个产品,而是用于创建一系列相关的产品族。抽象工厂为不同的产品族提供接口,允许客户端使用不同的具体工厂来创建整个产品族中的所有产品,而不需要修改客户端代码。
1.2.5.2 不同点
-
产品的数量:
- 工厂方法模式:一个工厂类只创建一个产品。
- 抽象工厂模式:一个工厂类可以创建多个相关的产品(即产品族)。
-
复杂度:
- 工厂方法模式相对较简单,关注于单一产品的创建。
- 抽象工厂模式更加复杂,适用于有多个产品需要同时创建,且这些产品通常是有内在关联的情况。
1.3 总结
- 不用工厂模式:简单但耦合度高,扩展性差。
- 简单工厂模式:将对象的创建逻辑集中管理,但扩展性差,违反开闭原则。
- 工厂方法模式:符合开闭原则,增加扩展性,适合单个产品的创建,但类数量增多。
- 抽象工厂模式:适合创建一组相关产品,能管理产品族,扩展性强,但实现复杂,增加了系统的难度。
1.4 通过 简单工厂模式+配置文件 解除耦合
通过简单工厂模式和配置文件的结合,可以有效解除客户端和具体产品类之间的耦合。这种方式通过在配置文件中配置具体产品类的名称或类型,简单工厂读取配置文件动态创建对象,从而让客户端不需要直接依赖具体类。这样如果以后要替换或新增产品,客户端代码无需修改,只需要更新配置文件和相应的工厂逻辑。
1.4.1 步骤分析
- 配置文件:将需要创建的类(具体产品)的信息存储在配置文件中,工厂可以根据该配置文件动态加载类。
- 简单工厂模式:根据读取到的配置文件的内容,工厂通过反射等机制动态创建具体的产品对象。
- 解除耦合:客户端不再直接依赖具体的产品类,而是依赖工厂类。通过工厂类读取配置文件,灵活地创建不同的产品。
1.4.2 例子
类图:
配置文件 (config.properties
):配置文件中指定了产品类的全限定类名,便于工厂动态读取和创建实例。
Dog=patterns.factory.Implementation.after.configfactory.Dog
Cat=patterns.factory.Implementation.after.configfactory.Cat
Bird=patterns.factory.Implementation.after.configfactory.Bird
// 简单工厂类+配置文件
class AnimalFactory {
// 加载配置文件,获取配置文件中配置的全类名,并创建该类的对象进行存储
private static HashMap<String, Animal> map = new HashMap<>();
// 加载配置文件, 只需要加载一次
static {
// 根据配置文件创建产品对象
Properties properties = new Properties();
InputStream in = AnimalFactory.class.getClassLoader().getResourceAsStream("config.properties");
try {
properties.load(in);
// 从配置文件对象中获取全类名并创建对象
Set<Object> keys = properties.keySet();
for (Object key : keys) {
String className = properties.getProperty((String) key);
// 通过反射技术创建对象
Class<?> clazz = Class.forName(className);
Animal animal = (Animal) clazz.getDeclaredConstructor().newInstance();
// 将名称和对象存储到容器中
map.put(((String) key).toLowerCase(), animal);
}
if (in != null) {
in.close();
}
} catch (IOException | ReflectiveOperationException e) {
e.printStackTrace();
}
}
public static Animal createAnimal(String type) {
return map.get(type.toLowerCase());
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
// 客户端通过工厂获取具体产品,而不关心具体产品的实现
Animal animal1 = AnimalFactory.createAnimal("Dog");
// 输出:Woof
animal1.makeSound();
Animal animal2 = AnimalFactory.createAnimal("Cat");
// 输出:Meow
animal2.makeSound();
Animal animal3 = AnimalFactory.createAnimal("Bird");
// 输出:Tweet
animal3.makeSound();
}
}
1.4.3 关键点
配置文件:配置文件
config.properties
存储了具体类的类名信息,这样可以方便更改具体的实现。比如,修改配置文件中的Dog
为Cat
,就会创建Cat
类的对象,而不需要修改客户端代码。反射机制:工厂通过反射动态加载配置文件中的类名,并创建对应的实例。
解耦合:客户端不再依赖于具体的
Dog
或Cat
类,而是依赖于工厂,工厂通过配置文件灵活创建实例。即便以后新增其他Animal
实现类,客户端也无需修改,只需调整配置文件。
1.4.4 优缺点
- 优点:
- 低耦合:客户端不直接依赖具体产品,具体产品类可以灵活替换。
- 扩展性强:通过修改配置文件即可动态替换不同的产品类,而不影响代码。
- 维护性好:新增产品时,只需新增实现类和配置,而不需要修改客户端代码。
- 缺点:
- 反射开销:使用反射机制会带来一定的性能开销,且在编译期无法检测类的有效性,容易在运行时出现错误。
- 配置管理:需要额外的配置文件管理。
1.5 JDK源码解析-Collection.iterator方法
Collection.iterator()
方法的设计实际上可以看作是工厂模式的一个应用。虽然工厂模式在经典的形式中通常用于创建独立的对象,但在 Java 的集合框架中,iterator()
方法提供了类似工厂模式的功能:它通过接口返回一个 Iterator
对象,而不用暴露 Iterator
的具体实现,符合工厂模式的核心思想,即将对象的创建和使用分离。
类图:
1.5.1 Iterator 和 Collection
Iterator
是 Java 集合框架中的一个接口,允许客户端在不知道集合内部实现的情况下进行遍历。Collection
是所有集合类的顶层接口,如 List
、Set
、Queue
等都实现了这个接口。而 iterator()
方法则是 Collection
接口中的一个方法,它返回该集合元素的迭代器(Iterator
),用于遍历集合。
1.5.2 如何体现工厂模式
工厂模式的核心思想是:
- 提供一个接口,用于创建对象。
- 隐藏具体实现,让客户端只关心接口,不关心具体实现。
iterator()
方法符合这种思路,它提供一个接口 Iterator
来遍历集合,而客户端并不需要关心具体集合类是如何实现 Iterator
的。
-
Collection 是集合的顶层接口,定义了
iterator()
方法。 -
Iterator 是迭代器的接口,定义了迭代器需要的方法,如
hasNext()
、next()
、remove()
。 -
List 和 Set 分别实现了
Collection
接口,提供了具体的iterator()
实现。它们返回的是各自特定的迭代器。
1.5.4 工厂模式的体现
-
Collection
接口中的iterator()
方法就像是一个工厂方法,返回Iterator
对象。 - 每个具体的集合类(如
List
和Set
)都提供了自己的iterator()
实现。这相当于工厂方法中的不同工厂类,返回不同的产品对象(不同的Iterator
实现)。 - 客户端调用
iterator()
后,得到的是Iterator
接口,而不是具体的Iterator
实现类。这种方式屏蔽了集合内部的实现细节,客户端不需要关心集合的具体类型,只需要按照Iterator
接口的规范来操作集合。
1.5.4.1 Collection 接口(简化版)
public interface Collection<E> {
Iterator<E> iterator();
}
1.5.4.2 List 和 Set 类
public class Client {
public static void main(String[] args) {
// 使用ArrayList的迭代器
ArrayList<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
Iterator<String> listIterator = list.iterator();
while (listIterator.hasNext()) {
System.out.println(listIterator.next());
}
// 使用HashSet的迭代器
HashSet<String> set = new HashSet<>();
set.add("X");
set.add("Y");
set.add("Z");
Iterator<String> setIterator = set.iterator();
while (setIterator.hasNext()) {
System.out.println(setIterator.next());
}
}
}
1.5.5 工厂模式的优缺点在 Collection.iterator()
中的体现
- 优点:
-
解耦:客户端无需知道集合的具体实现,只需要通过
Iterator
接口来进行遍历。无论是List
、Set
还是其他集合类型,都提供统一的遍历方式。 -
扩展性强:如果添加新的集合类型,只需要提供自己的
iterator()
实现即可,客户端不需要做任何修改。
- 缺点:
- 工厂模式可能会导致类的数量增加,因为每种具体集合类型都需要提供自己的迭代器实现。