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

本文涉及的产品
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
云数据库 RDS SQL Server,基础系列 2核4GB
简介: 设计模式之抽象工厂模式--创建一系列相关对象的艺术(简单工厂、工厂方法、到抽象工厂的进化过程,类图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 {
    //新增用户
    @Override
    public 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 {
    @Override
    public 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和SQLServer
        User 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和SQLServer
        User user = new User();
        Department department = new Department();
        //实例化数据库交给DataAccess
        IUser 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创建相应的部件。

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

总结

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

相关实践学习
使用SQL语句管理索引
本次实验主要介绍如何在RDS-SQLServer数据库中,使用SQL语句管理索引。
SQL Server on Linux入门教程
SQL Server数据库一直只提供Windows下的版本。2016年微软宣布推出可运行在Linux系统下的SQL Server数据库,该版本目前还是早期预览版本。本课程主要介绍SQLServer On Linux的基本知识。 相关的阿里云产品:云数据库RDS SQL Server版 RDS SQL Server不仅拥有高可用架构和任意时间点的数据恢复功能,强力支撑各种企业应用,同时也包含了微软的License费用,减少额外支出。 了解产品详情: https://www.aliyun.com/product/rds/sqlserver
相关文章
|
1月前
|
设计模式 开发者 Python
Python编程中的设计模式:工厂方法模式###
本文深入浅出地探讨了Python编程中的一种重要设计模式——工厂方法模式。通过具体案例和代码示例,我们将了解工厂方法模式的定义、应用场景、实现步骤以及其优势与潜在缺点。无论你是Python新手还是有经验的开发者,都能从本文中获得关于如何在实际项目中有效应用工厂方法模式的启发。 ###
|
3月前
|
设计模式
设计模式-工厂模式 Factory Pattern(简单工厂、工厂方法、抽象工厂)
这篇文章详细解释了工厂模式,包括简单工厂、工厂方法和抽象工厂三种类型。每种模式都通过代码示例展示了其应用场景和实现方法,并比较了它们之间的差异。简单工厂模式通过一个工厂类来创建各种产品;工厂方法模式通过定义一个创建对象的接口,由子类决定实例化哪个类;抽象工厂模式提供一个创建相关或依赖对象家族的接口,而不需要明确指定具体类。
设计模式-工厂模式 Factory Pattern(简单工厂、工厂方法、抽象工厂)
|
3月前
|
设计模式 Java
Java设计模式-抽象工厂模式(5)
Java设计模式-抽象工厂模式(5)
|
3月前
|
设计模式 Java
Java设计模式-工厂方法模式(4)
Java设计模式-工厂方法模式(4)
|
4月前
|
设计模式 Java
Java 设计模式之谜:工厂模式与抽象工厂模式究竟隐藏着怎样的神奇力量?
【8月更文挑战第30天】在Java编程中,设计模式为常见问题提供了高效解决方案。工厂模式与抽象工厂模式是常用的对象创建型设计模式,能显著提升代码的灵活性、可维护性和可扩展性。工厂模式通过定义创建对象的接口让子类决定实例化哪个类;而抽象工厂模式则进一步提供了一个创建一系列相关或相互依赖对象的接口,无需指定具体类。这种方式使得系统更易于扩展和维护。
43 1
|
4月前
|
设计模式 XML 存储
【三】设计模式~~~创建型模式~~~抽象工厂模式(Java)
文章详细介绍了抽象工厂模式,这是一种创建型设计模式,用于提供一个接口以创建一系列相关或相互依赖的对象,而不指定它们具体的类。通过代码示例和结构图,文章展示了抽象工厂模式的动机、定义、结构、优点、缺点以及适用场景,并探讨了如何通过配置文件和反射机制实现工厂的动态创建。
【三】设计模式~~~创建型模式~~~抽象工厂模式(Java)
|
4月前
|
设计模式 XML 存储
【二】设计模式~~~创建型模式~~~工厂方法模式(Java)
文章详细介绍了工厂方法模式(Factory Method Pattern),这是一种创建型设计模式,用于将对象的创建过程委托给多个工厂子类中的某一个,以实现对象创建的封装和扩展性。文章通过日志记录器的实例,展示了工厂方法模式的结构、角色、时序图、代码实现、优点、缺点以及适用环境,并探讨了如何通过配置文件和Java反射机制实现工厂的动态创建。
【二】设计模式~~~创建型模式~~~工厂方法模式(Java)
|
28天前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
|
3月前
|
设计模式 数据库连接 PHP
PHP中的设计模式:提升代码的可维护性与扩展性在软件开发过程中,设计模式是开发者们经常用到的工具之一。它们提供了经过验证的解决方案,可以帮助我们解决常见的软件设计问题。本文将介绍PHP中常用的设计模式,以及如何利用这些模式来提高代码的可维护性和扩展性。我们将从基础的设计模式入手,逐步深入到更复杂的应用场景。通过实际案例分析,读者可以更好地理解如何在PHP开发中应用这些设计模式,从而写出更加高效、灵活和易于维护的代码。
本文探讨了PHP中常用的设计模式及其在实际项目中的应用。内容涵盖设计模式的基本概念、分类和具体使用场景,重点介绍了单例模式、工厂模式和观察者模式等常见模式。通过具体的代码示例,展示了如何在PHP项目中有效利用设计模式来提升代码的可维护性和扩展性。文章还讨论了设计模式的选择原则和注意事项,帮助开发者在不同情境下做出最佳决策。
|
24天前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
37 1