ABAP设计模式之---“抽象工厂模式(Abstract Factory Pattern)”

1. 定义

提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

工厂类中提供了多个创建方法,分别负责相关类的实例化。

一个工厂类,对应的多个相关的生产类。

2. 解读

类比:

  • 抽象工厂模式,是工厂方法模式的一个扩展。还记得工厂方法模式的那个类比么 😉
  • 工厂方法模式中的工厂,小而美,每个工厂仅生产一种产品。这种方式,带来的一个问题就是,工厂类的数量会和生产类的数量一样多。在现实生活中,其实仅生产一种产品的工厂是少之又少的,大多数工厂其实都是生产某一品类的产品,不同的产品之间是相关的。
  • 生产面包的工厂,通常也会生产蛋糕、饼干等,具体到产品层级,面包产品线可以生产切片面包、法式面包、意式面包等,蛋糕生产线包括乳酪蛋糕、艺术蛋糕、婚礼蛋糕等,同样的,饼干生产线也可生成多种样式的饼干。在此例中,面包+蛋糕+饼干构成了此工厂的产品族
  • 抽象工厂模式,就是这种生产某一族产品的工厂。

解析:

  • 抽象工厂模式中的工厂类,包含多个创建方法,分别用于实例化相关的生产类(例如分别创建面包的实例、蛋糕的实例、饼干的实例)。也即,一个工厂类,对应的多个相关的生产类。
  • 当一个工厂等级结构,可以创建出分属于不同产品等级结构的一个产品族中的所有对象时,抽象工厂模式比工厂方法模式更为简单、更有效率。
  • 在抽象工厂中,新增加产品族很方便,此种情况下只需要新增对应的工厂即可;但新增新的产品等级结构很麻烦,因为此时要调整抽象工厂的接口(添加新的创建方法),同时要调整所有已生成的工厂类。
  • 因此,抽象工厂模式其实具有一种倾斜性的“开闭原则”,为新增产品族提供的拓展的方便性,但对于新增的产品结构并不能提供这种方便。这要求设计人员在设计之初,就要尽可能地考虑全面,尽量避免在完成抽象工厂的设计后,变化产品的等级结构,否则会导致系统出现较大的修改,为后续的维护工作带来麻烦。
  • 在抽象工厂模式中,也适用于反射机制的结合应用,用动态编程的方式可以解决客户端判断的问题。也即,使用哪个工厂的场景写到配置文件中,程序运行时通过读取配置文件,动态选择所需要的逻辑。

要点:

  1. 抽象生产类接口
  2. 抽象工厂类接口
  3. 工厂类是实例化生产类的唯一位置
  4. 工厂类负责某一组相关生产类的实例化

3. 举例

3.1 抽象工厂模式与工厂方法模式的对比

下图描述了当有两个不同的类时,使用工厂方法模式时,需要有对应的4个工厂类。

  • 产品工厂类A - 实现A产品类的实例化;
  • 产品工厂类B - 实现B产品类的实例化;
  • 用户工厂类A - 实现A用户类的实例化;
  • 用户工厂类B - 实现B用户类的实例化;
    在这里插入图片描述

如果,product类和User类是相互关联的,也即使用Product类时,一定也会使用User类,说明这两个类时属于同一个相关的场景,那么此时使用抽象工厂模式将会更为合适。可见下图,使用抽象工厂模式,将4个工厂减少到2个工厂。

  • 工厂类A - 实现A产品类和A类用户的实例化;
  • 工厂类B - 实现B产品类和B类用户的实例化;
    在这里插入图片描述

3.2 抽象工厂模式代码示例

在下面示例中,我们需要的操作是:

  1. 新增和查找用户信息
  2. 新增和查找部门信息
  3. 支持SQL Server DB和Oracle DB

建模的步骤:

  • 抽象User和Department接口
  • SQL Server和Oracle DB的访问操作类实现此接口
  • 用户和部门信息的操作是相关联的,使用抽象工厂模式,抽象工厂接口中创建方法分别返回User的操- 作实例和Department的操作实例
  • SQL和Oracle分别对应不同的工厂类
REPORT zabstract_factory_pattern.
**********************************************************************
*1) Extract productive interface
*2) Extract factory interface
*3) Each concrete class has its factory class
*4) Factory is the only place to create a productive instance
*5) When new product class is added, a corresponding factory is also
*   added, open-close principle
**********************************************************************
INTERFACE lif_user.
  METHODS get_user
    IMPORTING iv_user TYPE string.
  METHODS insert_user
    IMPORTING iv_user TYPE string.
ENDINTERFACE.

INTERFACE lif_department.
  METHODS get_department
    IMPORTING iv_department TYPE string.
  METHODS insert_department
    IMPORTING iv_department TYPE string.
ENDINTERFACE.

INTERFACE lif_db_factory.
  METHODS create_user
    RETURNING
      VALUE(ro_user) TYPE REF TO lif_user.
  METHODS create_department
    RETURNING VALUE(ro_department) TYPE REF TO lif_department.
ENDINTERFACE.
**********************************************************************
CLASS lcf_sql_server DEFINITION DEFERRED.
CLASS lcf_oracle DEFINITION DEFERRED.
**********************************************************************
CLASS lcl_sql_server_user DEFINITION FINAL CREATE PRIVATE
FRIENDS lcf_sql_server.
  PUBLIC SECTION.
    INTERFACES lif_user.
ENDCLASS.

