类模板的习惯用法或设计模式?

1 投票
5 回答
785 浏览
提问于 2025-04-17 10:22

我的代码是用Python写的。假设我有一个比较通用的类,叫做Report(报告)。这个类需要很多参数。

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

而且我们创建了很多Report的实例。这些实例之间并不是完全没有关系。很多报告共享一组相似的参数,只有一些小的不同,比如数据源和列是一样的,但标题不同。

为了避免重复写这些参数,我们可以用一些编程技巧来简化这个结构的表达。我正在寻找一些帮助,以便理清思路,找出合适的写法或设计模式。

如果某个报告的子类需要额外的处理代码,使用子类(subclass)似乎是个不错的选择。比如我们有一个支出报告的子类,叫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 ...

但在很多情况下,子类只是修正了一些参数,并没有额外的处理。这种情况可以用部分函数(partial function)来轻松解决。

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

在某些情况下,甚至没有任何区别。我们只是需要两个相同对象的副本,用在不同的环境中,比如嵌入在不同的页面里。

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

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

在我的代码中,使用了一种不太优雅的技巧。因为我们需要为每个页面创建两个独立的实例,而且我们不想重复写一长串创建报告的参数,所以我们只是克隆(在Python中是deepcopy)第二个页面的报告。克隆的需求并不明显,如果不克隆对象,而是共享一个实例,会在系统中产生很多隐藏的问题和微妙的bug。

在这种情况下,有什么建议吗?是用子类、部分函数还是其他写法?我希望这个结构能够轻便且透明。我对使用子类有点担心,因为这可能会导致子类的丛林(复杂的层级结构)。而且这会促使程序员添加像ExpenseReport那样的特殊处理代码。如果有需要,我宁愿分析代码,看看是否可以进行通用化,并推到Report层。这样Report就能更具表现力,而不需要在底层进行特殊处理。

附加信息

我们确实使用了关键字参数。问题更多在于如何管理和组织这些实例化。我们有很多实例化的情况,且有共同的模式:

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
...

5 个回答

0

如果你主要的问题是类构造函数中的常见参数,一个可能的解决办法是写成下面这样:

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))

更好的命名可以让代码更方便。也许还有更好的设计方案。

0

我觉得你完全可以使用一个部分函数,没有什么理由不这样做。

2

你为什么不直接使用关键字参数,把它们都收集到一个 dict 里呢?

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

撰写回答