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" 经常重复,因为继承经常被滥用,因为它减少了您需要编写的代码量。但是,这完全是错误的决定依据。
如果您有两个 类、A
和 B
,一个粗略的通用指南是:
- 如果
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 访问器
我一直在学习如何在我的 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" 经常重复,因为继承经常被滥用,因为它减少了您需要编写的代码量。但是,这完全是错误的决定依据。
如果您有两个 类、A
和 B
,一个粗略的通用指南是:
- 如果
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).