【设计模式】抽象工厂模式

news2024/11/24 16:21:11

【设计模式】抽象工厂模式

参考资料:

Java设计模式 - 抽象工厂模式

重学 Java 设计模式:实战抽象工厂模式

文章目录

  • 【设计模式】抽象工厂模式
    • 一、抽象工厂模式介绍
      • 1.1、什么是工厂方法模式
      • 1.2、角色概述
    • 二、案例场景模拟
      • 2.1、背景一:Redis的集群扩容
        • 2.1.1、模拟单机服务 RedisUtils
        • 2.1.2、模拟集群 EGM
        • 2.1.3、模拟集群 IIR
        • 2.1.4、if/else实现需求
        • 2.1.5、定义适配接口
        • 2.1.6、实现集群使用服务
        • 2.1.7、定义抽象工程代理类和实现
        • 2.1.8、测试验证
      • 2.2、背景二:咖啡店点餐系统
        • 2.2.1、抽象产品及具体产品
        • 2.2.2、抽象工厂 及具体工厂
        • 2.2.3、测试
    • 三、总结

一、抽象工厂模式介绍

1.1、什么是工厂方法模式

抽象工厂模式与工厂方法模式虽然主要意图都是为了解决,接口选择问题。但在实现上,抽象工厂是一个中心工厂,创建其他工厂的模式。

抽象工厂模式,图片来自 refactoringguru.cn

可能在平常的业务开发中很少关注这样的设计模式或者类似的代码结构,但是这种场景确一直在我们身边,例如;

  1. 不同系统内的回车换行

    1. Unix系统里,每行结尾只有 <换行>,即 \n
    2. Windows系统里面,每行结尾是 <换行><回车>,即 \n\r
    3. Mac系统里,每行结尾是 <回车>
  2. IDEA 开发工具的差异展示(Win\Mac)

    不同系统下,IDEA 开发工具的展示差异点

除了这样显而易见的例子外,我们的业务开发中时常也会遇到类似的问题,需要兼容做处理。但大部分经验不足的开发人员,常常直接通过添加ifelse方式进行处理了。

1.2、角色概述

抽象工厂模式中存在四种角色,分别是抽象工厂角色,具体工厂角色,抽象产品角色,具体产品角色。

  • 抽象工厂:提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。
  • 具体工厂:主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
  • 抽象产品:定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
  • 具体产品:实现了抽象产品角色所定义的接口,由具体工厂来创建,它 同具体工厂之间是多对一的关系。

二、案例场景模拟

2.1、背景一:Redis的集群扩容

image-20230504021123803

随着业务超过预期的快速发展,系统的负载能力也要随着跟上。原有的单机 Redis 已经满足不了系统需求。这时候就需要更换为更为健壮的Redis集群服务,虽然需要修改但是不能影响目前系统的运行,还要平滑过渡过去。

随着这次的升级,可以预见的问题会有;

  1. 很多服务用到了Redis需要一起升级到集群。
  2. 需要兼容集群A和集群B,便于后续的灾备。
  3. 两套集群提供的接口和方法各有差异,需要做适配。
  4. 不能影响到目前正常运行的系统。

2.1.1、模拟单机服务 RedisUtils

image-20230504021220720

  • 模拟Redis功能,也就是假定目前所有的系统都在使用的服务
  • 类和方法名次都固定写死到各个业务系统中,改动略微麻烦

2.1.2、模拟集群 EGM

image-20230504021255330

  • 模拟一个集群服务,但是方法名与各业务系统中使用的方法名不同。有点像你mac,我用win。做一样的事,但有不同的操作。

2.1.3、模拟集群 IIR

image-20230504021331385

  • 这是另外一套集群服务,有时候在企业开发中就很有可能出现两套服务,这里我们也是为了做模拟案例,所以添加两套实现同样功能的不同服务,来学习抽象工厂模式。

综上可以看到,我们目前的系统中已经在大量的使用redis服务,但是因为系统不能满足业务的快速发展,因此需要迁移到集群服务中。而这时有两套集群服务需要兼容使用,又要满足所有的业务系统改造的同时不影响线上使用。

2.1.4、if/else实现需求

public class CacheServiceImpl implements CacheService {

    private RedisUtils redisUtils = new RedisUtils();

    private EGM egm = new EGM();

    private IIR iir = new IIR();

    public String get(String key, int redisType) {

        if (1 == redisType) {
            return egm.gain(key);
        }

        if (2 == redisType) {
            return iir.get(key);
        }

        return redisUtils.get(key);
    }

