当前位置: 首页 > news >正文

【Java】Java 设计模式之工厂模式与策略模式

Java设计模式是软件工程中一系列被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结,它们代表了最佳的实践,帮助开发者解决在软件设计过程中遇到的各种问题。这些模式可以根据其用途分为三大类:创建型、结构型和行为型,每种模式都有其特定的应用场景和解决的问题,例如单例模式用于确保一个类只有一个实例,工厂模式用于创建对象而不暴露创建逻辑,观察者模式用于定义对象间的一对多依赖关系,使得当一个对象改变状态时,所有依赖于它的对象都会得到通知并自动更新。掌握这些设计模式有助于提高代码的可读性、可维护性和可扩展性。

本文中所讲的工厂模式是一种创建型设计模式,它提供了一个接口,用于创建对象,但允许子类决定实例化的类是哪一个,从而将对象的创建逻辑与实际使用逻辑分离,增强了系统的可扩展性和灵活性。而策略模式是一种行为型设计模式,它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户,使得算法可以独立于客户端进行扩展和切换。

1. 工厂模式

工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一,属于创建型模式。它的主要特点是提供了一种创建对象的方式,使得创建对象的过程与使用对象的过程分离。这样做的好处是将对象的创建逻辑封装在一个工厂类中,从而提高代码的可维护性和可扩展性。

1.1 工厂模式的主要角色:
  1. 抽象工厂(Abstract Factory):定义一个创建对象的接口,但不负责具体的对象创建过程。
  2. 具体工厂(Concrete Factory):实现抽象工厂接口,负责实际创建具体的对象。
  3. 产品(Product):工厂所创建的对象类型。
  4. 具体产品(Concrete Product):实现产品接口的具体对象。

工厂模式的主要目的是将对象的创建过程封装在工厂类中,客户端代码只需要关心从工厂获取对象的过程,而不需要了解对象的创建细节。这样可以降低代码的耦合度。

1.2 工厂模式分类:
  1. 简单工厂模式(Simple Factory Pattern):使用一个单独的工厂类来创建不同的对象,根据传入的参数决定创建哪种类型的对象。
  2. 工厂方法模式(Factory Method Pattern):定义了一个创建对象的接口,但由子类决定实例化哪个类。
  3. 抽象工厂模式(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 工厂模式适用于以下几种场景:
  1. 不确定要使用哪个类的情况:当客户端代码需要创建对象,但具体要创建哪个类的对象在运行时才能确定时,可以使用工厂模式。
  2. 处理复杂对象的创建逻辑:如果一个对象的创建过程很复杂,包含多个步骤或依赖其他对象,使用工厂模式可以将这些逻辑封装起来,简化客户端代码。
  3. 需要屏蔽具体实现的情况:当客户端代码不应依赖于具体的产品类时,工厂模式可以提供一个统一的接口,使得客户端与具体的产品实现解耦。
  4. 系统需要支持多种产品系列的情况:比如一个系统需要支持多种数据库,可以使用抽象工厂模式,为每种数据库提供一个具体的工厂。
2.2 具体场景包括:
  • 日志记录:可以根据配置或运行时条件,选择不同的日志记录器(例如文件日志记录器、数据库日志记录器等)。
  • 数据库访问:系统可能需要支持多种数据库,如 MySQL、PostgreSQL、Oracle 等,工厂模式可以帮助切换不同的数据库实现。
  • 文件格式处理:如果系统需要处理多种文件格式,如 CSV、XML、JSON 等,可以使用工厂模式来创建相应的处理器。
  • 硬件接口适配:在需要与多种硬件设备交互时,可以为每种设备提供一个工厂,以创建相应的接口适配器。
  • 图形界面组件创建:在一个图形用户界面(GUI)应用程序中,根据不同的操作系统创建不同的按钮、文本框等组件。

工厂模式通过隐藏对象创建的细节,提供了更加灵活和可维护的代码结构。通过使用工厂模式,这些场景下的系统可以更加灵活,易于扩展和维护。当需要添加新的产品类型时,只需增加新的产品和对应的工厂,而不需要修改现有代码,符合开闭原则。

3. 策略模式

策略模式(Strategy Pattern)是一种行为型设计模式,它定义了一系列的算法,把它们一个个封装起来,并且使它们可以互相替换。策略模式让算法的变化独立于使用算法的客户。

3.1 策略模式主要包含的角色:
  1. 策略接口(Strategy Interface):定义所有支持的算法的公共接口。策略类将实现这个接口,从而实现具体的算法。
  2. 具体策略类(Concrete Strategies):实现策略接口的类,封装了具体的算法。
  3. 上下文类(Context Class):持有一个策略接口的引用,用于操作策略接口。上下文类并不实现算法,而是通过策略接口调用具体策略类实现的算法。
3.2 策略模式的优点:

优点:

  • 算法可以自由切换:客户端可以根据需要选择不同的策略算法。
  • 扩展性良好:增加新的策略只需要实现策略接口,然后将其注入到上下文中即可。
  • 避免使用多重条件判断:策略模式可以避免在代码中使用大量的if-else或switch-case语句。
  • 维护各算法的独立性:每个策略类封装了自己的算法,减少了算法间的耦合。

缺点:

  1. 客户端必须了解所有策略:策略模式要求客户端必须知道所有的策略类,并理解它们之间的区别,以便能够选择合适的策略。这增加了客户端的负担,尤其是在策略很多或者策略之间差异不明显时。
  2. 策略类数量增加:随着策略的增加,系统中类的数量也会相应增加。每个策略都是一个单独的类,这可能导致类爆炸,增加了系统的复杂性。
  3. 策略的管理:客户端需要负责管理策略对象的生命周期,这会增加客户端的复杂性,尤其是在需要动态切换策略的情况下。

以下是一个简单的策略模式示例:

// 策略接口
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 策略模式适用于以下场景:
  1. 多个算法只在行为上有所不同:当有一组算法,它们执行的任务相同,但实现细节不同,可以使用策略模式来动态选择使用哪个算法。
  2. 算法需要频繁切换:如果应用程序需要根据不同的条件或用户输入频繁更改算法,策略模式可以提供一种灵活的方式来切换算法。例如支付方式的选择、排序算法的选择等。
  3. 算法需要自由扩展:当预计将来会添加更多算法时,策略模式可以轻松地通过添加新的策略类来扩展,而不需要修改现有代码。
  4. 需要隐藏算法的具体实现:策略模式可以将算法的实现细节封装在具体的策略类中,客户端只需通过策略接口与它们交互。
  5. 避免使用多重条件语句:如果代码中存在大量的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) {// 实现快速排序算法}
    }
    

