设计模式之抽象工厂模式--创建一系列相关对象的艺术(简单工厂、工厂方法、到抽象工厂的进化过程,类图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.mzph.cn/news/93162.shtml

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

相关文章

react create-react-app v5 从零搭建(使用 npm run eject)

前言: 好久没用 create-react-app做项目了,这次为了个h5项目,就几个页面,决定自己搭建一个(ps:mmp 好久没用,搭建的时候遇到一堆问题)。 我之前都是使用 umi 。后台管理系统的项目 使用 antd-…

PY32F003F18之RTC

一、RTC振荡器 PY32F003F18实时时钟的振荡器是内部RC振荡器,频率为32.768KHz。它也可以使用HSE时钟,不建议使用。HAL库提到LSE振荡器,但PY32F003F18实际上没有这个振荡器。 缺点:CPU掉电后,需要重新配置RTC&#xff…

保姆级 -- Zookeeper超详解

1. Zookeeper 是什么(了解) Zookeeper 是一个 分布式协调服务 的开源框架, 主要用来解决分布式集群中应用系统的一致性问题, 例如怎样避免同时操作同一数据造成脏读的问题. ZooKeeper 本质上是 一个分布式的小文件存储系统 . 提供基于类似于文件系统的目录树方式的数据存储, …

第二十届北京消防展即将开启,汉威科技即将精彩亮相

10月10日~13日,第二十届中国国际消防设备技术交流展览会,将在北京市顺义区中国国际展览中心新馆隆重举行。该展会由中国消防协会举办,是世界三大消防品牌展会之一,本届主题为“助力产业发展,服务消防救援”。届时将有4…

【Java 进阶篇】JDBC(Java Database Connectivity)详解

JDBC(Java Database Connectivity)是 Java 中用于连接和操作数据库的标准 API。它允许 Java 应用程序与不同类型的数据库进行交互,执行查询、插入、更新和删除等操作。本文将详细介绍 JDBC 的各个类及其用法,以帮助您更好地理解和…

【C语言经典100例题-66】(用指针解决)输入3个数a,b,c,按大小顺序输出。

代码&#xff1a; #include<stdio.h> #define _CRT_SECURE_NO_WARNINGS 1//VS编译器使用scanf函数时会报错&#xff0c;所以添加宏定义 swap(p1, p2) int* p1, * p2; {int p;p *p1;*p1 *p2;*p2 p; } int main() {int n1, n2, n3;int* pointer1, * pointer2, * point…

力扣 -- 416. 分割等和子集(01背包问题)

解题步骤&#xff1a; 参考代码&#xff1a; 未优化代码&#xff1a; class Solution { public:bool canPartition(vector<int>& nums) {int nnums.size();int sum0;for(const auto& e:nums){sume;}if(sum%21){return false;}int aimsum/2;//多开一行&#xff…

Linux系统编程基础:进程控制

文章目录 一.子进程的创建操作系统内核视角下的父子进程存在形式验证子进程对父进程数据的写时拷贝 二.进程等待进程非阻塞等待示例: 三.进程替换内核视角下的进程替换过程:综合利用进程控制系统接口实现简单的shell进程 进程控制主要分为三个方面,分别是:子进程的创建,进程等待…

前端两年半,CSDN创作一周年

文章目录 一、机缘巧合1.1、起因1.2、万事开头难1.3、 何以坚持&#xff1f; 二、收获三、日常四、憧憬 五、总结 一、机缘巧合 1.1、起因 最开始接触CSDN&#xff0c;还是因为同专业的同学&#xff0c;将计算机实验课的实验题&#xff0c;记录总结并发在了专业群里。后来正式…

几个推荐程序员养成的好习惯

本文框架 前言case1 不想当然case2 不为了解决问题而解决问题case3 不留问题死角case4 重视测试环节 前言 中秋国庆双节至&#xff0c;旅行or回乡探亲基本是大家的选择&#xff0c;看看风景或陪陪家人确实是个难得的机会。不过我的这次假期选择了闭关&#xff0c;不探亲&#…

【Python基础】常用模块学习:sys|os|pytest

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…

Python|OpenCV-如何给目标图像添加边框(7)

前言 本文是该专栏的第7篇,后面将持续分享OpenCV计算机视觉的干货知识,记得关注。 在使用opencv处理图像的时候,会不可避免的对图像的一些具体区域进行一些操作。比如说,想要给目标图像创建一个围绕图像的边框。简单的来说,就是在图片的周围再填充一个粗线框。具体效果,…

快速开发微信小程序之一登录认证

一、背景 记得11、12年的时候大家一窝蜂的开始做客户端Android、IOS开发&#xff0c;我是14年才开始做Andoird开发&#xff0c;干了两年多&#xff0c;然后18年左右微信小程序火了&#xff0c;我也做了两个小程序&#xff0c;一个是将原有牛奶公众号的功能迁移到小程序&#x…

centos7卸载docker

菜鸟教程-常见命令&#xff1a;https://www.runoob.com/docker/docker-command-manual.html 1. 准备工作&#xff1a; 1.1 杀死docker有关的容器&#xff1a; docker kill $(docker ps -a -q)1.2 删除所有docker容器&#xff1a; docker rm $(docker ps -a -q)1.3 删除所有d…

简单走近ChatGPT

目录 一、ChatGPT整体背景认知 &#xff08;一&#xff09;ChatGPT引起关注的原因 &#xff08;二&#xff09;与其他公司的竞争情况 二、NLP学习范式的发展 &#xff08;一&#xff09;规则和机器学习时期 &#xff08;二&#xff09;基于神经网络的监督学习时期 &…

房产政策松绑,VR看房助力市场回春

近日房贷利率、房产限购开始松绑&#xff0c;房地产市场逐渐被激活&#xff0c;房产行业的线上服务能力&#xff0c;也愈发的受到了重视。随着房贷利率、首付比例变化的消息逐渐推出&#xff0c;部分用户开始入手房产市场&#xff0c;因此房产行业的线上服务也需要不断升级&…

leetCode 122.买卖股票的最佳时机 II 贪心算法

122. 买卖股票的最佳时机 II - 力扣&#xff08;LeetCode&#xff09; 给你一个整数数组 prices &#xff0c;其中 prices[i] 表示某支股票第 i 天的价格。 在每一天&#xff0c;你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买&…

gitlab配置webhook限制提交注释

一、打开gitlab相关配置项 vim /etc/gitlab/gitlab.rb gitlab_shell[custom_hooks_dir] "/etc/gitlab/custom_hooks" 二、创建相关文件夹 mkdir -p /etc/gitlab/custom_hooks mkdir -p /etc/gitlab/custom_hooks/post-receive.d mkdir -p /etc/gitlab/custom_h…

Python教程:PyQt5需要学习,哪些知识点??

PyQt5是基于图形程序框架Qt5的Python语言实现&#xff0c;由一组Python模块构成。它可用于Python 2和3&#xff0c;拥有超过620个类和6000个函数和方法。这是一个跨平台的工具包&#xff0c;可以运行在所有主要的操作系统&#xff0c;包括UNIX、Windows、Mac OS、Linux等。 #我…

vue3学习实战

vue3新增变化 diff算法变化 vue3的diff算法没有vue2的头尾、尾头之间的diff&#xff0c;对diff算法进行了优化&#xff0c;最长递归子序列。 ref VS reactive ref 支持所有的类型&#xff0c;reactive 支持引用类型&#xff0c;array object Map Setref取值、赋值&#xff…