设计模式问题:删除布尔函数参数的过度使用
Design Pattern question: removing overuse of boolean function parameters
我已经几十年没刷过四人帮了。我最近从某些代码中闻到难闻的气味,正在寻找有关优化设计的建议。
存在一个通过接受二进制上传、对其执行各种处理并存储文件来为 API POST 提供服务的函数。随着时间的推移,随着新需求的出现,根据上传的二进制文件的类型,需要跳过该功能中的一些步骤。随着时间的推移,方法签名演变为:
第一次迭代:
public ResponseObject uploadThing(long user_id, long location_id, byte[] file_bytes)
第二次迭代:
public ResponseObject uploadThing(long user_id, long location_id, byte[] file_bytes, boolean ignore_azure)
第三次迭代:
public ResponseObject uploadThing(long user_id, long location_id, byte[] file_bytes, boolean ignore_azure, boolean ignore_aws)
第四次迭代:
public ResponseObject uploadThing(long user_id, long location_id, byte[] file_bytes, boolean ignore_azure, boolean ignore_aws, boolean log_to_airtable)
布尔值对应于包装函数相关部分的新添加条件。此函数有多个路径,因此每次添加新布尔值时,都需要重新访问所有调用代码。此外,代码的自文档化程度越来越低。这里有一个例子:
ro = uploadThing(user_id, org_id, file_bytes, true, false, true)
如果实际触发了任何布尔相关代码,则执行顺序很关键。其他参数中也没有包含可用于确定执行 uploadThing 方法的哪些部分的信息 - 它完全基于特定调用代码的位置。
我不喜欢这个的一些事情:调用者和被调用者之间的耦合越来越紧密,在重构中覆盖多个点的需求越来越大,以及方法调用的预期行为在这一点上越来越模糊它被调用的地方。你会如何重组它?
添加标志枚举,并使用可变参数传递它们。
ResponseObject uploadThing(long user_id, long location_id, byte[] file_bytes, Flags... flags)
您的最终来电者可能看起来像
uploadThing(userId, orgId, fileBytes, Flags.IGNORE_AWS, Flags.LOG_TO_AIRTABLE);
在不了解您的客户端代码(调用 uploadThing
的代码)的要求的情况下,很难给出一个笼统的答案。
你所有的调用都有固定的布尔常量吗?如果是这样,您可以将布尔值重构为单独的配置 class 并将其对象传递给 uploadThing
。此外,如果某些代码使用了一些布尔标志而不是全部,您可以为配置创建一个继承层次结构 class,其中在每个子 class 中仅使用相关标志。
另一种解决方案可能是将 uploadThing
重构为新的 class 并通过合成将其包含在内。在构造函数中,您可以提供适当的布尔值组合。
您可以根据自己的喜好增强这些选项(依赖注入等)
但这在很大程度上取决于被调用方的要求以及他们如何使用uploadThing
。
你有一个伸缩参数反模式的例子。
使用 builder 模式,尤其是在添加功能、可选性、规定时。
示例:旧的 HttpClient,Base64 编码器。
这里的问题是你想要一个薄的API,甚至可能生成。
使用一个类似地图的参数class:
public ResponseObject uploadThing(UploadParams params)
public class UploadParams {
private UploadParams() { }
public static UploadParamsBuilder(long user_id, long location_id) { }
}
class UploadParamsBuilder {
UploadParams build() { }
UploadParamsBuilder withFileBytes(byte[] bytes) { }
...
引入可能已经在第二次迭代中完成,因为 azure 是额外的附加功能。
我已经几十年没刷过四人帮了。我最近从某些代码中闻到难闻的气味,正在寻找有关优化设计的建议。
存在一个通过接受二进制上传、对其执行各种处理并存储文件来为 API POST 提供服务的函数。随着时间的推移,随着新需求的出现,根据上传的二进制文件的类型,需要跳过该功能中的一些步骤。随着时间的推移,方法签名演变为:
第一次迭代:
public ResponseObject uploadThing(long user_id, long location_id, byte[] file_bytes)
第二次迭代:
public ResponseObject uploadThing(long user_id, long location_id, byte[] file_bytes, boolean ignore_azure)
第三次迭代:
public ResponseObject uploadThing(long user_id, long location_id, byte[] file_bytes, boolean ignore_azure, boolean ignore_aws)
第四次迭代:
public ResponseObject uploadThing(long user_id, long location_id, byte[] file_bytes, boolean ignore_azure, boolean ignore_aws, boolean log_to_airtable)
布尔值对应于包装函数相关部分的新添加条件。此函数有多个路径,因此每次添加新布尔值时,都需要重新访问所有调用代码。此外,代码的自文档化程度越来越低。这里有一个例子:
ro = uploadThing(user_id, org_id, file_bytes, true, false, true)
如果实际触发了任何布尔相关代码,则执行顺序很关键。其他参数中也没有包含可用于确定执行 uploadThing 方法的哪些部分的信息 - 它完全基于特定调用代码的位置。
我不喜欢这个的一些事情:调用者和被调用者之间的耦合越来越紧密,在重构中覆盖多个点的需求越来越大,以及方法调用的预期行为在这一点上越来越模糊它被调用的地方。你会如何重组它?
添加标志枚举,并使用可变参数传递它们。
ResponseObject uploadThing(long user_id, long location_id, byte[] file_bytes, Flags... flags)
您的最终来电者可能看起来像
uploadThing(userId, orgId, fileBytes, Flags.IGNORE_AWS, Flags.LOG_TO_AIRTABLE);
在不了解您的客户端代码(调用 uploadThing
的代码)的要求的情况下,很难给出一个笼统的答案。
你所有的调用都有固定的布尔常量吗?如果是这样,您可以将布尔值重构为单独的配置 class 并将其对象传递给 uploadThing
。此外,如果某些代码使用了一些布尔标志而不是全部,您可以为配置创建一个继承层次结构 class,其中在每个子 class 中仅使用相关标志。
另一种解决方案可能是将 uploadThing
重构为新的 class 并通过合成将其包含在内。在构造函数中,您可以提供适当的布尔值组合。
您可以根据自己的喜好增强这些选项(依赖注入等)
但这在很大程度上取决于被调用方的要求以及他们如何使用uploadThing
。
你有一个伸缩参数反模式的例子。
使用 builder 模式,尤其是在添加功能、可选性、规定时。 示例:旧的 HttpClient,Base64 编码器。
这里的问题是你想要一个薄的API,甚至可能生成。
使用一个类似地图的参数class:
public ResponseObject uploadThing(UploadParams params)
public class UploadParams {
private UploadParams() { }
public static UploadParamsBuilder(long user_id, long location_id) { }
}
class UploadParamsBuilder {
UploadParams build() { }
UploadParamsBuilder withFileBytes(byte[] bytes) { }
...
引入可能已经在第二次迭代中完成,因为 azure 是额外的附加功能。