1. 定义
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
工厂类中提供了多个创建方法,分别负责相关类的实例化。
一个工厂类,对应的多个相关的生产类。
2. 解读
类比:
- 抽象工厂模式,是工厂方法模式的一个扩展。还记得工厂方法模式的那个类比么 😉
- 工厂方法模式中的工厂,小而美,每个工厂仅生产一种产品。这种方式,带来的一个问题就是,工厂类的数量会和生产类的数量一样多。在现实生活中,其实仅生产一种产品的工厂是少之又少的,大多数工厂其实都是生产某一品类的产品,不同的产品之间是相关的。
- 生产面包的工厂,通常也会生产蛋糕、饼干等,具体到产品层级,面包产品线可以生产切片面包、法式面包、意式面包等,蛋糕生产线包括乳酪蛋糕、艺术蛋糕、婚礼蛋糕等,同样的,饼干生产线也可生成多种样式的饼干。在此例中,面包+蛋糕+饼干构成了此工厂的产品族。
- 抽象工厂模式,就是这种生产某一族产品的工厂。
解析:
- 抽象工厂模式中的工厂类,包含多个创建方法,分别用于实例化相关的生产类(例如分别创建面包的实例、蛋糕的实例、饼干的实例)。也即,一个工厂类,对应的多个相关的生产类。
- 当一个工厂等级结构,可以创建出分属于不同产品等级结构的一个产品族中的所有对象时,抽象工厂模式比工厂方法模式更为简单、更有效率。
- 在抽象工厂中,新增加产品族很方便,此种情况下只需要新增对应的工厂即可;但新增新的产品等级结构很麻烦,因为此时要调整抽象工厂的接口(添加新的创建方法),同时要调整所有已生成的工厂类。
- 因此,抽象工厂模式其实具有一种倾斜性的“开闭原则”,为新增产品族提供的拓展的方便性,但对于新增的产品结构并不能提供这种方便。这要求设计人员在设计之初,就要尽可能地考虑全面,尽量避免在完成抽象工厂的设计后,变化产品的等级结构,否则会导致系统出现较大的修改,为后续的维护工作带来麻烦。
- 在抽象工厂模式中,也适用于反射机制的结合应用,用动态编程的方式可以解决客户端判断的问题。也即,使用哪个工厂的场景写到配置文件中,程序运行时通过读取配置文件,动态选择所需要的逻辑。
要点:
- 抽象生产类接口
- 抽象工厂类接口
- 工厂类是实例化生产类的唯一位置
- 工厂类负责某一组相关生产类的实例化
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 抽象工厂模式代码示例
在下面示例中,我们需要的操作是:
- 新增和查找用户信息
- 新增和查找部门信息
- 支持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' ).
以上是对抽象工厂模式的总结,欢迎分享、留言。😉
本博客专注于技术分享,干货满满,持续更新。
欢迎关注❤️、点赞👍、转发📣!