设计模式之抽象工厂模式--创建一系列相关对象的艺术(简单工厂、工厂方法、到抽象工厂的进化过程,类图NS图)

目录

  • 概述
    • 概念
    • 适用场景
    • 结构
    • 类图
  • 衍化过程
    • 业务需求
    • 基本的数据访问程序
    • 工厂方法实现数据访问程序
    • 抽象工厂实现数据访问程序
    • 简单工厂改进抽象工厂
    • 使用反射+抽象工厂
    • 反射+配置文件
    • 衍化过程总结
  • 常见问题
  • 总结

概述

概念

    抽象工厂模式是一种创建型设计模式,它提供了一种将相关对象组合在一起创建的方式,而无需指定具体类。该模式通过定义一个抽象工厂接口来创建一系列相关或依赖的对象,而不是直接实例化具体类。这种方式使得系统更加灵活,易于扩展和维护。

适用场景

抽象工厂模式适用于以下情况:

当一个系统需要独立于其产品的创建、组合和表示时;
当一个系统需要由多个系列的产品中的一个进行配置时;
当强调一系列相关产品对象的创建和一起使用时;
当提供一个产品类库,而只想显示它们的接口而不是实现时。

结构

抽象工厂模式包含以下几个角色:

抽象工厂(Abstract Factory):声明了创建一系列产品对象的接口。
具体工厂(Concrete Factory):实现了抽象工厂接口,具体工厂负责创建具体的产品对象。
抽象产品(Abstract Product):声明了具体产品的接口。
具体产品(Concrete Product):实现了抽象产品接口,具体产品是由具体工厂创建的。

类图

在这里插入图片描述

衍化过程

业务需求

    假设有一个简单的应用程序,它使用了 SQL Server 数据库来存储数据。现在需要将数据库更换为 Access 数据库,同时保持应用程序的功能不变。

基本的数据访问程序

在这里插入图片描述
客户端直接和qlserverUser耦合
在这里插入图片描述

//SqlServer
public class SqlServerUser {//新增用户public void insert(User user){System.out.println("在SQlServer中给USER表添加一条数据");}//获取用户信息public User getUser(int id){System.out.println("在SQlServer中根据用户id得到USER表中的一条记录");return null;}
}//user表
public class User {private int _id;public int getid(){return this._id;}public void setId(int value){this._id=value;}private String _name;public String getname(){return this._name;}public void setname(String value){this._name=value;}
}//客户端
public class Client {public static void main(String[] args) {User user=new User();SqlServerUser su=new SqlServerUser();su.insert(user);su.getUser(1);}
}

    在这段客户端代码中,可以看到SqlServerUser su=new SqlServerUser();使su这个对象被框死在了SqlServerUser上。
    现在要做的就是解除客户端和SqlServerUser 的耦合(简答说就是客户端不再依赖SqlServerUser ,那么更换其他的数据库管理系统就不会影响应用程序了)

工厂方法实现数据访问程序

    解除客户端和SqlServerUser对象的耦合,就是把 new SqlServerUser()封装起来,这点上想到使用工厂方法封装new SqlServerUser()过程。
在这里插入图片描述
在这里插入图片描述

抽出两个接口

//工厂接口
interface IFactory {public IUser  creatUserDB();}
//数据库接口
public interface IUser {public void insert(User user);public User getUser(int id);
}//SqlServerUser ,用于访问SqlServer的User 
public class SqlServerUser implements IUser {//新增用户@Overridepublic void insert(User user) {System.out.println("在SQlServer中给USER表添加一条数据");}public User getUser(int id){System.out.println("在SQlServer中根据用户id得到USER表中的一条记录");return null;}
}
//SqlServerFactory 实例化SqlServerUser
public class SqlServerFactory implements IFactory {@Overridepublic IUser creatUser() {return new SqlServerUser();}
}//客户端
public static void main(String[] args) {Ifactory factory=new SqlServerFactory();User user=new User();IUser iu = factory.creatUser();iu.insert(user);iu.getUser(1);}

    再来看客户端声明IUser接口的对象iu,事先并不知道要访问哪个数据库,却可以在运行时完成工作,这就是业务逻辑和数据访问的解耦(也就是客户端不再依赖SqlServerUser,如果需要更换AccessUser,只需要创建一个AccessFactory,由AccessFactory封装创建AccessUser对象的过程。)
    现在又有了新的问题:
    数据库里如果不是只有一个User表呢,该怎么办?

