前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【设计模式 11】抽象工厂模式

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

作者头像
JuneBao
发布2022-10-26 15:00:07
2890
发布2022-10-26 15:00:07
举报
文章被收录于专栏:JuneBao

抽象工厂模式( Abstract Factory),提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

抽象工厂模式

抽象工厂模式( Abstract Factory),提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。 参考:

工厂方法中每一个工厂只提供一种产品,为此,抽象工厂将工厂方法中的所用工厂向上抽象出一个抽象工厂,将所有产品向上抽象出抽象产品,这样抽象工厂模式中的角色就包括:

  • AbstractFactory:抽象工厂
  • ConcreteFactory:具体工厂
  • AbstractProduct:抽象产品
  • Product:具体产品

简单工厂,工厂方法,抽象工厂

对比三种工厂模式,简单工厂直接在一个静态工厂中返回产品实例,没有对产品或工厂做任何抽象,是最简单粗暴的工厂模式,但每次添加新的产品都要修改工厂类,违反开闭原则,后来工厂方法将工厂类向上抽象出一个“抽象工厂接口”,让每一个具体的工厂类只返回一种产品,这样添加新产品时只需要给“抽象工厂接口”添加新的实现类即可,但工厂方法的问题在于每个具体工厂只能生产一种产品,因此,在抽象工厂中,把一系列产品进一步分装为抽象产品,具体工厂就可以产生产生多个产品了。

当抽象工厂模式中每一个具体工厂类只创建一个产品对象,也就是只存在一个产品等级结构时,抽象工厂模式退化成工厂方法模式;当工厂方法模式中抽象工厂与具体工厂合并,提供一个统一的工厂来创建产品对象,并将创建对象的工厂方法设计为静态方法时,工厂方法模式退化成简单工厂模式。

实现方法

模拟在 MySQL 或 SQLServer 数据库中对用户信息(User)和部门信息(Department)的查询(select)和插入(insert)功能。(假设MySQL 和 SQLServer 中的查询和插入语句不同),同时希望客户端对数据库的变更不敏感。

要实现的功能是在 MySQL 或 SQL Server 中实现对 User 和 Department 的插入和查询,把 SQL Server 的插入和查询看作 MySQL 插入查询的变体,那么就可以把系统功能抽象为对 User 的操作和对 Department 的操作,分别建立两个接口表示:

代码语言:javascript
复制
public interface IUserDao {
    void insert(User user);
    void select(String name);
}
代码语言:javascript
复制
public interface IDepartDao {
    Department select(String name);
    void insert(Department department);
}

由于 MySQL 和 SQLServer 中的查询和插入语句不同, 所以他们分别实现这两个接口,表示两种数据库具体的操作细节。

代码语言:javascript
复制
public class MySQLUserDao implements IUserDao {
    @Override
    public void insert(User user) {
        System.out.println("使用MySQL的插入方法把User插入表中");
    }

    @Override
    public void select(String name) {
        System.out.println("使用MySQL中的查询方法查询name");
    }
}

public class MySQLDepartDao implements IDepartDao {
    // ......
}

public class SQLServerUserDao implements IUserDao {
    // ......
}

public class SQLServerDepartDao implements IDepartDao {
    // ......
}

然后就需要一个工厂来产生具体的用来操作数据的对象,如果采用工厂方法,就要建立四个工厂类,产品更多时,显然不合适,但其实MySQLUserDaoMySQLDepartDao可以看作是一类对象(MySQL处理数据的对象),SQLServerUserDaoSQLServerDepartDao可以看作是一类对象(SQL Server处理数据的对象),这样只需要建立两个工厂类就可以了,这两个工厂类又可以向上抽象(他们都是返回一个IUserDao对象, 一个IDepartDao)对象,所以最终为:

代码语言:javascript
复制
public interface IFactor {
    IUserDao getUserDao();
    IDepartDao getDepartDao();
}
代码语言:javascript
复制
public class MySQLFactory implements IFactor {
    @Override
    public IDepartDao getDepartDao() {
        return new MySQLDepartDao();
    }

    @Override
    public IUserDao getUserDao() {
        return new MySQLUserDao();
    }
}
代码语言:javascript
复制
public class SQLServerFactory implements IFactor {
    @Override
    public IDepartDao getDepartDao() {
        return new SQLServerDepartDao();
    }