工厂模式关注对象的创建,而策略模式关注算法或行为的封装和切换。两者都是通过抽象来提高代码的灵活性和可扩展性,但它们的应用目的和场景有所不同。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 07 - procfs
  • Java中IO基础文本数据处理:BufferedReader 和 BufferedWriter
  • CSI 插件如何注册到 kubelet 的
  • 【网络基础】探索 NAT 技术:IP 转换、NAPT、NAT穿越及代理服务器
  • Git实战精粹
  • 百度搜索的RLHF性能优化实践
  • APP渠道来源方案探索
  • 在 macOS 上升级 Ruby 版本的几种方法
  • vue事件监听
  • ReentrantLock可重入锁又是怎么回事?
  • CLIP微调方法总结
  • Threejs绘制方形管道
  • IO进程day01(标准IO、缓存区)
  • 51单片机最快能生成多高频率的方波?
  • 趣味算法------试用 6 和 9 组成的最大数字
  • css属性的继承、初识值、计算值、当前值、应用值
  • gulp 教程
  • Java 内存分配及垃圾回收机制初探
  • JavaScript对象详解
  • JS变量作用域
  • js递归,无限分级树形折叠菜单
  • MYSQL 的 IF 函数
  • Redis 中的布隆过滤器
  • Sass 快速入门教程
  • ViewService——一种保证客户端与服务端同步的方法
  • 给初学者:JavaScript 中数组操作注意点
  • 构建工具 - 收藏集 - 掘金
  • 验证码识别技术——15分钟带你突破各种复杂不定长验证码
  • 字符串匹配基础上
  • FaaS 的简单实践
  • Nginx惊现漏洞 百万网站面临“拖库”风险
  • raise 与 raise ... from 的区别
  • 扩展资源服务器解决oauth2 性能瓶颈
  • #07【面试问题整理】嵌入式软件工程师
  • #define用法
  • $.ajax()
  • $NOIp2018$劝退记
  • (007)XHTML文档之标题——h1~h6
  • (31)对象的克隆
  • (7) cmake 编译C++程序(二)
  • (JS基础)String 类型
  • (附源码)spring boot火车票售卖系统 毕业设计 211004
  • (附源码)ssm旅游企业财务管理系统 毕业设计 102100
  • (七)glDrawArry绘制
  • (一)认识微服务
  • (转)LINQ之路
  • (最完美)小米手机6X的Usb调试模式在哪里打开的流程
  • ./configure、make、make install 命令
  • .equal()和==的区别 怎样判断字符串为空问题: Illegal invoke-super to void nio.file.AccessDeniedException
  • .NET Framework、.NET Core 、 .NET 5、.NET 6和.NET 7 和.NET8 简介及区别
  • .NET IoC 容器(三)Autofac
  • .net Stream篇(六)
  • .NET 服务 ServiceController
  • .NET连接MongoDB数据库实例教程
  • .NET设计模式(7):创建型模式专题总结(Creational Pattern)