抽象工厂实现数据访问程序

    现在需要增加一个Department表,SqlServer和Access分别操作这个Department表,
    再抽出一个IDepartment的接口,两个工厂了分别增加创建SqlserverDepartment和AccessDepartment的方法

在这里插入图片描述
在这里插入图片描述
工厂里两个方法,可以有不同的实现,可以理解为两个不同的系列

//工厂接口
public interface IFactory {public IUser creatUserDB();public IDepartment createDepartment();
}
//客户端
public class Client {public static void main(String[] args) {//需要对两个表操作,客户端只认识两个表,不认识Access和SQLServerUser user = new User();Department department = new Department();//需要用哪个DBMS就实例化哪个工厂IFactory factory = new SqlServerFactory();//实例化数据库交给对应的工厂IUser iu = factory.creatUserDB();//user表里插入,读取操作iu.getUser(1);iu.insert(user);IDepartment idept=factory.createDepartment();//department表里插入,读取操作idept.getDepartment(2);idept.insert(department);}
}

    再分析一下需求,我们是要读写User表和Department表,IUser和IDepartment里有对应的方法,也就是干活的是这两个接口的实现类,SqlServerUser、AccessUser;以及SqlServerDepartment、AccessDepartment,那谁负责生产这些实现类的对象呢,那就看谁的返回对象是IUser和IDepartment,当然是工厂,SqlServerFactory和AccessFactory.

  • 抽象工厂的优势现在体现出来了:
    1、易于交换产品系列
    由于具体工厂类,例如Factory factory=new AccessFactory0,
    在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置。
    我们的设计不能去防止需求的更改,那么我们的理想便是让改动变得最小
    2、它让具体的创建实例过程与客户端分离(这点工厂方法也做到了)
    客户端是通过它们的抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户代码中。事实上客户端所认识的只有User和Department,至于它是用SQL Server来实现还是Access来实现就不知道了。”

  • 但是仍然存在的问题:
    1、业务扩充
    要增加项目表Project,至少要增加三个类,IProject、.SqlserverProject、AccessProject,.还需要更改IFactory、SqlserverFactory和AccessFactory才可以完全实现。(对应两个表的两个dbms,两个生产dbms的工厂,还有两个接口)
    2、客户端如果多个,改动就很多
    有很多地方使用IUesr或Department,而这样的设计,其实在每一个类的开始都需要声明Factory factory=new SqlserverFactory0,如果有100个调用数据库访问的类,就要更改l00次Factory factory=new AccessFactory()这样的代码

简单工厂改进抽象工厂

