如何有效地对在特定点上仅略有不同的共享代码片段进行建模?
How do I model shared code pieces differing only slightly at a specific point effectively?
我正在编写一个用于许多场景(供应商、客户、成本中心、REFX 合同等)的数据导出应用程序。
最后导出的方式主要有两种:保存到文件或者调用webservice。
所以我的想法是创建一个接口 if_export
,为每个场景实现一个 class。
问题是,调用 webservice 代码在实际调用时略有不同:每次调用的方法都有不同的名称。
到目前为止我处理这个问题的想法是:
- 摘要
cl_webservice_export
每个场景都有子class。覆盖包含实际调用的方法。
cl_webservice_export
成员类型 if_webservice_call
。 class 对于实施 if_webservice_call
方法的每个场景 call_webservice()
- 动态
CALL METHOD webservice_instance->(method_name)
里面
具体 cl_webservice_export 方法包含实际调用并将 (method_name)
传递给 cl_webservice_export
.
我的代码:
export_via_webservice
是由 cl_webservice_export
或通过 if_export
提供的 public 接口
METHODS export_via_webservice
IMPORTING
VALUE(it_xml_strings) TYPE tt_xml_string_table
io_service_consumer TYPE REF TO ztnco_service_vmsoap
RETURNING
VALUE(rt_export_results) TYPE tt_xml_string_table.
METHOD export_via_webservice.
LOOP AT it_xml_strings INTO DATA(lv_xml_string).
call_webservice(
EXPORTING
io_service = io_service_consumer
iv_xml_string = lv_xml_string-xmlstring
RECEIVING
rv_result = DATA(lv_result)
).
rt_export_results = VALUE #( BASE rt_export_results (
lifnr = lv_xml_string-xmlstring
xmlstring = lv_result ) ).
ENDLOOP.
ENDMETHOD.
实际的网络服务调用,由 if_webservice_call
覆盖或提供
METHODS call_webservice
IMPORTING
io_service TYPE REF TO ztnco_service_vmsoap
iv_xml_string TYPE string
RETURNING
VALUE(rv_result) TYPE string.
METHOD call_webservice.
TRY.
io_service->import_creditor(
EXPORTING
input = VALUE #( xml_creditor_data = iv_xml_string )
IMPORTING
output = DATA(lv_output)
).
CATCH cx_ai_system_fault INTO DATA(lx_exception).
ENDTRY.
rv_result = lv_output-import_creditor_result.
ENDMETHOD.
你会如何解决这个问题,也许还有其他更好的方法?
我知道解决这个问题的三种常见模式。它们按质量升序排列:
个别实施
创建一个接口 if_export
,以及一个 class 为您需要的每个 Web 服务导出变体实现它,即 cl_webservice_export_variant_a
、cl_webservice_export_variant_b
等。
主要优点是直观简单的 class 设计和实现的完全独立性,避免了从一个变体到另一个变体的意外溢出。
主要缺点是不同变体之间可能有大量代码重复,如果它们的代码仅在少数次要位置有所不同。
您已经将此草拟为您的选项 2,并且还强调了它是最不适合您的场景的最佳解决方案。永远不欢迎代码重复。更是如此,因为您的网络服务调用在某些方法名称中仅略有不同。
综上所述,这个模式比较差,不宜主动选择。它通常会自行存在,当人们从变体 a 开始,几个月后通过复制粘贴现有的 class 添加变体 b,然后忘记重构代码以摆脱重复的部分。
策略模式
这种设计通常被称为strategy design pattern。创建一个接口 if_export
,以及一个 abstract
class cl_abstract_webservice_export
实现该接口并包含大部分 Web 服务调用代码。
除了这个细节:应该调用的方法的名称不是硬编码的,而是通过调用 protected
子方法 get_service_name
检索的。摘要 class 未 实现此方法。相反,您创建抽象 class 的子 classes,即 cl_concrete_webservice_export_variant_a
、cl_concrete_webservice_export_variant_b
等。这些 classes 仅实现继承的受保护方法 get_service_name
,提供他们的具体需求。
主要优点是这种模式完全避免了代码重复,对进一步的扩展开放,并已在许多框架实现中成功采用。
主要缺点是当第一个不完全适合的变体到达时,模式开始腐蚀,例如因为它不仅改变了方法名称,还改变了一些参数。然后,进化需要对 所有 涉及的 class 进行深入重新设计,这可能会产生相当大的成本。另一个缺点是继承设置会使编写单元测试变得很麻烦:例如,单元测试抽象 class 需要组成一个测试替身,子classes它并覆盖受保护的带有传感和模拟代码的方法 - 所有可能但不如 classes.
之间的接口那么整洁
您已经将此草拟为您的选项 1。总而言之,如果您可以控制所有涉及的 classes 并且愿意花费一些额外的努力来保持此模式,我建议您选择此模式清洁以防它不完全适合。
作文
组合意味着避免继承,有利于独立 class 和 class 之间的松散交互。创建接口 if_export
及其具体实现,如 cl_webservice_export_variant_a
、cl_webservice_export_variant_b
等
将共享代码移出到一个 class cl_export_webservice_caller
中,它接收它需要的任何数据和变体(例如方法名称)。让变体classes调用这个共享代码。为完成 class 设计,引入另一个接口 if_export_webservice_caller
将变量 class 与调用者 class.
分离
主要优点是所有 classes 都是相互独立的,并且可以通过几种不同的方式重新组合。例如,如果将来您需要引入一个以完全不同的方式调用其 Web 服务的变体 X,您可以简单地添加它,而无需重新设计任何其他涉及的 classes。与策略模式相比,为所有涉及的 classes 编写单元测试是微不足道的。
这种模式没有真正的缺点。 (它需要一个接口的表面上的缺点实际上并不是一个 - 面向对象的目的是清楚地分离关注点,而不是最小化 classes/interfaces 的总数,我们不应该害怕添加更多这些如果它增加了整体设计的清晰度。)
这个选项听起来和你草拟的选项 3 很相似,但我不是 100% 确定。无论如何,这将是我投票支持的模式。
我正在编写一个用于许多场景(供应商、客户、成本中心、REFX 合同等)的数据导出应用程序。 最后导出的方式主要有两种:保存到文件或者调用webservice。
所以我的想法是创建一个接口 if_export
,为每个场景实现一个 class。
问题是,调用 webservice 代码在实际调用时略有不同:每次调用的方法都有不同的名称。
到目前为止我处理这个问题的想法是:
- 摘要
cl_webservice_export
每个场景都有子class。覆盖包含实际调用的方法。 cl_webservice_export
成员类型if_webservice_call
。 class 对于实施if_webservice_call
方法的每个场景call_webservice()
- 动态
CALL METHOD webservice_instance->(method_name)
里面 具体 cl_webservice_export 方法包含实际调用并将(method_name)
传递给cl_webservice_export
.
我的代码:
export_via_webservice
是由 cl_webservice_export
或通过 if_export
METHODS export_via_webservice
IMPORTING
VALUE(it_xml_strings) TYPE tt_xml_string_table
io_service_consumer TYPE REF TO ztnco_service_vmsoap
RETURNING
VALUE(rt_export_results) TYPE tt_xml_string_table.
METHOD export_via_webservice.
LOOP AT it_xml_strings INTO DATA(lv_xml_string).
call_webservice(
EXPORTING
io_service = io_service_consumer
iv_xml_string = lv_xml_string-xmlstring
RECEIVING
rv_result = DATA(lv_result)
).
rt_export_results = VALUE #( BASE rt_export_results (
lifnr = lv_xml_string-xmlstring
xmlstring = lv_result ) ).
ENDLOOP.
ENDMETHOD.
实际的网络服务调用,由 if_webservice_call
METHODS call_webservice
IMPORTING
io_service TYPE REF TO ztnco_service_vmsoap
iv_xml_string TYPE string
RETURNING
VALUE(rv_result) TYPE string.
METHOD call_webservice.
TRY.
io_service->import_creditor(
EXPORTING
input = VALUE #( xml_creditor_data = iv_xml_string )
IMPORTING
output = DATA(lv_output)
).
CATCH cx_ai_system_fault INTO DATA(lx_exception).
ENDTRY.
rv_result = lv_output-import_creditor_result.
ENDMETHOD.
你会如何解决这个问题,也许还有其他更好的方法?
我知道解决这个问题的三种常见模式。它们按质量升序排列:
个别实施
创建一个接口 if_export
,以及一个 class 为您需要的每个 Web 服务导出变体实现它,即 cl_webservice_export_variant_a
、cl_webservice_export_variant_b
等。
主要优点是直观简单的 class 设计和实现的完全独立性,避免了从一个变体到另一个变体的意外溢出。
主要缺点是不同变体之间可能有大量代码重复,如果它们的代码仅在少数次要位置有所不同。
您已经将此草拟为您的选项 2,并且还强调了它是最不适合您的场景的最佳解决方案。永远不欢迎代码重复。更是如此,因为您的网络服务调用在某些方法名称中仅略有不同。
综上所述,这个模式比较差,不宜主动选择。它通常会自行存在,当人们从变体 a 开始,几个月后通过复制粘贴现有的 class 添加变体 b,然后忘记重构代码以摆脱重复的部分。
策略模式
这种设计通常被称为strategy design pattern。创建一个接口 if_export
,以及一个 abstract
class cl_abstract_webservice_export
实现该接口并包含大部分 Web 服务调用代码。
除了这个细节:应该调用的方法的名称不是硬编码的,而是通过调用 protected
子方法 get_service_name
检索的。摘要 class 未 实现此方法。相反,您创建抽象 class 的子 classes,即 cl_concrete_webservice_export_variant_a
、cl_concrete_webservice_export_variant_b
等。这些 classes 仅实现继承的受保护方法 get_service_name
,提供他们的具体需求。
主要优点是这种模式完全避免了代码重复,对进一步的扩展开放,并已在许多框架实现中成功采用。
主要缺点是当第一个不完全适合的变体到达时,模式开始腐蚀,例如因为它不仅改变了方法名称,还改变了一些参数。然后,进化需要对 所有 涉及的 class 进行深入重新设计,这可能会产生相当大的成本。另一个缺点是继承设置会使编写单元测试变得很麻烦:例如,单元测试抽象 class 需要组成一个测试替身,子classes它并覆盖受保护的带有传感和模拟代码的方法 - 所有可能但不如 classes.
之间的接口那么整洁您已经将此草拟为您的选项 1。总而言之,如果您可以控制所有涉及的 classes 并且愿意花费一些额外的努力来保持此模式,我建议您选择此模式清洁以防它不完全适合。
作文
组合意味着避免继承,有利于独立 class 和 class 之间的松散交互。创建接口 if_export
及其具体实现,如 cl_webservice_export_variant_a
、cl_webservice_export_variant_b
等
将共享代码移出到一个 class cl_export_webservice_caller
中,它接收它需要的任何数据和变体(例如方法名称)。让变体classes调用这个共享代码。为完成 class 设计,引入另一个接口 if_export_webservice_caller
将变量 class 与调用者 class.
主要优点是所有 classes 都是相互独立的,并且可以通过几种不同的方式重新组合。例如,如果将来您需要引入一个以完全不同的方式调用其 Web 服务的变体 X,您可以简单地添加它,而无需重新设计任何其他涉及的 classes。与策略模式相比,为所有涉及的 classes 编写单元测试是微不足道的。
这种模式没有真正的缺点。 (它需要一个接口的表面上的缺点实际上并不是一个 - 面向对象的目的是清楚地分离关注点,而不是最小化 classes/interfaces 的总数,我们不应该害怕添加更多这些如果它增加了整体设计的清晰度。)
这个选项听起来和你草拟的选项 3 很相似,但我不是 100% 确定。无论如何,这将是我投票支持的模式。