CLASS lcl_sql_server_user IMPLEMENTATION.
  METHOD lif_user~get_user.
    WRITE: / 'Sql Server, return user:' && iv_user.
  ENDMETHOD.

  METHOD lif_user~insert_user.
    WRITE: / 'Sql Server, insert user:' && iv_user.
  ENDMETHOD.
ENDCLASS.

CLASS lcl_sql_server_department DEFINITION FINAL CREATE PRIVATE
FRIENDS lcf_sql_server.
  PUBLIC SECTION.
    INTERFACES lif_department.
ENDCLASS.

CLASS lcl_sql_server_department IMPLEMENTATION.
  METHOD lif_department~get_department.
    WRITE: / 'Sql Server, return department:' && iv_department.
  ENDMETHOD.

  METHOD lif_department~insert_department.
    WRITE: / 'Sql Server, insert department:' && iv_department.
  ENDMETHOD.
ENDCLASS.
**********************************************************************
CLASS lcl_oracle_user DEFINITION FINAL CREATE PRIVATE
FRIENDS lcf_oracle.
  PUBLIC SECTION.
    INTERFACES lif_user.
ENDCLASS.

CLASS lcl_oracle_user IMPLEMENTATION.
  METHOD lif_user~get_user.
    WRITE: / 'Oracle, return user:' && iv_user.
  ENDMETHOD.

  METHOD lif_user~insert_user.
    WRITE: / 'Oracle, insert user:' && iv_user.
  ENDMETHOD.
ENDCLASS.

CLASS lcl_oracle_department DEFINITION FINAL CREATE PRIVATE
FRIENDS lcf_oracle.
  PUBLIC SECTION.
    INTERFACES lif_department.
ENDCLASS.

CLASS lcl_oracle_department IMPLEMENTATION.
  METHOD lif_department~get_department.
    WRITE: / 'Oracle, return department:' && iv_department.
  ENDMETHOD.

  METHOD lif_department~insert_department.
    WRITE: / 'Oracle, insert department:' && iv_department.
  ENDMETHOD.
ENDCLASS.
**********************************************************************
CLASS lcf_sql_server DEFINITION FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    INTERFACES lif_db_factory.
ENDCLASS.

CLASS lcf_sql_server IMPLEMENTATION.
  METHOD lif_db_factory~create_department.
    ro_department = NEW lcl_sql_server_department(  ).
  ENDMETHOD.

  METHOD lif_db_factory~create_user.
    ro_user = NEW lcl_sql_server_user(  ).
  ENDMETHOD.
ENDCLASS.

CLASS lcf_oracle DEFINITION FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    INTERFACES lif_db_factory.
ENDCLASS.

CLASS lcf_oracle IMPLEMENTATION.
  METHOD lif_db_factory~create_department.
    ro_department = NEW lcl_oracle_department(  ).
  ENDMETHOD.

  METHOD lif_db_factory~create_user.
    ro_user = NEW lcl_oracle_user(  ).
  ENDMETHOD.
ENDCLASS.
**********************************************************************
START-OF-SELECTION.
  " if it's a SQL DB
  DATA(lo_sql_factory) = NEW lcf_sql_server(  ).
  DATA(lo_sql_user) = lo_sql_factory->lif_db_factory~create_user(  ).
  DATA(lo_sql_department) = lo_sql_factory->lif_db_factory~create_department(  ).

  lo_sql_user->insert_user( 'Jerry' ).
  lo_sql_user->get_user( 'Jerry' ).
  lo_sql_department->insert_department( 'R&D' ).
  lo_sql_department->get_department( 'R&D' ).

  " if it's a Oracle DB
  DATA(lo_oracle_factory) = NEW lcf_oracle(  ).
  DATA(lo_oracle_user) = lo_oracle_factory->lif_db_factory~create_user(  ).
  DATA(lo_oracle_department) = lo_oracle_factory->lif_db_factory~create_department(  ).

  lo_oracle_user->insert_user( 'Jerry' ).
  lo_oracle_user->get_user( 'Jerry' ).
  lo_oracle_department->insert_department( 'R&D' ).
  lo_oracle_department->get_department( 'R&D' ).

运行结果:
在这里插入图片描述

3.3 动态编程实现

在上面的例子中,通过抽象工厂模式,屏蔽的DB的差异性,对于客户端,只需要关心具体的User操作和Department操作即可。在不同的DB下,使用不同的工厂类即可。

其实,此处也可以通过反射或动态编程的方式进一步优化,也即,将DB信息写入到配置文件中,程序运行过程中,读取配置文件来决定使用哪种DB,进而实例化相关的工厂类完成操作。

下面的代码段,给出了一个简单示例。

  " to create product dynamically by factory,
  " the product is changed according to its dynamic factory
  DATA lo_dao_factory TYPE REF TO lif_db_factory.
  DATA lv_db_factory_name TYPE seoclsname VALUE 'LCF_SQL_SERVER'. " read from config file

  CREATE OBJECT lo_dao_factory TYPE (lv_db_factory_name).
  DATA(lo_user) = lo_dao_factory->create_user(  ).
  DATA(lo_department) = lo_dao_factory->create_department(  ).
  lo_user->insert_user( 'Jerry' ).
  lo_user->get_user( 'Jerry' ).
  lo_department->insert_department( 'R&D' ).
  lo_department->get_department( 'R&D' ).

以上是对抽象工厂模式的总结,欢迎分享、留言。😉

本博客专注于技术分享,干货满满,持续更新。
欢迎关注❤️、点赞👍、转发📣!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

十年铸器

给作者赏杯咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值