    抽象工厂客户端耦合太多
    去掉工厂,创建对象交给DateAccess
在这里插入图片描述
在这里插入图片描述

//DataAccess 负责创建具体数据库对象
public class public class DataAccess {private static String db="Sqlserver";//可换成Access//创建用户对象工厂public static IUser createUser(){IUser result=null;switch (db){case "Sqlserver":result=new SqlServerUser();break;case "Access":result=new AccessUser();break;}return result;}//创建部门对象工厂public static  IDepartment createDepartment(){IDepartment result=null;switch (db){case "Sqlserver":result=new SqlServerDepartment();break;case "Access":result=new AccessDepartment();break;}return result;}}
{private static String db="Sqlserver";//可换成Access//创建用户对象工厂public static IUser createUser(){IUser result=null;switch (db){case "Sqlserver":result=new SqlServerUser();break;case "Access":result=new AccessUser();break;}return result;}//创建部门对象工厂public static  IDepartment createDepartment(){IDepartment result=null;switch (db){case "Sqlserver":result=new SqlServerDepartment();break;case "Access":result=new AccessDepartment();break;}return result;}}//客户端
public class Client {public static void main(String[] args) {//需要对两个表操作,客户端只认识两个表,不认识Access和SQLServerUser user = new User();Department department = new Department();//实例化数据库交给DataAccessIUser iu = DataAccess.createUser();//user表里插入,读取操作iu.getUser(1);iu.insert(user);IDepartment idept=DataAccess.createDepartment();//department表里插入,读取操作idept.getDepartment(2);idept.insert(department);}
}

问题:
    如果需要增加Oracle数据库访问,抽象工厂只增加一个OracleFactory工厂类就可以了,现在就需要在DataAccess类中每个方法的swicth中加case
改进:
    不在程序里写明‘如果是Sqlserver就去实例化SQL Server数据库相关类,如果是Access就去实例化Access相关类’这样的语句,而是根据字符串db的值去某个地方找应该要实例化的类是哪一个。这样就不用switch case了

使用反射+抽象工厂

类图只改变DataAccess就可以
在这里插入图片描述
在这里插入图片描述

public class DataAccess {private static String assemblyName="designpatterns.abstractfactory.reflaxAbstractFactoryAccess.DB.";private static String db="SqlServer";//数据库名称,可替换为Access//创建用户对象工厂public static IUser createUser(){return (IUser)getInstance(assemblyName+db+"User");}//创建部门对象工厂public static IDepartment createDepartment(){return (IDepartment) getInstance(assemblyName+db+"Department");}private static  Object getInstance(String className){Object result=null;try {result=Class.forName(className).getDeclaredConstructor().newInstance();}catch(InvocationTargetException e){e.printStackTrace();}catch(NoSuchMethodException e){e.printStackTrace();}catch(InstantiationException e){e.printStackTrace();}catch(IllegalAccessException e){e.printStackTrace();}catch(ClassNotFoundException e){e.printStackTrace();}return result;}}

    对象可以根据类路径动态创建了,但是在更换数据库访问时,还是需要去改程序(改db这个字符串的值)重编译,如果可以不改程序,那才是真正地符合开放-封闭原则。

反射+配置文件

相对于只用反射,属性db(数据库名)动态获取了
在这里插入图片描述

public class DataAccess {private static String assemblyName="designpatterns.abstractfactory.profileReflaxAbstractFactoryAccess.DB.";public static String getDb() throws IOException {String result="";Properties properties=new Properties();properties.load(new FileInputStream("E:\\tgb\\training program\\java\\myProject\\src\\main\\java\\designpatterns\\abstractfactory\\profileReflaxAbstractFactoryAccess\\db.properties"));result=properties.getProperty("db");return result;}//创建用户对象工厂public static IUser createUser() throws IOException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {String db=getDb();return (IUser)getInstance(assemblyName+db+"User");}
//反射获取对象public static  IDepartment createDepartment() throws IOException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {String db=getDb();return (IDepartment) getInstance(assemblyName+db+"Department");}private static  Object getInstance(String className) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {Object result=null;result=Class.forName(className).getDeclaredConstructor().newInstance();     return result;}}

进步:
    如果更换数据库,无须重新编译代码,只需要改配置文件就可以。
    所有在用简单工厂的地方,都可以考虑用反射技术来去除switch或if,解除分支判断带来的耦合。”
    反射技术可以很好地解决switch 或者if分支难以应对变化,难以维护和扩展的诟病。

衍化过程总结

    在上面的示例中,业务需求是通过数据库连接来执行查询操作。一开始,使用了简单的数据访问方式,直接在应用程序中编写了连接和查询数据库的代码。

    随着业务的发展,需要将数据库从 SQL Server 更换为 Access。为了实现这个数据库更换的需求,采用了工厂方法模式。通过引入抽象产品(IUser)和具体产品(SqlServerUser和AccessUser),抽象工厂(IFactory)和具体工厂(SqlServerFactory和AccessFactory)将数据库连接的创建和使用进行了解耦,使得代码更加灵活和可扩展。