    public void set(String key, String value, int redisType) {

        if (1 == redisType) {
            egm.set(key, value);
            return;
        }

        if (2 == redisType) {
            iir.set(key, value);
            return;
        }

        redisUtils.set(key, value);
    }

}

抽象工厂模型结构

image-20230504021559538

  • 工程中涉及的部分核心功能代码,如下;
    • ICacheAdapter,定义了适配接口,分别包装两个集群中差异化的接口名称。EGMCacheAdapterIIRCacheAdapter
    • JDKProxyJDKInvocationHandler,是代理类的定义和实现,这部分也就是抽象工厂的另外一种实现方式。通过这样的方式可以很好的把原有操作Redis的方法进行代理操作,通过控制不同的入参对象,控制缓存的使用。

2.1.5、定义适配接口

public interface ICacheAdapter {

    String get(String key);

    void set(String key, String value);

    void set(String key, String value, long timeout, TimeUnit timeUnit);

    void del(String key);

}
  • 这个类的主要作用是让所有集群的提供方,能在统一的方法名称下进行操作。也方面后续的拓展。

2.1.6、实现集群使用服务

EGMCacheAdapter

public class EGMCacheAdapter implements ICacheAdapter {

    private EGM egm = new EGM();

    public String get(String key) {
        return egm.gain(key);
    }

    public void set(String key, String value) {
        egm.set(key, value);
    }

    public void set(String key, String value, long timeout, TimeUnit timeUnit) {
        egm.setEx(key, value, timeout, timeUnit);
    }

    public void del(String key) {
        egm.delete(key);
    }
}

IIRCacheAdapter

public class IIRCacheAdapter implements ICacheAdapter {

    private IIR iir = new IIR();

    public String get(String key) {
        return iir.get(key);
    }

    public void set(String key, String value) {
        iir.set(key, value);
    }

    public void set(String key, String value, long timeout, TimeUnit timeUnit) {
        iir.setExpire(key, value, timeout, timeUnit);
    }

    public void del(String key) {
        iir.del(key);
    }

} 
  • 以上两个实现都非常容易,在统一方法名下进行包装。

2.1.7、定义抽象工程代理类和实现

JDKProxy

public static <T> T getProxy(Class<T> interfaceClass, ICacheAdapter cacheAdapter) throws Exception {
    InvocationHandler handler = new JDKInvocationHandler(cacheAdapter);
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    Class<?>[] classes = interfaceClass.getInterfaces();
    return (T) Proxy.newProxyInstance(classLoader, new Class[]{classes[0]}, handler);
}
  • 这里主要的作用就是完成代理类,同时对于使用哪个集群有外部通过入参进行传递。

JDKInvocationHandler

public class JDKInvocationHandler implements InvocationHandler {

    private ICacheAdapter cacheAdapter;

    public JDKInvocationHandler(ICacheAdapter cacheAdapter) {
        this.cacheAdapter = cacheAdapter;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return ICacheAdapter.class.getMethod(method.getName(), ClassLoaderUtils.getClazzByArgs(args)).invoke(cacheAdapter, args);
    }

}   
  • 在代理类的实现中其实也非常简单,通过穿透进来的集群服务进行方法操作。
  • 另外在invoke中通过使用获取方法名称反射方式,调用对应的方法功能,也就简化了整体的使用。
  • 到这我们就已经将整体的功能实现完成了,关于抽象工厂这部分也可以使用非代理的方式进行实现。

2.1.8、测试验证

编写测试类:

@Test
public void test_CacheService() throws Exception {
    CacheService proxy_EGM = JDKProxy.getProxy(CacheServiceImpl.class, new EGMCacheAdapter());
    proxy_EGM.set("user_name_01","yby");
    String val01 = proxy_EGM.get("user_name_01");
    System.out.println(val01);
    
    CacheService proxy_IIR = JDKProxy.getProxy(CacheServiceImpl.class, new IIRCacheAdapter());
    proxy_IIR.set("user_name_01","yby");
    String val02 = proxy_IIR.get("user_name_01");
    System.out.println(val02);
}
  • 在测试的代码中通过传入不同的集群类型,就可以调用不同的集群下的方法。JDKProxy.getProxy(CacheServiceImpl.class, new EGMCacheAdapter());
  • 如果后续有扩展的需求,也可以按照这样的类型方式进行补充,同时对于改造上来说并没有改动原来的方法,降低了修改成本。

