【Java】Java 设计模式之工厂模式与策略模式
Java设计模式是软件工程中一系列被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结,它们代表了最佳的实践,帮助开发者解决在软件设计过程中遇到的各种问题。这些模式可以根据其用途分为三大类:创建型、结构型和行为型,每种模式都有其特定的应用场景和解决的问题,例如单例模式用于确保一个类只有一个实例,工厂模式用于创建对象而不暴露创建逻辑,观察者模式用于定义对象间的一对多依赖关系,使得当一个对象改变状态时,所有依赖于它的对象都会得到通知并自动更新。掌握这些设计模式有助于提高代码的可读性、可维护性和可扩展性。
本文中所讲的工厂模式是一种创建型设计模式,它提供了一个接口,用于创建对象,但允许子类决定实例化的类是哪一个,从而将对象的创建逻辑与实际使用逻辑分离,增强了系统的可扩展性和灵活性。而策略模式是一种行为型设计模式,它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户,使得算法可以独立于客户端进行扩展和切换。
1. 工厂模式
工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一,属于创建型模式。它的主要特点是提供了一种创建对象的方式,使得创建对象的过程与使用对象的过程分离。这样做的好处是将对象的创建逻辑封装在一个工厂类中,从而提高代码的可维护性和可扩展性。
1.1 工厂模式的主要角色:
- 抽象工厂(Abstract Factory):定义一个创建对象的接口,但不负责具体的对象创建过程。
- 具体工厂(Concrete Factory):实现抽象工厂接口,负责实际创建具体的对象。
- 产品(Product):工厂所创建的对象类型。
- 具体产品(Concrete Product):实现产品接口的具体对象。
工厂模式的主要目的是将对象的创建过程封装在工厂类中,客户端代码只需要关心从工厂获取对象的过程,而不需要了解对象的创建细节。这样可以降低代码的耦合度。
1.2 工厂模式分类:
- 简单工厂模式(Simple Factory Pattern):使用一个单独的工厂类来创建不同的对象,根据传入的参数决定创建哪种类型的对象。
- 工厂方法模式(Factory Method Pattern):定义了一个创建对象的接口,但由子类决定实例化哪个类。
- 抽象工厂模式(Abstract Factory Pattern):提供一个创建一系列相关或互相依赖对象的接口,而无需指定它们具体的类。
1.3 工厂模式的优缺点:
优点:
- 调用者只需要知道对象的名称即可创建对象。
- 扩展性高,如果需要增加新产品,只需扩展一个工厂类即可。
- 屏蔽了产品的具体实现,调用者只关心产品的接口。
缺点:
- 每次增加一个产品时,都需要增加一个具体类和对应的工厂,使系统中类的数量成倍增加,增加了系统的复杂度和具体类的依赖。
下面是一个简单的工厂模式示例,我们将创建一个简单的图形工厂,该工厂能够根据给定的类型创建不同的图形对象。
首先,我们定义一个图形接口和几个具体的图形类:
// 图形接口
interface Shape {
void draw();
}
// 具体的图形类:圆形
class Circle implements Shape {
public void draw() {
System.out.println("Drawing Circle");
}
}
// 具体的图形类:矩形
class Rectangle implements Shape {
public void draw() {
System.out.println("Drawing Rectangle");
}
}
// 具体的图形类:正方形
class Square implements Shape {
public void draw() {
System.out.println("Drawing Square");
}
}
接下来,我们定义一个图形工厂类,它将根据传入的类型参数来创建并返回相应的图形对象:
// 图形工厂类
class ShapeFactory {
// 获取图形对象
public Shape getShape(String shapeType) {
if (shapeType == null) {
return null;
}
if (shapeType.equalsIgnoreCase("CIRCLE")) {
return new Circle();
} else if (shapeType.equalsIgnoreCase("RECTANGLE")) {
return new Rectangle();
} else if (shapeType.equalsIgnoreCase("SQUARE")) {
return new Square();
}
return null;
}
}
最后,我们使用这个工厂类来创建图形对象并调用它们的draw
方法:
public class FactoryPatternDemo {
public static void main(String[] args) {
ShapeFactory shapeFactory = new ShapeFactory();
// 获取 Circle 对象并调用它的 draw 方法
Shape circle = shapeFactory.getShape("CIRCLE");
circle.draw();
// 获取 Rectangle 对象并调用它的 draw 方法
Shape rectangle = shapeFactory.getShape("RECTANGLE");
rectangle.draw();
// 获取 Square 对象并调用它的 draw 方法
Shape square = shapeFactory.getShape("SQUARE");
square.draw();
}
}
在以上示例中,ShapeFactory
类扮演了工厂的角色,它根据传入的字符串参数来决定创建哪种类型的图形对象。客户端代码不需要直接实例化具体的图形类,而是通过工厂类来获取所需的图形对象。这样,如果将来需要添加新的图形类,只需修改工厂类即可,无需修改客户端代码,这符合开闭原则。
2. 工厂模式使用场景
2.1 工厂模式适用于以下几种场景:
- 不确定要使用哪个类的情况:当客户端代码需要创建对象,但具体要创建哪个类的对象在运行时才能确定时,可以使用工厂模式。
- 处理复杂对象的创建逻辑:如果一个对象的创建过程很复杂,包含多个步骤或依赖其他对象,使用工厂模式可以将这些逻辑封装起来,简化客户端代码。
- 需要屏蔽具体实现的情况:当客户端代码不应依赖于具体的产品类时,工厂模式可以提供一个统一的接口,使得客户端与具体的产品实现解耦。
- 系统需要支持多种产品系列的情况:比如一个系统需要支持多种数据库,可以使用抽象工厂模式,为每种数据库提供一个具体的工厂。
2.2 具体场景包括:
- 日志记录:可以根据配置或运行时条件,选择不同的日志记录器(例如文件日志记录器、数据库日志记录器等)。
- 数据库访问:系统可能需要支持多种数据库,如 MySQL、PostgreSQL、Oracle 等,工厂模式可以帮助切换不同的数据库实现。
- 文件格式处理:如果系统需要处理多种文件格式,如 CSV、XML、JSON 等,可以使用工厂模式来创建相应的处理器。
- 硬件接口适配:在需要与多种硬件设备交互时,可以为每种设备提供一个工厂,以创建相应的接口适配器。
- 图形界面组件创建:在一个图形用户界面(GUI)应用程序中,根据不同的操作系统创建不同的按钮、文本框等组件。
工厂模式通过隐藏对象创建的细节,提供了更加灵活和可维护的代码结构。通过使用工厂模式,这些场景下的系统可以更加灵活,易于扩展和维护。当需要添加新的产品类型时,只需增加新的产品和对应的工厂,而不需要修改现有代码,符合开闭原则。
3. 策略模式
策略模式(Strategy Pattern)是一种行为型设计模式,它定义了一系列的算法,把它们一个个封装起来,并且使它们可以互相替换。策略模式让算法的变化独立于使用算法的客户。
3.1 策略模式主要包含的角色:
- 策略接口(Strategy Interface):定义所有支持的算法的公共接口。策略类将实现这个接口,从而实现具体的算法。
- 具体策略类(Concrete Strategies):实现策略接口的类,封装了具体的算法。
- 上下文类(Context Class):持有一个策略接口的引用,用于操作策略接口。上下文类并不实现算法,而是通过策略接口调用具体策略类实现的算法。
3.2 策略模式的优点:
优点:
- 算法可以自由切换:客户端可以根据需要选择不同的策略算法。
- 扩展性良好:增加新的策略只需要实现策略接口,然后将其注入到上下文中即可。
- 避免使用多重条件判断:策略模式可以避免在代码中使用大量的if-else或switch-case语句。
- 维护各算法的独立性:每个策略类封装了自己的算法,减少了算法间的耦合。
缺点:
- 客户端必须了解所有策略:策略模式要求客户端必须知道所有的策略类,并理解它们之间的区别,以便能够选择合适的策略。这增加了客户端的负担,尤其是在策略很多或者策略之间差异不明显时。
- 策略类数量增加:随着策略的增加,系统中类的数量也会相应增加。每个策略都是一个单独的类,这可能导致类爆炸,增加了系统的复杂性。
- 策略的管理:客户端需要负责管理策略对象的生命周期,这会增加客户端的复杂性,尤其是在需要动态切换策略的情况下。
以下是一个简单的策略模式示例:
// 策略接口
interface Strategy {
void execute();
}
// 具体策略A
class ConcreteStrategyA implements Strategy {
public void execute() {
// 具体的算法实现
}
}
// 具体策略B
class ConcreteStrategyB implements Strategy {
public void execute() {
// 具体的算法实现
}
}
// 上下文类
class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public void executeStrategy() {
strategy.execute();
}
}
// 使用
Context context = new Context(new ConcreteStrategyA());
context.executeStrategy(); // 执行策略A
context.setStrategy(new ConcreteStrategyB());
context.executeStrategy(); // 切换策略,执行策略B
在这个例子中,Context
类可以根据需要切换不同的策略,而客户端代码只需要知道如何与Context
交互,不需要关心具体策略的实现细节。
4. 策略模式使用场景
4.1 策略模式适用于以下场景:
- 多个算法只在行为上有所不同:当有一组算法,它们执行的任务相同,但实现细节不同,可以使用策略模式来动态选择使用哪个算法。
- 算法需要频繁切换:如果应用程序需要根据不同的条件或用户输入频繁更改算法,策略模式可以提供一种灵活的方式来切换算法。例如支付方式的选择、排序算法的选择等。
- 算法需要自由扩展:当预计将来会添加更多算法时,策略模式可以轻松地通过添加新的策略类来扩展,而不需要修改现有代码。
- 需要隐藏算法的具体实现:策略模式可以将算法的实现细节封装在具体的策略类中,客户端只需通过策略接口与它们交互。
- 避免使用多重条件语句:如果代码中存在大量的if-else或switch-case语句来选择不同的算法,使用策略模式可以替代这些条件语句,使代码更加清晰。
4.2 具体的应用场景包括:
- 支付系统:根据不同的支付方式(如信用卡、PayPal、支付宝等)选择不同的支付策略。
- 排序算法:实现不同的排序算法(如快速排序、冒泡排序、归并排序等),根据数据特点选择合适的排序策略。
- 图像处理:根据不同的图像处理需求(如缩放、旋转、过滤等)选择不同的处理策略。
- 折扣计算:在电子商务系统中,根据不同的促销活动计算不同的折扣策略。
- 验证机制:根据不同的用户角色或操作类型选择不同的验证策略。
- 数据导出:根据用户需求将数据导出为不同的格式(如PDF、Excel、CSV等)。
通过使用策略模式,这些场景下的系统可以更加灵活,易于维护和扩展。策略模式有助于保持代码的整洁和可读性,同时提供了在运行时动态更改算法的能力。
5. 工厂模式和策略模式区别
工厂模式和策略模式都是常用的设计模式,但它们解决的问题和应用场景不同。以下是它们之间的主要区别:
5.1 目的:
- 工厂模式:用于创建对象,它封装了对象的创建逻辑,使得创建对象的过程与使用对象的过程分离。
- 策略模式:用于定义一系列算法,将每个算法封装起来,并使它们可以互换,从而让算法的变化独立于使用算法的客户。
5.2 关注点:
- 工厂模式:关注对象的创建过程。
- 策略模式:关注算法或行为的切换和扩展。
5.3 主要组件:
- 工厂模式:
- 抽象工厂(Abstract Factory)
- 具体工厂(Concrete Factory)
- 产品(Product)
- 具体产品(Concrete Product)
- 策略模式:
- 策略接口(Strategy Interface)
- 具体策略(Concrete Strategies)
- 上下文(Context)
5.4 使用场景:
- 工厂模式:
- 当创建对象的过程复杂,需要封装时。
- 当需要根据不同条件创建不同类型的对象时。
- 当系统需要与多个产品系列交互,但只想通过一个统一接口与它们交互时。
- 策略模式:
- 当多个类只区别在行为上,可以使用策略模式来动态选择不同的行为。
- 当需要自由切换算法或行为时。
- 当算法需要扩展,但不想修改使用算法的客户端代码时。
5.5 优缺点:
- 工厂模式:
- 优点:提高了代码的扩展性和可维护性,降低了对象间的耦合。
- 缺点:可能导致系统中类的数量增加,增加了系统的复杂度。
- 策略模式:
- 优点:算法可以自由切换,扩展性好,避免了使用多重条件判断。
- 缺点:客户端需要知道所有的策略类,并理解它们之间的区别。
5.6 示例对比:
-
工厂模式:
interface VehicleFactory { Vehicle createVehicle(); } class CarFactory implements VehicleFactory { public Vehicle createVehicle() { return new Car(); } } class BikeFactory implements VehicleFactory { public Vehicle createVehicle() { return new Bike(); } }
-
策略模式:
interface SortingStrategy { void sort(List<Integer> items); } class BubbleSortStrategy implements SortingStrategy { public void sort(List<Integer> items) { // 实现冒泡排序算法 } } class QuickSortStrategy implements SortingStrategy { public void sort(List<Integer> items) { // 实现快速排序算法 } }
工厂模式关注对象的创建,而策略模式关注算法或行为的封装和切换。两者都是通过抽象来提高代码的灵活性和可扩展性,但它们的应用目的和场景有所不同。