MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

从简单到复杂Java工厂模式的逐步升级

2024-01-033.3k 阅读

一、简单工厂模式

1.1 基本概念

简单工厂模式(Simple Factory Pattern)不属于GoF 23种设计模式之一,但它是工厂模式家族的基础。简单工厂模式定义了一个工厂类,用于创建产品对象。它将对象的创建和使用分离,使得代码的依赖关系更加清晰,提高了代码的可维护性。

1.2 角色构成

  • 工厂类(Creator):这是简单工厂模式的核心,负责创建产品对象。它提供一个创建产品对象的方法,该方法根据传入的参数决定创建哪种具体的产品对象。
  • 抽象产品类(Product):定义了产品的共性,是所有具体产品类的父类或接口。具体产品类必须实现该抽象产品类中定义的方法。
  • 具体产品类(ConcreteProduct):继承或实现抽象产品类,代表具体的产品对象。

1.3 代码示例

假设我们正在开发一个图形绘制系统,需要绘制不同类型的图形,如圆形、矩形。首先定义抽象产品类Shape

// 抽象产品类Shape
public interface Shape {
    void draw();
}

然后定义具体产品类CircleRectangle

// 具体产品类Circle
public class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("绘制圆形");
    }
}
// 具体产品类Rectangle
public class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("绘制矩形");
    }
}

接着创建工厂类ShapeFactory

// 工厂类ShapeFactory
public class ShapeFactory {
    public Shape createShape(String shapeType) {
        if ("circle".equalsIgnoreCase(shapeType)) {
            return new Circle();
        } else if ("rectangle".equalsIgnoreCase(shapeType)) {
            return new Rectangle();
        }
        return null;
    }
}

最后在客户端代码中使用:

public class Client {
    public static void main(String[] args) {
        ShapeFactory factory = new ShapeFactory();
        Shape circle = factory.createShape("circle");
        if (circle != null) {
            circle.draw();
        }
        Shape rectangle = factory.createShape("rectangle");
        if (rectangle != null) {
            rectangle.draw();
        }
    }
}

1.4 优缺点

  • 优点:实现了对象创建和使用的分离,提高了代码的可维护性;客户端不需要知道具体产品类的类名,只需要关心产品的抽象类。
  • 缺点:工厂类的职责过重,所有产品的创建逻辑都集中在一个工厂类中,违反了单一职责原则;如果添加新的产品类,需要修改工厂类的代码,违反了开闭原则。

二、工厂方法模式

2.1 基本概念

工厂方法模式(Factory Method Pattern)是GoF 23种设计模式之一。它将对象的创建延迟到子类中,由子类来决定创建哪一种具体的产品对象。工厂方法模式通过引入抽象工厂类和具体工厂子类,使得代码更具扩展性和维护性。

2.2 角色构成

  • 抽象工厂类(Creator):定义了一个创建产品对象的抽象方法,由具体工厂子类实现。抽象工厂类也可以提供一些通用的方法。
  • 具体工厂类(ConcreteCreator):继承抽象工厂类,实现抽象工厂类中定义的创建产品对象的方法,返回具体的产品对象。
  • 抽象产品类(Product):与简单工厂模式中的抽象产品类类似,定义了产品的共性,是所有具体产品类的父类或接口。
  • 具体产品类(ConcreteProduct):继承或实现抽象产品类,代表具体的产品对象。

2.3 代码示例

继续以图形绘制系统为例,修改代码如下。首先还是抽象产品类Shape

// 抽象产品类Shape
public interface Shape {
    void draw();
}

具体产品类CircleRectangle不变:

// 具体产品类Circle
public class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("绘制圆形");
    }
}
// 具体产品类Rectangle
public class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("绘制矩形");
    }
}

然后定义抽象工厂类ShapeFactory

// 抽象工厂类ShapeFactory
public abstract class ShapeFactory {
    public abstract Shape createShape();
}

接着创建具体工厂类CircleFactoryRectangleFactory

