【问题标题】:A idiom or design pattern for class template?类模板的习语或设计模式?
【发布时间】:2012-01-16 17:50:23
【问题描述】:

我的代码库是 Python。假设我有一个相当通用的类,称为 Report。它需要大量的参数

class Report(object):
  def __init__(self, title, data_source, columns, format, ...many more...)

而且有很多很多实例化的报告。这些实例化并非完全无关。许多报告共享一组相似的参数,只是略有不同,例如具有相同的 data_source 和列,但标题不同。

不是复制参数,而是应用了一些编程结构来使这种结构的表达更容易。我正在努力寻找一些帮助来整理我的头脑,以确定一些成语或设计模式。

如果报表的子类需要一些额外的处理代码,子类似乎是一个不错的选择。假设我们有一个 ExpenseReport 的子类别。

class ExpenseReport(Report):
    def __init__(self, title, ... a small number of parameters ...)

        # some parameters are fixed, while others are specific to this instance
        super(ExpenseReport,self).__init__(
                title,
                EXPENSE_DATA_SOURCE,
                EXPENSE_COLUMNS,
                EXPENSE_FORMAT,
                ... a small number of parameters...)

    def processing(self):
        ... extra processing specific to ExpenseReport ...

但在很多情况下,子类只是修复了一些参数,没有任何额外的处理。使用偏函数可以轻松完成。

ExpenseReport = functools.partial(Report,
                        data_source = EXPENSE_DATA_SOURCE,
                        columns = EXPENSE_COLUMNS,
                        format = EXPENSE_FORMAT,
                )

在某些情况下,甚至没有任何区别。我们只需要将同一对象的 2 个副本用于不同的环境,例如嵌入到不同的页面中。

expense_report = Report("Total Expense", EXPENSE_DATA_SOURCE, ...)
page1.add(expense_report)

...
page2.add(clone(expense_report))

在我的代码库中,使用了一种丑陋的技术。因为我们需要为每个页面提供 2 个单独的实例,并且因为我们不想复制具有创建报告的长参数列表的代码,所以我们只需克隆(在 Python 中深度复制)第 2 页的报告。不仅需要克隆不明显,忽略克隆对象而是共享一个实例会在我们的系统中产生许多隐藏的问题和细微的错误。

在这种情况下有什么指导吗?子类、部分函数或其他成语?我的愿望是让这个结构轻而透明。我对子类稍有警惕,因为它可能会导致子类的丛林。并且它会诱导程序员添加特殊的处理代码,就像我在 ExpenseReport 中所做的那样。如果有需要,我宁愿分析代码,看看它是否可以泛化并推送到报告层。这样报表就变得更具表现力,无需在较低层进行特殊处理。

其他信息

我们确实使用关键字参数。问题更多在于如何管理和组织实例化。我们有大量具有通用模式的实例化:

expense_report = Report("Expense", data_source=EXPENSE, ..other common pattern..)
expense_report_usd = Report("USD Expense", data_source=EXPENSE, format=USD, ..other common pattern..)
expense_report_euro = Report("Euro Expense", data_source=EXPENSE, format=EURO, ..other common pattern..)
...
lot more reports 
...
page1.add(expense_report_usd)
page2.add(expense_report_usd)   # oops, page1 and page2 shared the same instance?!
...
lots of pages
...

【问题讨论】:

  • 关于克隆及其问题。也许您可以使用关键字参数的副本? report_args = dict(title='Some tile', data_source=..., columns=..., format=...) 然后expense_report1 = Report(**report_args), expense_report2 = Report(**report_args)
  • 我不清楚,你使用什么类型的参数,但如果你有需要大量参数的方法,我认为是使用关键字参数的情况。 SO question中的一些很好的例子

标签: python class design-patterns


【解决方案1】:

你为什么不直接使用关键字参数并将它们全部收集到dict

class Report(object):
  def __init__(self, **params):
      self.params = params
      ...

【讨论】:

  • 我们确实使用关键字参数。我添加了一些“附加信息”。
【解决方案2】:

我看不出你为什么不应该只使用偏函数。

【讨论】:

    【解决方案3】:

    如果您的主要问题是类构造函数中的常见参数,可能的解决方案是编写如下内容:

    common_arguments = dict(arg=value, another_arg=anoter_value, ...)
    
    expense_report = Report("Expense", data_source=EXPENSE, **common_arguments)
    args_for_shared_usd_instance = dict(title="USD Expense", data_source=EXPENSE, format=USD)
    args_for_shared_usd_instance.update(common_arguments)
    expense_report_usd = Report(**args_for_shared_usd_instance)
    
    page1.add(Report(**args_for_shared_usd_instance))
    page2.add(Report(**args_for_shared_usd_instance))
    

    更好的命名,可以方便。也许有更好的设计方案。

    【讨论】:

      【解决方案4】:

      我自己找到了一些信息。

      我。 curry -- 将参数与函数关联 « Python recipes « ActiveState Code

      http://code.activestate.com/recipes/52549-curry-associating-parameters-with-a-function/

      查看整个讨论。 Nick Perkins 对“轻量级”子类的评论与我所描述的相似。

      二。 PEP 309 -- 部分函数应用

      http://www.python.org/dev/peps/pep-0309/

      【讨论】:

        【解决方案5】:

        这个问题已经很老了,但这可能仍然对偶然发现它的人有所帮助......

        我创建了一个名为 classical 的小型库来简化此类继承案例(仅限 Python 3)。

        简单示例:

        from classical.descriptors import ArgumentedSubclass
        
        class CustomReport(Report):
            Expense = ArgumentedSubclass(data_source=EXPENSE, **OTHER_EXPENSE_KWARGS)
            Usd = ArgumentedSubclass(format=USD)
            Euro = ArgumentedSubclass(format=EURO)
            PatternN = ArgumentedSubclass(**PATTERN_N_KWARGS)
            PatternM = ArgumentedSubclass(**PATTERN_M_KWARGS)
        
        # Now you can chain these in any combination (and with additional arguments):
        my_report_1 = CustomReport.Expense.Usd(**kwargs)
        my_report_2 = CustomReport.Expense.Euro(**kwargs)
        my_report_3 = CustomReport.Expense.PatternM.PatternN(**kwargs)
        

        在此示例中,实际上没有必要将 ReportCustomReport 类分开,但保持原始类“干净”可能是个好主意。

        希望这会有所帮助:)

        【讨论】:

          猜你喜欢
          • 2020-03-19
          • 2015-07-26
          • 1970-01-01
          • 1970-01-01
          • 2018-10-23
          • 1970-01-01
          • 1970-01-01
          • 2010-09-23
          • 1970-01-01
          相关资源
          最近更新 更多