Python 面向对象编程:组合

Python Object-Oriented Programming: Composition

我一直在学习如何在我的 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)

我不确定我是不是弄错了地方还是怎么的,但当我可以从粒子 class.

继承时,这似乎非常浪费

我是不是做错了什么?

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

合成并不总是更好的选择。 "Composition over inheritance" 经常重复,因为继承经常被滥用,因为它减少了您需要编写的代码量。但是,这完全是错误的决定依据。

如果您有两个 类、AB,一个粗略的通用指南是:

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

在你这里的情况下,根据我对粒子物理学极其有限的知识,Hadron Particle,因此继承可能更合适. Hadron 包含 /Particle,所以我认为你正在努力反对通过在此处强制组合来获得谷物。

Carcigenicate 已经提供了很好的答案。只是想补充一下这部分:

I'm struggling to understand why (composition) is preferred over inheritance.

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

子类型表示 "is a" 关系 - 如果 B 是 A 的(适当的)子类型,则可以在任何可以使用 A 的地方使用 B。实际上,里氏替换原则反过来说:"B is a proper subtype of A if any code accepting an A can accept a B instead"。请注意,这并没有说明任何关于继承或实现的内容,并且子类型化都不需要(在理论上)。

现在使用静态类型语言,您 必须 将继承用于子类型化,期间 - 如果至少有任何实现可以重用,您最终会得到实现重用作为奖励。

OTHO Python 被动态类型化不需要继承子类型(只要使用 object 的代码当然不做任何类型检查)——只要有一个兼容的接口就是足够的。所以在 Python 中,继承主要是实现重用。

现在从技术上讲,通过继承实现重用是 composition/delegation 的一种形式 - 您的 object 是它自己的 class 的一个实例,但也是它的超级 class ]es,并且任何未在您的实例本身或它的 class 上解析的属性都将在 parent classes 上查找。与 "manual" composition/delegation 的主要区别在于继承受到更多限制 - 你不能在运行时或 per-instance 基础上更改委托给谁(例如......在 Python,从技术上讲,您实际上 可以 在运行时更改实例的 class,但实际上这是一个 非常 的坏主意,永远不会按预期工作 - 来过这里,完成了 xD)。

因此,对于实现重用,继承是 composition/delegation 的一种受限且主要是静态的形式。这对于很多用例来说都很好——通常是需要继承的框架 more-or-less-abstract 基础 classes 等——但是其他一些情况可以用更动态的解决方案更好地解决(典型的例子是状态和策略设计模式,但还有很多其他模式)。

此外,即使仅用于实现重用,继承仍然意味着 "is a" 关系 - 即使 child class 不是其基类的适当子类型(任何不兼容将破坏正确的子类型化,并且没有什么可以阻止您更改某些具有不兼容签名的 subclass 方法)-,并且 Python 没有任何 "private inheritance" 的概念,您的 child class 将公开它所有继承的接口,这不一定是您想要的(实际上,在进行实现重用时,您通常不想要的)。当然,如果您决定更改要重用的实现,那么...继承引入了比 composition/delegation.

更强的耦合(这是一种轻描淡写的说法)

一个典型的 "beginner error" 示例是继承某些内置集合类型(例如 list)并尝试 "restrict" 它以满足他们的特定需求。这从来没有真正按预期工作,通常最终需要比使用 composition/delegation 更多的工作。然后他们意识到(仍然是例如)OrderedDict 对于他们自己的用例来说是一个更好的基础,然后他们遇到了客户端代码现在依赖于继承的 list 接口的问题......使用composition/delegation 从一开始就可以避免这里的很多痛苦 - 通过将接口限制在相关功能而不是泄漏继承的接口,从而使 "implementation reuse" 保持真实的状态:一个客户端代码不应该知道的实现细节。

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

长话短说:与其他 "golden rule" 一样,当您不了解每个解决方案的优缺点时,composition/delegation 优于继承只是一个 "rule"当每一个都更合适时 - IOW,就像任何 "golden rule" 一样,你不想盲目地接受和应用它(这会导致愚蠢的设计选择),但是 - 正如你所做的那样 - 质疑它直到你了解它的真正含义,甚至不必再考虑它。

哦,是的:您可能还想了解 the __getattr__ magic method (for delegation) - and the descriptor protocol 和内置 property 类型(用于计算属性支持)(提示:您不需要那些Python).

中的私有属性/public 访问器