    然后,需要增加一个部门表,让两个数据库去访问,为了应对这种变化,引入了抽象工厂模式。通过在具体工厂(SqlServerFactory和AccessFactory)中增加一个creatDepartment方法,解决了创建SqlServerDepartment和AccessDepartment的问题。
后面又为了更加灵活,介绍了用简单工厂来改进抽象工厂,以及使用反射和配置文件,真正实现三高。

    每次的变化都是为了应对业务需求的不断变化。最初的简单数据访问方式可能适应了当时的需求,但随着业务的发展和技术环境的变化,需要更灵活、可扩展的解决方案。工厂方法和抽象工厂模式提供了一种可扩展的设计,使得我们能够快速适应业务变化,并以相对低的开销进行数据库类型的切换。

常见问题

什么是一系列相关或依赖的对象?
    一系列相关或相互依赖的对象指的是一组具有共同特征或者相互之间存在某种关联关系的对象集合。这些对象通常有着相似或者相互关联的功能、属性或行为。
如何判断这些对象是一系列相关或相互依赖的呢?

  • 共同特征:这些对象具有共同的特征或者属性。例如,它们可能都实现了同一个接口或者继承了同一个抽象类,并拥有类似的方法或属性。

  • 相互关联:这些对象之间存在某种关联关系,彼此之间相互依赖。例如,它们可能是一种逻辑上或功能上相关的对象,需要协同工作以完成某个复杂的任务。

  • 执行步骤:这些对象在特定的操作过程中需要按照一定的顺序或流程进行调用。它们可能是按照特定的顺序被创建、初始化、配置或者销毁的。

  • 统一管理:这些对象可能需要由同一个抽象工厂来创建和管理。抽象工厂可以提供一种统一的方式来创建这些对象,确保它们之间的协调性和一致性。

    总之,一系列相关或相互依赖的对象在抽象工厂模式中是作为一个整体出现的,它们具有共同特征、相互关联,需要按照一定顺序进行操作,并由同一个抽象工厂进行创建和管理。这样可以更好地实现对象之间的协作和组织。
举个栗子
在这里插入图片描述

    比如有一个一个汽车制造厂,可以使用抽象工厂模式来创建一系列相关或相互依赖的对象。

    假设汽车制造厂需要生产不同类型的汽车,如轿车(Sedan)和SUV(Sport Utility Vehicle)。每种类型的汽车由多个部件构成,包括引擎(Engine)、底盘(Chassis)和车身(Body)等。

在这个例子中,可以定义以下抽象类和接口:

  • CarFactory(抽象工厂):定义了用于创建汽车部件的方法。
  • Engine(抽象产品):定义了引擎的功能。
  • Chassis(抽象产品):定义了底盘的功能。
  • Body(抽象产品):定义了车身的功能。

具体的类可以包括:

  • SedanCarFactory(具体工厂):实现了CarFactory接口,并负责创建轿车相关的部件。

  • SedanEngine(具体产品):实现了Engine接口,提供了轿车引擎的具体功能。

  • SedanChassis(具体产品):实现了Chassis接口,提供了轿车底盘的具体功能。

  • SedanBody(具体产品):实现了Body接口,提供了轿车车身的具体功能。

  • SuvCarFactory(具体工厂):实现了CarFactory接口,并负责创建SUV相关的部件。

  • SuvEngine(具体产品):实现了Engine接口,提供了SUV引擎的具体功能。

  • SuvChassis(具体产品):实现了Chassis接口,提供了SUV底盘的具体功能。

  • SuvBody(具体产品):实现了Body接口,提供了SUV车身的具体功能。

    在这个例子中,抽象工厂模式将一系列相关或相互依赖的对象(引擎、底盘和车身)组合到具体的工厂中(轿车工厂和SUV工厂)。每个具体工厂负责创建特定类型汽车所需的部件,并保证这些部件之间的协调性和一致性。

    例如,当需要生产一辆轿车时,可以使用SedanCarFactory创建引擎、底盘和车身。而当需要生产一辆SUV时,可以使用SuvCarFactory创建相应的部件。

