1. 定义
通过拷贝原型本身,创建新的对象。
原型模式属于创建型模式的一种。
2. 解读
类比:
- 这个设计模式在现实生活中也能找到对应的例子。我们经常使用的“复制粘贴”快捷键,其实通过“拷贝”的方式进行的。Ctrl + C 来选中并复制对象,Ctrl + V 粘贴新生成的对象。
解析:
- 原型模式,其实就是通过复制一个实例对象,来生成另外一个可定制对象的过程。也即,通过复制,来实现类的实例化。
- 当对象的实例化过程非常复杂时,可以考虑使用原型模式,通过克隆的方式,动态地获得对象运行时的状态,并在此基础上操作。
- 对于某些语言,例如C#, Java等,系统的命名空间中已经提供了ICloneable接口,直接实现这个接口就可以完成原型模型; 对于没有预制ICloneable接口的语言,例如ABAP,我们可以自己定义Cloneable接口即可。
- 在复制过程中,对于引用类型的属性,要注意“浅复制(Shadow Clone)”和“深复制(Deep Clone)”的问题。
要点:
- 在Cloneable接口中,定义Clone( )方法,返回值为接口对象本身。在实现类中,实现这个cloneable的接口,通过Clone( )方法依照本实例,复制出一个新的实例。
适用场景:
- 并不是所有的场景都适合适用原型模式,特别是引用类型较多的类,要因情况而异。因为Cloneable接口的引入,会让类的设计变得更加复杂。
3.举例
定义一个if_prototype_cloneable的接口,在实现类中实现此接口,并在Clone中返回一个新的实例。
3.1 浅复制
在此例中,通过lcl_resume的clone( )方法来生成新的实例。
REPORT zprototype_pattern.
INTERFACE lif_resume_cloneable.
METHODS set_name
IMPORTING
iv_name TYPE string.
METHODS
set_age
IMPORTING iv_age TYPE i.
METHODS
set_company
IMPORTING iv_company TYPE string.
METHODS
display_info.
" use this clone method to do the initialization of an instance
METHODS clone
RETURNING
VALUE(ro_instance) TYPE REF TO lif_resume_cloneable.
ENDINTERFACE.
CLASS lcl_resume DEFINITION FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES lif_resume_cloneable.
PRIVATE SECTION.
DATA mv_name TYPE string.
DATA mv_age TYPE i.
DATA mv_company TYPE string.
ENDCLASS.
CLASS lcl_resume IMPLEMENTATION.
METHOD lif_resume_cloneable~clone.
ro_instance = NEW lcl_resume( ). " New a fresh instance
ro_instance->set_name( mv_name ).
ro_instance->set_age( mv_age ).
ro_instance->set_company( mv_company ).
ENDMETHOD.
METHOD lif_resume_cloneable~set_name.
mv_name = iv_name.
ENDMETHOD.
METHOD lif_resume_cloneable~set_age.
mv_age = iv_age.
ENDMETHOD.
METHOD lif_resume_cloneable~set_company.
mv_company = iv_company.
ENDMETHOD.
METHOD lif_resume_cloneable~display_info.
WRITE / 'Printing Resume... ...'.
WRITE / 'Name: ' && mv_name.
WRITE / 'Age: ' && mv_age.
WRITE / 'Company:' && mv_company.
WRITE / '-------'.
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
DATA lo_resume_a TYPE REF TO lif_resume_cloneable.
DATA lo_resume_b TYPE REF TO lif_resume_cloneable.
lo_resume_a = NEW lcl_resume( ).
lo_resume_a->set_name( 'Tom' ).
lo_resume_a->set_age( 18 ).
lo_resume_a->set_company( 'XXX Company ' ).
" lo_resume_b is cloned from lo_resume_a
lo_resume_b = lo_resume_a->clone( ).
lo_resume_b->set_name( 'Jerry' ).
lo_resume_a->display_info( ).
lo_resume_b->display_info( ).
运行结果:
3.2 深复制
在此例中,lcl_resume含有的引用对象lcl_work_experience. 在复制过程中,要同样为复制lcl_work_experience.
*&---------------------------------------------------------------------*
*& Report zprototype_pattern2
*&---------------------------------------------------------------------*
*& This case shows the way of deep clone
*&---------------------------------------------------------------------*
REPORT zprototype_pattern2.
INTERFACE lif_work_experience.
METHODS set_work_experience
IMPORTING
iv_experience TYPE string.
METHODS get_work_experience
RETURNING VALUE(rv_experience) TYPE string.
METHODS print_experience.
ENDINTERFACE.
CLASS lcl_work_experience DEFINITION FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES lif_work_experience.
PRIVATE SECTION.
DATA mv_experience TYPE string.
ENDCLASS.
CLASS lcl_work_experience IMPLEMENTATION.
METHOD lif_work_experience~print_experience.
WRITE / 'Experience:' && mv_experience.
ENDMETHOD.
METHOD lif_work_experience~set_work_experience.
mv_experience = iv_experience.
ENDMETHOD.
METHOD lif_work_experience~get_work_experience.
rv_experience = mv_experience.
ENDMETHOD.
ENDCLASS.
**********************************************************************
INTERFACE lif_resume_cloneable.
METHODS set_name
IMPORTING
iv_name TYPE string.
METHODS
set_age
IMPORTING iv_age TYPE i.
METHODS
set_company
IMPORTING iv_company TYPE string.
METHODS
set_experience
IMPORTING iv_experience TYPE string.
METHODS
display_info.
" use this clone method to do the initialization of an instance
METHODS clone
RETURNING
VALUE(ro_instance) TYPE REF TO lif_resume_cloneable.
ENDINTERFACE.
CLASS lcl_resume DEFINITION FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES lif_resume_cloneable.
METHODS
constructor.
PRIVATE SECTION.
DATA mv_name TYPE string.
DATA mv_age TYPE i.
DATA mv_company TYPE string.
DATA mo_work_experience TYPE REF TO lif_work_experience.
ENDCLASS.
CLASS lcl_resume IMPLEMENTATION.
METHOD lif_resume_cloneable~clone.
ro_instance = NEW lcl_resume( ). " New a fresh instance
ro_instance->set_name( mv_name ).
ro_instance->set_age( mv_age ).
ro_instance->set_company( mv_company ).
ro_instance->set_experience( mo_work_experience->get_work_experience( ) ).
ENDMETHOD.
METHOD lif_resume_cloneable~set_name.
mv_name = iv_name.
ENDMETHOD.
METHOD lif_resume_cloneable~set_age.
mv_age = iv_age.
ENDMETHOD.
METHOD lif_resume_cloneable~set_company.
mv_company = iv_company.
ENDMETHOD.
METHOD lif_resume_cloneable~display_info.
WRITE / '-------'.
WRITE / 'Printing Resume... ...'.
WRITE / 'Name: ' && mv_name.
WRITE / 'Age: ' && mv_age.
WRITE / 'Company:' && mv_company.
mo_work_experience->print_experience( ).
ENDMETHOD.
METHOD lif_resume_cloneable~set_experience.
mo_work_experience->set_work_experience( iv_experience ).
ENDMETHOD.
METHOD constructor.
" always create a new instance of work_experience (to have a separated address space, not a reference pass )
mo_work_experience = NEW lcl_work_experience( ).
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
DATA lo_resume_a TYPE REF TO lif_resume_cloneable.
DATA lo_resume_b TYPE REF TO lif_resume_cloneable.
lo_resume_a = NEW lcl_resume( ).
lo_resume_a->set_name( 'Tom' ).
lo_resume_a->set_age( 18 ).
lo_resume_a->set_company( 'XXX Company ' ).
lo_resume_a->set_experience( 'Tom work experience' ).
" lo_resume_b is cloned from lo_resume_a
lo_resume_b = lo_resume_a->clone( ).
lo_resume_b->set_name( 'Jerry' ).
lo_resume_b->set_experience( 'Jerry work experience' ).
" the cloned object is completed independent from each other
lo_resume_a->display_info( ).
lo_resume_b->display_info( ).
运行结果:
3.3 注意引用类型
3.3.1 共用地址空间
在复制时,要注意引用类型对象的处理。
*&---------------------------------------------------------------------*
*& Report zprototype_pattern3
*&---------------------------------------------------------------------*
*& This case shows the effects of reference pass (shadow clone)
*&---------------------------------------------------------------------*
REPORT zprototype_pattern3.
INTERFACE lif_work_experience.
METHODS set_work_experience
IMPORTING
iv_experience TYPE string.
METHODS print_experience.
* " use this clone method to do the initialization of an instance
* METHODS clone
* RETURNING
* VALUE(ro_instance) TYPE REF TO lif_work_experience.
ENDINTERFACE.
CLASS lcl_work_experience DEFINITION FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES lif_work_experience.
PRIVATE SECTION.
DATA mv_experience TYPE string.
ENDCLASS.
CLASS lcl_work_experience IMPLEMENTATION.
METHOD lif_work_experience~print_experience.
WRITE / 'Experience:' && mv_experience.
ENDMETHOD.
METHOD lif_work_experience~set_work_experience.
mv_experience = iv_experience.
ENDMETHOD.
* METHOD lif_work_experience~clone.
* ro_instance = NEW lcl_work_experience( ).
* ro_instance->set_work_experience( mv_experience ).
* ENDMETHOD.
ENDCLASS.
**********************************************************************
INTERFACE lif_resume_cloneable.
METHODS set_name
IMPORTING
iv_name TYPE string.
METHODS
set_age
IMPORTING iv_age TYPE i.
METHODS
set_company
IMPORTING iv_company TYPE string.
METHODS
set_experience
IMPORTING io_experience TYPE REF TO lif_work_experience.
METHODS
display_info.
" use this clone method to do the initialization of an instance
METHODS clone
RETURNING
VALUE(ro_instance) TYPE REF TO lif_resume_cloneable.
ENDINTERFACE.
CLASS lcl_resume DEFINITION FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES lif_resume_cloneable.
PRIVATE SECTION.
DATA mv_name TYPE string.
DATA mv_age TYPE i.
DATA mv_company TYPE string.
DATA mo_work_experience TYPE REF TO lif_work_experience.
ENDCLASS.
CLASS lcl_resume IMPLEMENTATION.
METHOD lif_resume_cloneable~clone.
ro_instance = NEW lcl_resume( ). " New a fresh instance
ro_instance->set_name( mv_name ).
ro_instance->set_age( mv_age ).
ro_instance->set_company( mv_company ).
" Here it is only a reference pass
ro_instance->set_experience( mo_work_experience ).
ENDMETHOD.
METHOD lif_resume_cloneable~set_name.
mv_name = iv_name.
ENDMETHOD.
METHOD lif_resume_cloneable~set_age.
mv_age = iv_age.
ENDMETHOD.
METHOD lif_resume_cloneable~set_company.
mv_company = iv_company.
ENDMETHOD.
METHOD lif_resume_cloneable~display_info.
WRITE / '-------'.
WRITE / 'Printing Resume... ...'.
WRITE / 'Name: ' && mv_name.
WRITE / 'Age: ' && mv_age.
WRITE / 'Company:' && mv_company.
mo_work_experience->print_experience( ).
ENDMETHOD.
METHOD lif_resume_cloneable~set_experience.
mo_work_experience = io_experience.
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
DATA lo_resume_a TYPE REF TO lif_resume_cloneable.
DATA lo_resume_b TYPE REF TO lif_resume_cloneable.
DATA lo_experience TYPE REF TO lif_work_experience.
lo_resume_a = NEW lcl_resume( ).
lo_resume_a->set_name( 'Tom' ).
lo_resume_a->set_age( 18 ).
lo_resume_a->set_company( 'XXX Company ' ).
" lo_resume_b is cloned from lo_resume_a
" the reference object is not independent, so it was shared and affect each other
" you could see 'Jerry work experience' affects lo_resume_a and lo_resume_b.
lo_resume_b = lo_resume_a->clone( ).
lo_resume_b->set_name( 'Jerry' ).
lo_experience = NEW lcl_work_experience( ).
lo_experience->set_work_experience( 'Tom work experience' ).
lo_resume_a->set_experience( lo_experience ).
lo_experience->set_work_experience( 'Jerry work experience' ).
lo_resume_b->set_experience( lo_experience ).
lo_resume_a->display_info( ).
lo_resume_b->display_info( ).
运行结果:
由于共用lo_experience的地址空间,复制出来的lo_resume_b和原来的lo_resume_a同时指向了lo_experience, 这导致这两个对象并非独立的,而是互相影响的。
3.3.2 开辟新的地址空间
为每个实例都开辟单独的地址空间。
*&---------------------------------------------------------------------*
*& Report zprototype_pattern4
*&---------------------------------------------------------------------*
*& This case shows the effects of reference pass (shadow clone)
*&---------------------------------------------------------------------*
REPORT zprototype_pattern4.
INTERFACE lif_work_experience.
METHODS set_work_experience
IMPORTING
iv_experience TYPE string.
METHODS print_experience.
* " use this clone method to do the initialization of an instance
* METHODS clone
* RETURNING
* VALUE(ro_instance) TYPE REF TO lif_work_experience.
ENDINTERFACE.
CLASS lcl_work_experience DEFINITION FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES lif_work_experience.
PRIVATE SECTION.
DATA mv_experience TYPE string.
ENDCLASS.
CLASS lcl_work_experience IMPLEMENTATION.
METHOD lif_work_experience~print_experience.
WRITE / 'Experience:' && mv_experience.
ENDMETHOD.
METHOD lif_work_experience~set_work_experience.
mv_experience = iv_experience.
ENDMETHOD.
* METHOD lif_work_experience~clone.
* ro_instance = NEW lcl_work_experience( ).
* ro_instance->set_work_experience( mv_experience ).
* ENDMETHOD.
ENDCLASS.
**********************************************************************
INTERFACE lif_resume_cloneable.
METHODS set_name
IMPORTING
iv_name TYPE string.
METHODS
set_age
IMPORTING iv_age TYPE i.
METHODS
set_company
IMPORTING iv_company TYPE string.
METHODS
set_experience
IMPORTING io_experience TYPE REF TO lif_work_experience.
METHODS
display_info.
" use this clone method to do the initialization of an instance
METHODS clone
RETURNING
VALUE(ro_instance) TYPE REF TO lif_resume_cloneable.
ENDINTERFACE.
CLASS lcl_resume DEFINITION FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES lif_resume_cloneable.
PRIVATE SECTION.
DATA mv_name TYPE string.
DATA mv_age TYPE i.
DATA mv_company TYPE string.
DATA mo_work_experience TYPE REF TO lif_work_experience.
ENDCLASS.
CLASS lcl_resume IMPLEMENTATION.
METHOD lif_resume_cloneable~clone.
ro_instance = NEW lcl_resume( ). " New a fresh instance
ro_instance->set_name( mv_name ).
ro_instance->set_age( mv_age ).
ro_instance->set_company( mv_company ).
" Here it is only a reference pass
ro_instance->set_experience( mo_work_experience ).
ENDMETHOD.
METHOD lif_resume_cloneable~set_name.
mv_name = iv_name.
ENDMETHOD.
METHOD lif_resume_cloneable~set_age.
mv_age = iv_age.
ENDMETHOD.
METHOD lif_resume_cloneable~set_company.
mv_company = iv_company.
ENDMETHOD.
METHOD lif_resume_cloneable~display_info.
WRITE / '-------'.
WRITE / 'Printing Resume... ...'.
WRITE / 'Name: ' && mv_name.
WRITE / 'Age: ' && mv_age.
WRITE / 'Company:' && mv_company.
mo_work_experience->print_experience( ).
ENDMETHOD.
METHOD lif_resume_cloneable~set_experience.
mo_work_experience = io_experience.
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
DATA lo_resume_a TYPE REF TO lif_resume_cloneable.
DATA lo_resume_b TYPE REF TO lif_resume_cloneable.
lo_resume_a = NEW lcl_resume( ).
lo_resume_a->set_name( 'Tom' ).
lo_resume_a->set_age( 18 ).
lo_resume_a->set_company( 'XXX Company ' ).
" lo_resume_b is cloned from lo_resume_a
" the reference object is not independent, so it was shared and affect each other
" you could see 'Jerry work experience' affects lo_resume_a and lo_resume_b.
lo_resume_b = lo_resume_a->clone( ).
lo_resume_b->set_name( 'Jerry' ).
" so in this case, to have 2 independent lo_experience is needed.
DATA lo_experience_a TYPE REF TO lif_work_experience.
DATA lo_experience_b TYPE REF TO lif_work_experience.
lo_experience_a = NEW lcl_work_experience( ).
lo_experience_a->set_work_experience( 'Tom work experience' ).
lo_resume_a->set_experience( lo_experience_a ).
lo_experience_b = NEW lcl_work_experience( ).
lo_experience_b->set_work_experience( 'Jerry work experience' ).
lo_resume_b->set_experience( lo_experience_b ).
lo_resume_a->display_info( ).
lo_resume_b->display_info( ).
运行结果:
此时,两个实例会是完全独立的。
本博客专注于技术分享,干货满满,持续更新。
欢迎关注❤️、点赞👍、转发📣!