结果:

23:07:06.953 [main] INFO  org.itstack.demo.design.matter.EGM - EGM写入数据 key:user_name_01 val:yby
23:07:06.956 [main] INFO  org.itstack.demo.design.matter.EGM - EGM获取数据 key:user_name_01
测试结果:yby
23:07:06.957 [main] INFO  org.itstack.demo.design.matter.IIR - IIR写入数据 key:user_name_01 val:yby
23:07:06.957 [main] INFO  org.itstack.demo.design.matter.IIR - IIR获取数据 key:user_name_01
测试结果:yby

Process finished with exit code 0    
  • 运行结果正常,这样的代码满足了这次拓展的需求,同时你的技术能力也给老板留下了深刻的印象。
  • 研发自我能力的提升远不是外接的压力就是编写一坨坨代码的接口,如果你已经熟练了很多技能,那么可以在即使紧急的情况下,也能做出完善的方案。

2.2、背景二:咖啡店点餐系统

设计一个咖啡类(Coffee),并定义其两个子类(美式咖啡【AmericanCoffee】和拿铁咖啡【LatteCoffee】);再设计一个咖啡店类(CoffeeStore),咖啡店具有点咖啡的功能。

现咖啡店业务发生改变,不仅要生产咖啡还要生产甜点,如提拉米苏、抹茶慕斯等,要是按照工厂方法模式,需要定义提拉米苏类、抹茶慕斯类、提拉米苏工厂、抹茶慕斯工厂、甜点工厂类,很容易发生类爆炸情况。其中拿铁咖啡、美式咖啡是一个产品等级,都是咖啡;提拉米苏、抹茶慕斯也是一个产品等级;拿铁咖啡和提拉米苏是同一产品族(也就是都属于意大利风味),美式咖啡和抹茶慕斯是同一产品族(也就是都属于美式风味)。

2.2.1、抽象产品及具体产品

第一种产品:

Coffee(第一种抽象产品类)、AmericanCoffee和LatteCoffee (具体产品类)

public abstract class Coffee {
    public abstract void addMilk();
    public abstract void addSugar();
    public abstract String getName();
}

public class AmericanCoffee extends Coffee {
    @Override
    public void addMilk() {  System.out.println("给咖啡加奶");  }

    @Override
    public void addSugar() {  System.out.println("给咖啡加糖"); }

    @Override
    public String getName() {  return "美式咖啡"; }
}

public class LatteCoffee extends Coffee {
    @Override
    public void addMilk() {    System.out.println("给咖啡加奶"); }

    @Override
    public void addSugar() {  System.out.println("给咖啡加糖"); }

    @Override
    public String getName() {  return "拿铁咖啡"; }
}

第二种产品:

Dessert (第二种抽象产品 甜点) MatchaMousse、Tiramisu(具体产品类)

public abstract class Dessert {
    public abstract void show();
}

public class MatchaMousse extends Dessert{
    @Override
    public void show() {  System.out.println("抹茶慕斯"); }
}

public class Tiramisu extends Dessert{
    @Override
    public void show() {    System.out.println("提拉米苏"); }
}

2.2.2、抽象工厂 及具体工厂

DessertFactory (抽象工厂) AmericanDessertFactory 和(具体工厂)

public interface DessertFactory {

    Coffee createCoffee();

    Dessert createDessert();
}

public class AmericanDessertFactory implements DessertFactory {

    @Override
    public Coffee createCoffee() {   return new AmericanCoffee(); }

    @Override
    public Dessert createDessert() {  return new MatchaMousse(); }
}

public class ItalyDessertFactory implements DessertFactory {

    @Override
    public Coffee createCoffee() {   return new LatteCoffee(); }

    @Override
    public Dessert createDessert() {    return new Tiramisu(); }
}

2.2.3、测试

public class Client {
    public static void main(String[] args) {
         // 想次美式东西
	    // AmericanDessertFactory factory = new AmericanDessertFactory();
        // 想换成意大利风味,仅仅只需要换一个工厂类 其他的代码无需改变
        ItalyDessertFactory factory= new ItalyDessertFactory();
        Coffee coffee = factory.createCoffee();
        Dessert dessert = factory.createDessert();
        System.out.println(coffee.getName());
        dessert.show();
    }
}

如果要加同一个产品族的话,只需要再加一个对应的工厂类即可,不需要修改其他的类。