    通过使用抽象工厂模式,汽车制造厂可以轻松扩展以生产不同类型的汽车,而无需修改现有代码。同时,不同类型的汽车部件之间的依赖关系也得到了良好的管理和维护,提高了系统的可维护性和可扩展性。

总结

    抽象工厂模式通过引入抽象工厂接口,使得客户端代码与具体产品的实现解耦。它提供了一种简单的方式来创建一系列相关的产品对象,而无需关心具体的实现细节。通过选择不同的具体工厂,可以轻松地切换不同的产品系列,以满足不同的需求。抽象工厂模式在大型系统中非常有用,它能够提高代码的可维护性、可扩展性和可测试性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/146952.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【开发篇】十、Spring缓存:手机验证码的生成与校验

文章目录 1、缓存2、用HashMap模拟自定义缓存3、SpringBoot提供缓存的使用4、手机验证码案例完善 1、缓存 缓存是一种介于数据永久存储介质与数据应用之间的数据临时存储介质使用缓存可以有效的减少低速数据读取过程的次数(例如磁盘IO),提高…

Shapiro-Francia正态检验

Shapiro-Francia检验是一种用于检验数据是否来自正态分布的统计方法。它是Shapiro-Wilk检验的一个变种,通常适用于小到中等样本大小的数据集。Shapiro-Francia检验的核心思想是通过计算统计量来评估数据的正态性。 Shapiro-Francia检验的零假设是数据来自正态分布&…

26 docker前后端部署

[参考博客]((257条消息) DockerNginx部署前后端分离项目(SpringBootVue)的详细教程_在docker中安装nginx实现前后端分离_这里是杨杨吖的博客-CSDN博客) (DockerNginx部署前后端分离项目(SpringBootVue)) 安装docker # 1、yum 包更新到最新 yum update # 2、安装需要的软件包…

JavaSE | 初识Java(七) | 数组 (下)

Java 中提供了 java.util.Arrays 包 , 其中包含了一些操作数组的常用方法 代码实例: import java.util.Arrays int[] arr {1,2,3,4,5,6}; String newArr Arrays.toString(arr); System.out.println(newArr); // 执行结果 [1, 2, 3, 4, 5, 6] 数组拷贝 代码实例…

cf 解题报告 01

E. Power of Points Problem - 1857E - Codeforces 题意: 给你 n n n 个点,其整数坐标为 x 1 , … x n x_1,\dots x_n x1​,…xn​,它们位于一条数线上。 对于某个整数 s s s,我们构建线段[ s , x 1 s,x_1 s,x1​], [ s , x…

C语言结构体指针学习

结构体变量存放内存中,也有起始地址,定义一个变量来存放这个地址,那这个变量就是结构体指针; typedef struct mydata{int a1;int a2;int a3; }mydata;void CJgtzzView::OnDraw(CDC* pDC) {CJgtzzDoc* pDoc GetDocument();ASSERT…

npm ,yarn 更换使用国内镜像源,淘宝源

背景 文章首发地址 在平时开发当中,我们经常会使用 Npm,yarn 来构建 web 项目。但是npm默认的源的服务器是在国外的,如果没有梯子的话。下载速度会特别慢。那有没有方法解决呢? 其实是有的,设置国内镜像即可&#x…

基于web的医院预约挂号系统/医院管理系统

摘 要 随着信息技术和网络技术的飞速发展,人类已进入全新信息化时代,传统管理技术已无法高效,便捷地管理信息。为了迎合时代需求,优化管理效率,各种各样的管理系统应运而生,各行各业相继进入信息管理时代&a…

如何解决版本不兼容Jar包冲突问题

如何解决版本不兼容Jar包冲突问题 引言 “老婆”和“妈妈”同时掉进水里,先救谁? 常言道:编码五分钟,解冲突两小时。作为Java开发来说,第一眼见到ClassNotFoundException、 NoSuchMethodException这些异常来说&…

八大排序(三)堆排序,计数排序,归并排序

一、堆排序 什么是堆排序:堆排序(Heap Sort)就是对直接选择排序的一种改进。此话怎讲呢?直接选择排序在待排序的n个数中进行n-1次比较选出最大或者最小的,但是在选出最大或者最小的数后,并没有对原来的序列…

k8s--storageClass自动创建PV

文章目录 一、storageClass自动创建PV1.1 安装NFS1.2 创建nfs storageClass1.3 测试自动创建pv 一、storageClass自动创建PV 这里使用NFS实现 1.1 安装NFS 安装nfs-server: sh nfs_install.sh /mnt/data03 10.60.41.0/24nfs_install.sh #!/bin/bash### How to i…

springboot 简单配置mongodb多数据源

准备工作&#xff1a; 本地mongodb一个创建两个数据库 student 和 student-two 所需jar包&#xff1a; # springboot基于的版本 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId>&l…

SSM - Springboot - MyBatis-Plus 全栈体系(十六)

第三章 MyBatis 三、MyBatis 多表映射 2. 对一映射 2.1 需求说明 根据 ID 查询订单&#xff0c;以及订单关联的用户的信息&#xff01; 2.2 OrderMapper 接口 public interface OrderMapper {Order selectOrderWithCustomer(Integer orderId); }2.3 OrderMapper.xml 配置…

一文拿捏基于redis的分布式锁、lua、分布式性能提升

1.分布式锁 jdk的锁&#xff1a; 1、显示锁&#xff1a;Lock 2、隐式锁&#xff1a;synchronized 使用jdk锁保证线程的安全性要求&#xff1a;要求多个线程必须运行在同一个jvm中 但现在的系统基本都是分布式部署的&#xff0c;一个应用会被部署到多台服务器上&#xff0c;s…

【系统架构】软件架构的演化和维护

导读&#xff1a;本文整理关于软件架构的演化和维护知识体系。完整和扎实的系统架构知识体系是作为架构设计的理论支撑&#xff0c;基于大量项目实践经验基础上&#xff0c;不断加深理论体系的理解&#xff0c;从而能够创造新解决系统相关问题。 目录 1、软件架构演化和定义 …

关掉在vscode使用copilot时的提示音

1. 按照图示的操作File --> Preferences --> Settings 2. 搜索框输入关键字Sound&#xff0c;因为是要关掉声音&#xff0c;所以找有关声音的设置 3. 找到如下图所示的选项 Audio Cues:Line Has Inline Suggetion,将其设置为Off 这样&#xff0c;就可以关掉suggest code时…

Python无废话-基础知识字典Dictionary详讲

“字典Dictionary” 是一种无序、可变且可嵌套的数据类型&#xff0c;用于存储键值对。字典使用花括号{}来定义&#xff0c;并用逗号分隔键值对。本文对字典常使用方法&#xff0c;创建字典、添加字典、删除字典、如何获取字典做了知识归纳。 字典有以下几个特征&#xff1a; …

傅里叶系列 P1 的定价选项

如果您想了解更多信息&#xff0c;请查看第 2 部分和第 3 部分。 一、说明 这是第一篇文章&#xff0c;我将帮助您获得如何使用这个新的强大工具来解决金融中的半分析问题并取代您的蒙特卡洛方法的直觉。 我们都知道并喜欢蒙特卡洛数字积分方法&#xff0c;但是如果我告诉你你可…

VS code本地安装PlantUML

VS code本地安装PlantUML 需要条件vs code安装插件使用常见错误 需要条件 在VS Code上安装PlantUML扩展之前&#xff0c;请确保您具有以下先决条件: : Java与GraphViz(点击可直接跳转下载界面); 安装省略 vs code安装插件 vs code安装以下两个插件&#xff08;PlantUML,Grap…

nodejs+vue流浪猫狗救助领养elementui

第三章 系统分析 10 3.1需求分析 10 3.2可行性分析 10 3.2.1技术可行性&#xff1a;技术背景 10 3.2.2经济可行性 11 3.2.3操作可行性&#xff1a; 11 3.3性能分析 11 3.4系统操作流程 12 3.4.1管理员登录流程 12 3.4.2信息添加流程 12 3.4.3信息删除流程 13 第四章 系统设计与…