【问题标题】:Python Object-Oriented Programming: CompositionPython 面向对象编程:组合
【发布时间】:2020-01-05 16:52:19
【问题描述】:

我一直在学习如何在我的 python 编程中实现组合,但我很难理解为什么它比继承更受欢迎。

例如,到目前为止,这是我的代码:

class Particle:
   # Constructor (public)
   def __init__(self, _name, _charge, _rest_energy, _mass, _velocity):
       # Attributes (private)
       self.__name = _name
       self.__charge = _charge
       self.__restEnergy = _rest_energy
       self.__mass = _mass
       self.__velocity = _velocity

   # Getter functions (public)
   def getName(self):
       return self.__name

   def getCharge(self):
       return self.__charge

   def getRestEnergy(self):
       return self.__restEnergy

   def getMass(self):
       return self.__mass

   def getVelocity(self):
       return self.__velocity

   # Setter procedures (public)
   def setName(self, _name):
       self.__name = _name

   def setCharge(self, _charge):
       self.__charge = _charge

   def setRestEnergy(self, _rest_energy):
       self.__restEnergy = _rest_energy

   def setMass(self, _mass):
       self.__mass = _mass

   def setVelocity(self, _velocity):
       self.__velocity = _velocity


class Quark:
   # Constructor (public)
   def __init__(self, _name, _charge, _strangeness):
       # Attributes (private)
       self.__name = _name
       self.__charge = _charge
       self.__strangeness = _strangeness

   # Getter functions (public)
   def getName(self):
       return self.__name

   def getCharge(self):
       return self.__charge

   def getStrangeness(self):
       return self.__strangeness


class Hadron:
   # Constructor (public)
   def __init__(self, _name, _charge, _rest_energy, _mass, _velocity, _quarks):
       # Attributes (private)
       self.__particle = Particle(_name, _charge, _rest_energy, _mass, _velocity)
       self.__quarks = _quarks

   # Getter functions (public)
   def getParticle(self):
       return self.__particle

   def getQuark(self):
       return self.__quarks

   def getStrangeness(self):
       _quarks = self.__quarks
       _strangeness = 0
       for _quark in _quarks:
           _strangeness += _quark.getStrangeness()
       return _strangeness

   def getRelCharge(self):
       _quarks = self.__quarks
       _relCharge = 0
       for _quark in _quarks:
           _relCharge += _quark.getCharge()
       return _relCharge

   def getName(self):
       return self.__particle.getName()

   def getCharge(self):
       return self.__particle.getCharge()

   def getRestEnergy(self):
       return self.__particle.getRestEnergy()

   def getMass(self):
       return self.__particle.getMass()

   def getVelocity(self):
       return self.__particle.getVelocity()

   # Setter functions (public)
   def setName(self, _name):
       self.__particle.setName(_name)

   def setCharge(self, _charge):
       self.__particle.setCharge(_charge)

   def setRestEnergy(self, _rest_energy):
       self.__particle.setRestEnergy(_rest_energy)

   def setMass(self, _mass):
       self.__particle.setMass(_mass)

   def setVelocity(self, _velocity):
       self.__particle.setVelocity(_velocity)

我不确定我是不是在这里搞错了,但是当我可以从 Particle 类继承时,这似乎非常浪费。

我做错了吗?

【问题讨论】:

  • 完全不相关,但那些 getter/setter 完全没用。 Python 对计算属性有很强的支持,所以只需将你的属性公开(嗯,那些是公共类接口的一部分,但在你的示例中,所有属性都是这种情况),你总是可以选择如果需要,稍后将一些转换为计算属性(不更改类接口)。 FWIW,请注意,即使在 Java 领域,为所有属性添加公共 getter/setter 也被认为是一种设计味道......

标签: python oop composition


【解决方案1】:

您使用哪种取决于您要建模的关系。

构图并非总是是更好的选择。 “组合优于继承”经常被重复,因为继承经常被滥用,认为它减少了您需要编写的代码量。但是,这完全是您做出决定的错误动机。

如果您有两个课程,AB,粗略的一般指南是:

  • 如果B 是一个 A,您可能需要继承。
  • 如果B 有一个 A,您可能需要组合。

在您的情况下,根据我对粒子物理学的极其有限的了解,Hadron Particle,因此继承可能更适合。 Hadron包含/ Particle,所以我认为你试图通过在此处强制组合来违反规定。