三、总结

  • 抽象工厂模式,所要解决的问题就是在一个产品族,存在多个不同类型的产品(Redis集群、操作系统)情况下,接口选择的问题。而这种场景在业务开发中也是非常多见的,只不过可能有时候没有将它们抽象化出来。
  • 你的代码只是被ifelse埋上了!当你知道什么场景下何时可以被抽象工程优化代码,那么你的代码层级结构以及满足业务需求上,都可以得到很好的完成功能实现并提升扩展性和优雅度。
  • 那么这个设计模式满足了;单一职责、开闭原则、解耦等优点,但如果说随着业务的不断拓展,可能会造成类实现上的复杂度。但也可以说算不上缺点,因为可以随着其他设计方式的引入和代理类以及自动生成加载的方式降低此项缺点。

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

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

相关文章

Camtasia2023最好用的电脑屏幕录制软件

Camtasia2023是市场上最好的录像机和屏幕录制软件之一。强大的软件视频编辑程序的Camtasia 适用于Windows和iOS。 它支持多种流行的媒体格式&#xff0c;并对您创建的视频提供令人印象深刻的控制范围。3000多万专业人士在全球范围内使用Camtasia展示产品&#xff0c;教授课程&a…

.netCHARTING 10.5 dotnetcharting Crack

.net图表 10.5 为柱形图和条形图添加拐角半径控件。 5月 05&#xff0c; 2023 - 16&#xff1a;18新版本 特征 直角或直线组织连接线 - 默认情况下&#xff0c;通过以直角绘制组织连接线来增强组织连接线的显示方式。您可以使用直线选项更改此默认值&#xff0c;并直接在点…

mssql修改排序规则

修改排序规则 在 Microsoft SQL Server 中&#xff0c;可以通过以下步骤来修改排序规则&#xff1a; 打开 SQL Server Management Studio&#xff08;SSMS&#xff09;&#xff0c;连接到 SQL Server 数据库实例。在“对象资源管理器”窗格中&#xff0c;右键单击数据库&…

VS+Qt+C++医院排队叫号系统

程序示例精选 VSQtC医院排队叫号系统 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对<<VSQtC医院排队叫号系统>>编写代码&#xff0c;带用户登录&#xff0c;管理员登录&#xff…

开源进展 | WeIdentity v3.1.0 发布,新增数据库部署和使用模式

作为连接实体对象&#xff08;人或物&#xff09;的现实身份与链上身份的可信映射&#xff0c;实现实体对象之间安全可信的数据授权与交换&#xff0c;分布式身份技术解决方案在推动区块链应用繁荣及可信数据流转的过程中扮演着重要角色。 WeIdentity是由微众银行自主研发并完全…

荟萃金融科技成果,展现数字金融力量丨通付盾受邀出席中国国际金融展

2023年4月27日&#xff0c;中国国际金融展在北京顺利落下帷幕。本届金融展以“荟萃金融科技成果&#xff0c;展现数字金融力量&#xff0c;谱写金融服务中国式现代化新篇章”为主题&#xff0c;由中国金融电子化集团有限公司和北京市石景山区政府联合主办。来自国内外的众多金融…

教室资源管理系统【纯控制台】(Java课设)

系统类型 纯控制台类型&#xff08;没有用到数据库&#xff09; 使用范围 适合作为Java课设&#xff01;&#xff01;&#xff01; 部署环境 jdk1.8Idea或eclipse 运行效果 本系统源码地址&#xff1a;https://download.csdn.net/download/qq_50954361/87753360 更多系统…

helm部署nacos

1.去helm仓库拉取nacos包 https://artifacthub.io/packages/helm/kubegemsapp/nacos?modalinstall helm repo add kubegemsapp https://charts.kubegems.io/kubegemsapp helm pull kubegemsapp/nacos tar -zxvf nacos-0.1.5.tgz mkdir -p nacos/ci/test2.修改chart配置文件 …

如何根据期刊缩写查找期刊?

英文论文写作中&#xff0c;经常会插入参考文献。参考文献中的期刊名称&#xff0c;时常需要使用缩写。或者是手头有期刊缩写后的名称&#xff0c;但是有时候&#xff0c;查了半天也查不到期刊期刊全称&#xff0c;费时费力让人崩溃。今天就给各位学者老师总结一些查询期刊缩写…

如何在 Python 开发环境中调用 ChatGPT 模型?