// 具体工厂类CircleFactory
public class CircleFactory extends ShapeFactory {
    @Override
    public Shape createShape() {
        return new Circle();
    }
}
// 具体工厂类RectangleFactory
public class RectangleFactory extends ShapeFactory {
    @Override
    public Shape createShape() {
        return new Rectangle();
    }
}

最后在客户端代码中使用:

public class Client {
    public static void main(String[] args) {
        ShapeFactory circleFactory = new CircleFactory();
        Shape circle = circleFactory.createShape();
        if (circle != null) {
            circle.draw();
        }
        ShapeFactory rectangleFactory = new RectangleFactory();
        Shape rectangle = rectangleFactory.createShape();
        if (rectangle != null) {
            rectangle.draw();
        }
    }
}

2.4 优缺点

  • 优点:符合开闭原则,当添加新的产品类时,只需要创建新的具体工厂类,而不需要修改抽象工厂类和其他具体工厂类的代码;提高了代码的可扩展性和维护性,每个具体工厂类只负责创建一种产品对象,符合单一职责原则。
  • 缺点:工厂类的数量增多,增加了系统的复杂性;如果产品类的创建逻辑比较复杂,会导致具体工厂类的代码量增加。

三、抽象工厂模式

3.1 基本概念

抽象工厂模式(Abstract Factory Pattern)同样是GoF 23种设计模式之一。它提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。抽象工厂模式适用于产品族的创建,一个产品族是由多个相关的产品组成的。

3.2 角色构成

  • 抽象工厂类(AbstractFactory):定义了创建一系列产品对象的抽象方法,由具体工厂子类实现。
  • 具体工厂类(ConcreteFactory):继承抽象工厂类,实现抽象工厂类中定义的创建产品对象的方法,返回具体的产品对象。每个具体工厂类负责创建一个产品族的所有产品。
  • 抽象产品类(AbstractProduct):定义了产品的共性,是所有具体产品类的父类或接口。在抽象工厂模式中,可能存在多个抽象产品类,分别对应不同类型的产品。
  • 具体产品类(ConcreteProduct):继承或实现抽象产品类,代表具体的产品对象。

3.3 代码示例

假设我们的图形绘制系统不仅要绘制图形,还要填充图形颜色。我们可以将图形和颜色看作两个不同类型的产品,构成产品族。首先定义抽象产品类ShapeColor

// 抽象产品类Shape
public interface Shape {
    void draw();
}
// 抽象产品类Color
public interface Color {
    void fill();
}

具体产品类CircleRectangleRedBlue

// 具体产品类Circle
public class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("绘制圆形");
    }
}
// 具体产品类Rectangle
public class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("绘制矩形");
    }
}
// 具体产品类Red
public class Red implements Color {
    @Override
    public void fill() {
        System.out.println("填充红色");
    }
}
// 具体产品类Blue
public class Blue implements Color {
    @Override
    public void fill() {
        System.out.println("填充蓝色");
    }
}

接着定义抽象工厂类AbstractFactory

// 抽象工厂类AbstractFactory
public abstract class AbstractFactory {
    public abstract Shape createShape();
    public abstract Color createColor();
}

然后创建具体工厂类CircleRedFactoryRectangleBlueFactory

// 具体工厂类CircleRedFactory
public class CircleRedFactory extends AbstractFactory {
    @Override
    public Shape createShape() {
        return new Circle();
    }

    @Override
    public Color createColor() {
        return new Red();
    }
}
// 具体工厂类RectangleBlueFactory
public class RectangleBlueFactory extends AbstractFactory {
    @Override
    public Shape createShape() {
        return new Rectangle();
    }

    @Override
    public Color createColor() {
        return new Blue();
    }
}

最后在客户端代码中使用:

public class Client {
    public static void main(String[] args) {
        AbstractFactory circleRedFactory = new CircleRedFactory();
        Shape circle = circleRedFactory.createShape();
        Color red = circleRedFactory.createColor();
        if (circle != null) {
            circle.draw();
        }
        if (red != null) {
            red.fill();
        }
        AbstractFactory rectangleBlueFactory = new RectangleBlueFactory();
        Shape rectangle = rectangleBlueFactory.createShape();
        Color blue = rectangleBlueFactory.createColor();
        if (rectangle != null) {
            rectangle.draw();
        }
        if (blue != null) {
            blue.fill();
        }
    }
}

3.4 优缺点

  • 优点:隔离了具体产品类的创建,客户端只需要与抽象工厂类交互,降低了系统的耦合度;符合开闭原则,当添加新的产品族时,只需要创建新的具体工厂类,而不需要修改其他代码;便于产品族的切换,只需要更换具体工厂类即可。
  • 缺点:产品族扩展困难,如果需要添加新的产品类型,需要修改抽象工厂类及其所有具体工厂类的代码,违反了开闭原则;系统复杂,抽象工厂模式涉及多个抽象类和具体类,增加了系统的理解和维护难度。

四、三种工厂模式的比较与选择

4.1 比较

  • 创建对象的方式:简单工厂模式通过一个工厂类的方法根据传入参数创建不同产品;工厂方法模式通过抽象工厂类定义抽象创建方法,由具体工厂子类实现创建特定产品;抽象工厂模式通过抽象工厂类定义创建一系列产品的抽象方法,具体工厂子类实现创建一个产品族的所有产品。
  • 符合设计原则:简单工厂模式违反了单一职责原则和开闭原则;工厂方法模式符合开闭原则和单一职责原则;抽象工厂模式在产品族扩展时违反开闭原则,但在产品族切换等方面有优势。
  • 适用场景:简单工厂模式适用于产品种类较少且创建逻辑相对简单的场景;工厂方法模式适用于产品种类逐渐增多,需要更灵活的对象创建方式的场景;抽象工厂模式适用于产品有多个维度分类,形成产品族的场景。

4.2 选择

在实际项目中选择合适的工厂模式需要综合考虑多方面因素。如果项目规模较小,产品种类固定且创建逻辑简单,简单工厂模式可以快速实现对象创建的分离。当项目有一定规模,产品种类可能会不断增加,并且需要遵循开闭原则,工厂方法模式是更好的选择。而当项目涉及多个相关产品组成的产品族,并且需要方便地切换产品族时,抽象工厂模式则能发挥其优势。例如,在一个小型的图形绘制工具中,可能使用简单工厂模式就足够;而在一个大型的图形设计软件中,涉及多种图形及其相关属性(如颜色、线条样式等构成产品族),抽象工厂模式可能更合适。同时,在选择过程中也要考虑团队成员对不同模式的熟悉程度以及代码的可维护性和扩展性要求等。

五、工厂模式在开源框架中的应用

5.1 Spring框架中的BeanFactory

在Spring框架中,BeanFactory是一个核心接口,它采用了工厂模式来创建和管理Bean对象。BeanFactory通过配置文件(如XML或注解)读取Bean的定义信息,然后根据这些信息创建Bean实例。这类似于简单工厂模式,将Bean的创建和使用分离。例如,在Spring的XML配置文件中定义一个Bean:

<bean id="userService" class="com.example.UserService"/>

在代码中通过BeanFactory获取UserService实例:

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) context.getBean("userService");

这里的ApplicationContextBeanFactory的子接口,它在获取Bean时,内部实际调用了BeanFactory的创建逻辑。这种方式使得Spring应用中的Bean管理更加灵活,用户不需要关心Bean的具体创建过程,只需要通过配置和获取即可使用。

5.2 Hibernate框架中的SessionFactory

Hibernate框架中的SessionFactory是工厂模式的典型应用。SessionFactory负责创建Session对象,Session对象用于与数据库进行交互。SessionFactory在初始化时会读取Hibernate的配置文件,根据配置信息创建Session实例。这类似于工厂方法模式,SessionFactory作为抽象工厂,具体的SessionFactory实现类(如Configuration.buildSessionFactory()返回的实例)负责创建具体的Session产品。代码示例如下:

Configuration configuration = new Configuration().configure();
SessionFactory sessionFactory = configuration.buildSessionFactory();
Session session = sessionFactory.openSession();

通过SessionFactory创建Session,使得Hibernate的数据库操作更加统一和易于管理,同时也符合开闭原则,方便扩展不同的数据库连接和事务管理策略。

5.3 总结开源框架应用

从Spring和Hibernate等开源框架对工厂模式的应用可以看出,工厂模式在实际项目开发中具有重要意义。它能够有效地解耦对象的创建和使用,提高代码的可维护性和可扩展性。在学习和使用这些开源框架时,深入理解其内部的工厂模式实现,有助于更好地掌握框架的原理和使用方法,同时也能为自己的项目开发提供借鉴,合理运用工厂模式优化代码结构。

六、工厂模式的拓展与优化

6.1 结合反射机制优化简单工厂模式

在简单工厂模式中,工厂类的创建方法通常使用if - elseswitch - case语句来判断创建哪种具体产品。这种方式在产品种类较多时,代码会变得冗长且难以维护。结合反射机制可以优化这一问题。以之前的图形绘制系统为例,修改ShapeFactory如下:

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class ShapeFactory {
    public Shape createShape(String className) {
        try {
            Class<?> clazz = Class.forName(className);
            Constructor<?> constructor = clazz.getConstructor();
            return (Shape) constructor.newInstance();
        } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }
}

在客户端使用时,只需要传入具体产品类的全限定名:

public class Client {
    public static void main(String[] args) {
        ShapeFactory factory = new ShapeFactory();
        Shape circle = factory.createShape("com.example.Circle");
        if (circle != null) {
            circle.draw();
        }
        Shape rectangle = factory.createShape("com.example.Rectangle");
        if (rectangle != null) {
            rectangle.draw();
        }
    }
}

通过反射机制,当添加新的产品类时,不需要修改工厂类的createShape方法,只需要在客户端传入新的类名即可,一定程度上缓解了简单工厂模式违反开闭原则的问题。

6.2 利用泛型优化工厂方法模式

在工厂方法模式中,每个具体工厂类负责创建一种具体产品。可以利用泛型来简化代码结构,使代码更加通用。定义抽象工厂类如下:

public abstract class ShapeFactory<T extends Shape> {
    public abstract T createShape();
}

具体工厂类CircleFactoryRectangleFactory修改为:

public class CircleFactory extends ShapeFactory<Circle> {
    @Override
    public Circle createShape() {
        return new Circle();
    }
}
public class RectangleFactory extends ShapeFactory<Rectangle> {
    @Override
    public Rectangle createShape() {
        return new Rectangle();
    }
}

这样在客户端使用时,代码更加清晰,同时利用泛型的类型检查机制,可以避免一些类型转换错误。

6.3 抽象工厂模式的优化策略

对于抽象工厂模式,在产品族扩展困难的问题上,可以考虑采用一些设计策略来缓解。例如,可以引入一个中间层的配置模块,通过配置文件或数据库来动态管理产品族的信息。当需要添加新的产品族时,只需要在配置模块中添加相应的配置信息,而不需要修改抽象工厂类和具体工厂类的代码。具体实现可以利用Java的SPI(Service Provider Interface)机制,通过在META - INF/services目录下创建配置文件,指定具体工厂类的实现,使得系统能够根据配置动态加载不同的产品族创建逻辑。这样在一定程度上提高了抽象工厂模式的可扩展性,降低了产品族扩展对代码的侵入性。

通过这些拓展与优化方法,可以进一步提升工厂模式在不同场景下的适用性和灵活性,使其更好地满足复杂多变的项目需求。同时,这些优化方法也展示了如何结合Java的其他特性,如反射、泛型等,来完善设计模式的应用,为开发者在实际项目中运用工厂模式提供了更多的思路和方法。