【讨论】:

  • 嗯,是的,我同意;我知道这些关系,但我想看看我是否可能。不过,我想我会坚持继承这一点;谢谢。
  • @92carmnad 这当然是可能的,但你最终会得到很多像这里一样的“委托方法”。在所有情况下,这都必然是一件坏事,但我认为如果一个类的绝大多数只是委托给它的一个成员,那就是代码的味道。
【解决方案2】:

Carcigenicate 已经提供了一个很好的答案。只是想添加一些关于这部分的内容:

我很难理解为什么(组合)比继承更受欢迎。

实际上,它是关于组合/委托,而不仅仅是组合(它本身不提供相同的功能)。关键是继承实际上有两个目的:子类型化和实现重用。

子类型表示“是”关系 - 如果 B 是 A 的(正确的)子类型,则可以在任何可以使用 A 的地方使用 B。实际上,里氏替换原则反过来说:“B 是正确的如果任何接受 A 的代码可以接受 B 而不是 A 的子类型”。请注意,这并没有说明继承或实现的任何内容,并且(在理论上)子类型都不需要这些。

现在使用静态类型语言,您必须使用继承来进行子类型化,句号 - 如果至少有任何实现可以重用,那么您最终会获得实现重用作为奖励。

动态类型化的 Python 不需要继承来进行子类型化(当然,只要使用您的对象的代码不进行任何类型检查)——只要有一个兼容的接口就足够了。所以在 Python 中,继承主要是关于实现重用。

现在从技术上讲,通过继承重用实现是一种组合/委托形式 - 您的对象是它自己的类的实例,也是它所有超类的实例,并且无论您的实例本身还是它的类都没有解析任何属性在父类上查找。 “手动”组合/委托的主要区别在于继承受到更多限制 - 例如,您不能在运行时更改委托给谁,也不能在每个实例的基础上更改(嗯......在 Python 中,您实际上 可以,从技术上讲,在运行时更改实例的类,但实际上这是一个 非常 坏主意,并且永远不会按预期工作 - 在这里,完成了 xD)。

所以wrt/实现重用,继承是一种受限且主要是静态的组合/委托形式。这对于很多用例来说都很好——通常是框架或多或少需要继承的抽象基类等——但其他一些情况最好用更动态的解决方案来解决(典型的例子是状态和策略设计模式,但还有很多其他的)。

此外,即使仅用于实现重用,继承仍然意味着“是”关系 - 即使子类不是其基类的正确子类型(任何不兼容性都会破坏正确的子类型,并且没有什么可以阻止您更改您的一些具有不兼容签名的子类方法) - 并且 Python 没有任何“私有继承”的概念,您的子类将公开它的所有继承接口,这不一定是您想要的(实际上通常是您不想要的只是做实现重用)。当然,如果您决定更改要重用的实现,那么……继承引入了比组合/委托更强大的耦合(这是轻描淡写的)。

一个典型的“初学者错误”示例是从某些内置集合类型(例如list)继承并尝试将其“限制”到他们的特定需求。这从来没有真正按预期工作,并且通常最终需要比使用组合/委托更多的工作。然后他们意识到(仍然例如)OrderedDict 对于他们自己的用例来说是一个更好的基础,然后他们遇到了客户端代码现在依赖于继承的list 接口的问题......使用组合/委托从一开始就可以避免很多痛苦——通过将接口限制为相关功能而不是泄漏继承的接口,从而保持“实现重用”的本质:客户端的实现细节代码永远不应该知道。

核心问题实际上是有很多非常非常糟糕的“OO 101”文本将继承作为关键 OO 特性之一(它不是——真正的“关键 OO 特性”是封装——不要与数据混淆)隐藏 BTW - 和基于类型的多态调度),导致初学者试图过度使用继承而没有意识到还有其他 - 有时更好 - 解决方案。

长话短说:就像任何其他“黄金法则”一样,当您不了解每种解决方案的优缺点并且每个解决方案都更合适时,支持组合/委托而不是继承只是一个“规则” - IOW,就像任何“黄金法则”一样,你不想盲目地接受和应用它(这会导致愚蠢的设计选择),但是 - 正如你所做的那样 - 质疑它,直到你了解它的真正含义并且不要'甚至不必再考虑了。

哦,是的:您可能还想了解the __getattr__ magic method(用于委托)- 和the descriptor protocol 和内置property 类型(用于计算属性支持),同时您也在使用它(提示:您不知道)在 Python 中不需要那些私有属性/公共访问器)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多