建造者模式用于参数对象

3
请原谅我对设计模式的知识匮乏。
有时候,方法会有很多参数,引入一个“参数对象”是根据重构大师文章重构代码的正确方式。
想象一下这样一种情况:我们有一个处理某种类型文档的服务。
public class FinancialStatementService : IFinancialStatementService
{
    public Document Create(Options input)
    {
        // creating basic document content and adding optional data below:

        if (input.HasAccountNo)
        {
            // include account number
        }
        if (input.HasPaymentDetails)
        {
            // include payment details
        }
        if (input.HasZeroBalances)
        {
            // include zero balances
        }
        if (input.HasTotal)
        {
            // include total
        }

        // and then return the document
    }
}


public class Options
{
    public DateRange DateRange { get; set; }

    public bool HasAccountNo { get; set; }
    public bool HasPaymentDetails { get; set; }
    public bool HasZeroBalances { get; set; }
    public bool HasTotal { get; set; }
}

该文档由许多部分组成,其中一些是可选的。有时我们需要文档包含所有可能的细节。
但是,想象一种情况,某个组织不想要某些细节。
理想情况下,我希望有一个处理选项创建并包含组织名称方法的类。
public class OptionsCreator
{
    // tax officials want to see all possible details
    public static Options GetTaxOfficeOptions(DateRange dateRange)
    {
        return new Options() { HasAccountNo = true, HasPaymentDetails = true, HasZeroBalances = true, HasTotal = true, DateRange = dateRange };
    }

    // in some other organization they DO NOT NEED zero balances & payment details
    public static Options GetSomeOtherOgranizationOptions(DateRange dateRange)
    {
        return new Options() { HasAccountNo = true, HasTotal = true, DateRange = dateRange };
    }
}

但我担心上面的例子是一种反模式。

我能想到的另一件事是建造者模式。

建造者模式是否是参数对象的最佳解决方案?


回答自己的问题:这是我对软件设计的错误认识。实际上,如果我们将这样一个复杂的对象传递给一个方法,症状符合“长方法”代码异味的模式,并且可能导致SRP违规,正如@BionicCode所提到的那样。 - Alex Herman
2个回答

1
我不知道你的具体要求,但从我的观察来看,我的建议是将过程分解为清晰的独立部分或职责:
1. 角色(组织)识别 2. 角色相关文档创建 3. 文档输出
我认为Print(Options)只需要负责将文档发送到指定的输出。Print(Options)必须了解所有文档但不组装它们。目前的主要问题是,当例如添加新文档、角色或更多文档数据时,必须修改太多地方,因为没有干净的职责边界。
考虑这一点:在为特定文档创建或配置选项参数的那一刻,您已经知道文档会是什么样子。何必让Print(Options)重新开始呢?在您知道选项时,可以创建适当的文档。这样,您已经消除了对Options类型的需求,更重要的是,消除了Options对象的丑陋和难以维护的状态检查。
所以你的流程始于调用特定于角色的正确方法。该方法将创建遵循此特定角色要求的文档。因此,OptionsCreator 变成了 DocumentCreator
一旦文档被创建,您将其交给打印方法。因此,Print(Options) 变成了 Print(Document)。重要的是,Print(Document) 可以输出文档,而无需了解任何关于角色或文档详细信息,只需知道如何以及在哪里输出文档即可。现在,当引入新角色或文档内容时,Print(Document) 就不需要修改。您只需向 DocumentCreator 添加一个新的工厂方法来创建特定于新角色的新文档。
我认为在这里您无法从生成器中受益。

我还修改了我的帖子,现在服务方法只负责文档创建。 - Alex Herman

1

我通常使用以下代码:

  1. 常量类,包含一个 - (在此示例中,我使用第一个参数作为用于 xml 解析的字符串,第二个参数作为默认值)

    public static readonly Dictionary<string, Tuple<string, object>> fieldNamesWithOptions = new Dictionary<string, Tuple<string, object>>() { { "number", new Tuple<string, object>("PONumber","") }, { "shipDate", new Tuple<string, object>("ShipDate", new DateTime(1900, 1, 1)) }, { "detailID", new Tuple<string, object>("DetailID", -1) }, }
  2. 按照以下方式处理选项(伪代码,假设元组的第三个参数是准备函数):


    for every field F in object O:
        if(fieldNamesWithOptions.ContainsKey(F.name)) 
        {
            result += fieldNamesWithOptions[F].third(O.F);
        }

我使用伪代码,因为在C#中它看起来相当复杂。
这个重构将清理您的代码,将逻辑分成小片段,可以理解/测试。另一方面,我们有一个不寻常的循环对于对象O中的每个字段F:,但只要调试一次,它就可以在项目的任何其他部分中用作帮助程序。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接