【问题标题】:Classes versus parameterized instances类与参数化实例
【发布时间】:2012-10-12 22:34:09
【问题描述】:

假设我有一个类层次结构。通过为实例提供额外的参数,我理论上可以通过单个类来维护其功能。这是一个例子(超级做作,但我想保持简单):

class Base:
  def __init__(self, a):
    self.a = a
  def f(self, x):
    raise NotImplemented # needs to be defined in subclass

class Mult(Base)
  def f(self, x):
    return self.a * x

class Add(Base):
  def f(self, x):
    return self.a + x

m = Mult(5)
a = Add(7)
m.f(10)
a.f(20)

上面的代码可以重构为:

class Compute:
  def __init__(self, a, func):
    self.a = a
    self.func = func
  def f(self, x)
    return self.func(self.a, x)

m = Compute(5, operator.mult)
a = Compute(7, operator.add)

我知道对于这个愚蠢的例子,它没有任何区别。所以除了理解我的意思,请不要去想它。

我想知道在为我在现实生活中遇到的各种情况做出选择时应该考虑哪些因素?换句话说,使用类与参数化实例的优缺点是什么?

【问题讨论】:

  • 您能否详细说明您正在尝试做什么 IRL?听起来你希望你的类能够执行任意函数,但也有一些预定义的。 IMO 这应该是一个具有存储在字典中的预定义函数的单个类。
  • @poorsod 我需要代表各种各样的业务规则,它们有很多重叠。我想知道我是否应该创建一个类层次结构,或者只是创建使用正确功能实例化的实例。

标签: python oop class design-patterns architecture


【解决方案1】:

在第一个示例中,您为客户端代码提供了执行一组固定操作的固定方式。乘法很容易,但如果你想除法,那就倒霉了。

在第二个示例中,您将实现细节推送到客户端。您现在拥有一个完整的与操作无关的计算框架。这需要对客户端代码有更多了解。

因此,真正不同的是抽象级别——您希望您的客户了解多少。如果使用operator.mult 对客户端很重要,而不是其他一些逻辑(例如,XML-RPC 到乘法服务器),那么第二个选项似乎是合适的。如果客户比您更了解如何处理这两个数字,并且您只想提供一个框架(某种包装器),那么第二种选择会更好。如果你只是想让人们添加和乘法,第一个选项更好。

【讨论】:

  • 谢谢,这是一个有趣的观察。我确实认为,即使我不向客户端提供任何知识,我仍然可能会遵循第二种选择:我可以预先实例化对象 ma,这样就客户端而言只有几个变体可供选择,没有透明度。
  • @max 我所说的“客户端”是实例化你的类的任何东西。如果您私下执行此操作,则客户端是您自己的代码,与类定义位于同一位置。在这种情况下,选择不那么重要(只是内部实现细节),但同样的考虑仍然适用。
【解决方案2】:

这真的取决于类做什么。在您的示例中,类中的逻辑是如此之小,很明显您刚刚为方法提供了包装器作为类。并且拥有计算类更有意义。

在现实生活中,您应该问自己cohesive 是您想要组合在一起的不同功能。虽然单一责任很重要,但你不需要接受这种荒谬的广告

【讨论】:

  • 就我而言,我觉得Compute(5, operator.mult)Mult(5) 更优雅,因为前者以统一的方式处理所有参数,而后者将真正的两个参数拆分为一团糟两种完全不同的句法结构。但我担心这个论点太过分会导致我完全避免类,并将所有内容作为参数传递。我应该在什么时候停下来?
  • 当班级开始做太多不相关的事情时。 Bob Martin 将单一职责原则定义为“类应该有一个改变的理由”。无论如何,如果您有特定情况,您可以随时在这里询问:)
【解决方案3】:

我想这取决于您是否拥有有关转换的所有信息(?),您将在构建时对可用的对象进行操作。如果你不这样做,你将很难调用更复杂的构造函数。

此外,在将其提交到特定路径之前,保留对“基本”对象执行操作的选项通常很有帮助,即使在最初设计类时并不明显。

总的来说,我发现使用所需的最少参数创建一个构造函数并通过方法实现任何其他功能是最好的解决方案。

本着随机示例的精神,考虑:

参数化构造函数

class Image:
    # Some implementation

class Text:
    def __init__(self, image, whitelistChars):
        # This uses OCR to extract the text from an image then filters out 
        #   non-whitelisted characters
        #
        # image is an Image
        # whitelistChars is an interable containing characters that should 
        #   not be filtered out

im = Image('scan.tif')
t = Text(im, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ134567890');

基本构造函数

class Text:
    def __init__(self, image):
        # This uses OCR to extract the text from an image
        #
        # image is an Image


    def filter(self, whitelistChars):
        # This filters out non-whitelisted characters
        #
        # whitelistChars is an interable containing characters that should 
        #   not be filtered out

im = Image('scan.tif')
t = Text(im)
# <-- Are you interested in doing anything to the Text object t here?
t.filter('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ134567890')

在“基本构造函数”实现中,您可以选择使用Text 对象t,然后再去除未列入白名单的字符。也许这有用?也许更重要的是,也许这在未来会有用?

如果您可以自信地说“不”,它没有用,而且永远不会有用,那么我认为“参数化构造函数”很好。如果有的话,它消除了对额外的filter 调用的需要。

但是,如果您希望重用代码,则在转换 t 或什至只是检查它之前保持打开选项(在这种情况下过滤文本)通常会很有帮助。

(我的 0.02 美元)

【讨论】:

  • 我同意您的 cmets,但您正在分析与我面临的选择不同的选择。我在参数化方法(__init__ 或其他方法并不重要)与类层次结构(其中为每个类预定义参数)之间进行选择。对您而言,已经做出了针对类层次结构的选择,您正在决定参数是属于 __init__ 方法还是其他方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-10
  • 1970-01-01
  • 1970-01-01
  • 2011-05-16
  • 2021-04-13
  • 1970-01-01
相关资源
最近更新 更多