    @Override
    public IUserDao getUserDao() {
        return new SQLServerUserDao();
    }
}

客户端代码:

代码语言:javascript
复制
public class Client {
    public static void main(String[] args) {
        IFactor factor = new MySQLFactory();
        IUserDao userDao = factor.getUserDao();
        IDepartDao departDao = factor.getDepartDao();
        userDao.insert(new User("1", "张三"));
        userDao.select("张三");
    }
}
实现方法总结
  1. 判断要实现的多个功能是否存在 “变体” 的情况,找出抽象产品(对User 和 Department的操作)并定义抽象产品接口(IUserDao, IDepartDao)
  2. 实现每个变体的具体产品
  3. 声明抽象工厂接口, 并且在接口中为所有抽象产品提供一组构建方法。
  4. 实现具体工厂

反射改进抽象工厂

如果抽象工厂中加入了新的产品,那首先必须改变抽象工厂接口,其次所有的具体工厂类也要跟着修改,为此可以选择放弃工厂方法的思想,改用简单工厂的思想。

代码语言:javascript
复制
public class DaoFactory {
    private static String db = "MySQL";
//    private static String db = "SQLServer";
    
    public static IUserDao getUserDao(){
        switch (db){
            case "MySQL":
                return new MySQLUserDao();
            case "SQLServer":
                return new SQLServerUserDao();
            default:
                return null;
        }
    }
    
    public static IDepartDao getDepartDao(){
        switch (db){
            case "MySQL":
                return new MySQLDepartDao();
            case "SQLServer":
                return new SQLServerDepartDao();
            default:
                return null;
        }
    }
}

放弃抽象工厂接口以及具体工厂类后,如果要添加新产品,只要修改DaoFactory一个类就可以了,并且由于在DaoFactory中定义了要使用的数据库的类型(变体),换数据库时,就只需要改变DaoFactory就可以了,进一步降低了与客户端的耦合度。但简单工厂“从工厂内看”不符合开闭原则,如果要新添加一个数据库(新添加一种变体)如Oricle, 使用工厂方法时只需要添加一个抽象工厂接口的实现类(满足开闭原则),现在需要修改DaoFactory(不符合开闭原则),为此可以使用反射。

代码语言:javascript
复制
package pers.junebao.abstract_factory.db;

public class DaoFactory {
    private static String db = "MySQL";
//    private static String db = "SQLServer";

    public static IUserDao getUserDao(){
        try {
            try {
                return (IUserDao) Class.forName("pers.junebao.abstract_factory.db.SQLServer" + db + "UserDao").newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
                return null;
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }

    public static IDepartDao getDepartDao(){
        try {
            try {
                return (IDepartDao) Class.forName("pers.junebao.abstract_factory.db." + db + "DepartDao").newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
                return null;
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }
}

GitHub | 完整代码

完整类名,选择的数据库名这些都可以放到配置文件中,进一步降低修改成本

使用场景和优缺点

适用场景:

  1. 客户端不应该关心产品具体的创建细节时(所有工厂方法适用)
  2. 系统中存在多个“变种”,并且同一时刻只使用其中一个。

优点:

  • 抽象工厂模式隔离了具体类的生成,使得客户并不需要知道什么被创建。由于这种隔离,更换一个具体工厂就变得相对容易。所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。另外,应用抽象工厂模式可以实现高内聚低耦合的设计目的,因此抽象工厂模式得到了广泛的应用。
  • 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。这对一些需要根据当前环境来决定其行为的软件系统来说,是一种非常实用的设计模式。
  • 增加新的具体工厂和产品族很方便,无须修改已有系统,符合“开闭原则”。

缺点:

  • 在添加新的产品对象时,难以扩展抽象工厂来生产新种类的产品,这是因为在抽象工厂角色中规定了所有可能被创建的产品集合,要支持新种类的产品就意味着要对该接口进行扩展,而这将涉及到对抽象工厂角色及其所有子类的修改,显然会带来较大的不便。
  • 开闭原则的倾斜性(增加新的工厂和产品族容易,增加新的产品等级结构麻烦)。
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-5-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 抽象工厂模式
    • 简单工厂,工厂方法,抽象工厂
      • 实现方法
        • 实现方法总结
      • 反射改进抽象工厂
        • 使用场景和优缺点
        相关产品与服务
        云数据库 MySQL
        腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档