本文将演示在本地的 python 项目中调用 ChatGPT 模型。 写在前面第一步&#xff1a;获取 API Key第二步&#xff1a;安装 OpenAI 第三方库第三步&#xff1a;Python 开发环境中调用 ChatGPT 模型 写在前面 作为一名程序员&#xff0c;在开发过程当中时常需要使用 ChatGPT 来完…

项目创建第一天 搭建前端环境

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、环境是什么&#xff1f;二、使用步骤1.前台搭建方式1.创建项目2.目录结构3. 安装elementui4. 创建路由5.使用axios6.bug记录6.1出现跨域问题6.2 解决方式6.…

硬盘数据突然消失怎么回事?硬盘数据突然消失怎么找回

硬盘上的数据对每个人都至关重要&#xff0c;它可能是我们的珍贵回忆&#xff0c;多年学习的总结&#xff0c;或者一些不可告人的秘密。而硬盘中的数据可能会在不知情的情况下消失或被删除&#xff0c;这种情况对我们来说十分痛苦和困扰。然而&#xff0c;我们不必担心&#xf…

SLAM论文速递:SLAM—(2021)Amos-SLAM:一种基于视觉和几何的抗动态双阶段SLAM方法—5.65(1)

论文信息 题目&#xff1a; Visual SLAM in dynamic environments based on object detection 基于目标检测的动态环境下的视觉SLAM论文地址&#xff1a; https://www.sciencedirect.com/science/article/pii/S2214914720304402发表期刊&#xff1a; Defence Technology,&…

【python】pytorch包:深度学习(序章)

今日听闻师姐说pytorch实现深度学习要比keras更好用一些&#xff0c;特此记录 Part 0. 机器学习 与 深度学习 的联系与区别 参考B站视频链接 联系 深度学习是机器学习的分支&#xff0c;人工神经网络为基础&#xff0c;对数据的特征进行学习的方法 区别 特征抽取 机器学…

TensorRT入门实战,TensorRT Plugin介绍以及TensorRT INT8加速

文章目录 一、TensorRT介绍,工作流程和优化策略TensorRT是什么TensorRT的工作流程TRT优化策略介绍 二、TensorRT的组成和基本使用流程三、TensorRT的基本使用流程四、TensorRT Demo代码 : SampleMNISTCaffe Parser方式构建 五. TensorRT Plugin基本概念工作流程API介绍Dynamic …

复旦微的 JFM7K325T 国产化设计资料(PCIE711)

板卡概述 PCIE711 是一款基于 PCIE 总线架构的高性能数据预处理 FMC载板&#xff0c;板卡采用复旦微的 JFM7K325T FPGA 作为实时处理器&#xff0c;实现 各个接口之间的互联。该板卡可以实现 100%国产化。 板卡具有 1 个 FMC&#xff08;HPC&#xff09;接口&#xff0c;1 路…

字符设备注册与注销

1、对于字符设备驱动而言&#xff0c;当驱动模块加载成功以后需要注册字符设备&#xff0c;同样&#xff0c;卸载驱动模 块的时候也需要注销掉字符设备。字符设备的注册和注销函数原型如下所示 static inline int register_chrdev(unsigned int major, const char *name,const…

虹科方案 | HK-Edgility系统随时随地保护您的远程工作

通过上次的文章&#xff0c;我们了解到虹科HK-Edgility软件系统《将云计算扩展到边缘》的解决方案。今天的文章&#xff0c;我们将带您了解虹科系统在远程工作的方案简介。 一、时代背景 在当今新的数字化工作空间中&#xff0c;员工需要从家中、远程办公室和旅途中访问公司业务…

作为团队管理者,如何获得团队成员的信任和认可?

作为团队管理者&#xff0c;获得团队成员的信任和认可是非常重要的。只有当团队成员信任你并认可你的领导能力&#xff0c;才能更好地协同工作&#xff0c;提高工作效率和完成团队目标。那么&#xff0c;如何才能获得团队成员的信任和认可呢&#xff1f;以下是一些实用的建议。…

全球首个天基蜂窝语音通话,打通了,这个重大新闻非常值得关注

4月25日&#xff0c;美国卫星通信初创公司——AST SpaceMobile&#xff0c;宣布打通了全球首个天基蜂窝语音通话。 对于卫星通信乃至整个通信行业来说&#xff0c;这是一个重大新闻&#xff0c;非常值得关注。 去年&#xff0c;我们还只是实现了手机和卫星之间的双向短消息通信…