Python面向对象编程:组合

2024-09-27 00:15:33 发布

您现在位置:Python中文网/ 问答频道 /正文

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

我不确定我是不是搞错了,但当我可以从粒子类继承时,这似乎是难以置信的浪费

我做错什么了吗


Tags: nameselfrestreturndefpublicfunctionsmass
2条回答

您使用哪一种取决于您试图建立的关系

构图并不总是更好的选择。“组合重于继承”经常被重复,因为继承经常被滥用,因为它减少了您需要编写的代码量。然而,这完全是错误的动机,你的决定是建立在这个基础上的

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

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

在你的例子中,从我对粒子物理学非常有限的知识来看,aHadron是一个Particle,所以继承可能更合适。AHadron包含/haveAParticle,所以我认为你试图通过在这里强制合成来对抗晶粒

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

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

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

子类型表示“是a”关系-如果B是a的(适当的)子类型,则可以在任何可以使用a的地方使用B。实际上,Liskov替换原则反过来说:“如果任何接受a的代码都可以接受B,则B是a的适当子类型”。请注意,这并没有说明继承和实现的任何内容,而且这些都不是子类型化所必需的(在理论层面上)

现在,对于静态类型语言,必须使用继承来进行子类型化,period-如果至少有任何实现需要重用的话,您最终会得到实现重用作为奖励

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

现在,从技术上讲,通过继承实现重用是一种组合/委托的形式——您的对象是它自己的类的实例,也是它所有超类的实例,并且无论在您的实例本身或它的类上没有解析的属性都将在父类上查找。“手动”组合/委派的主要区别在于继承受到更严格的限制——例如,您不能在运行时更改委派对象,也不能基于每个实例更改委派对象(嗯……在Python中,实际上可以,从技术上讲,可以在运行时更改实例的类,但实际上这是一个非常的坏主意,从来没有像预期的那样工作过——在这里,就这样做了xD)

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

此外,即使继承仅用于实现重用,继承仍然意味着一种“是一种”关系——即使子类不是它的基的适当子类型(任何不兼容都会破坏适当的子类型,并且没有任何东西阻止您使用不兼容的签名更改某些子类方法),Python也没有任何概念“私有继承”,您的子类将公开其继承的所有接口,这不一定是您想要的(实际上,在执行实现重用时,通常是您不想要的)。当然,如果您决定更改要重用的实现,那么……继承引入了更强的耦合(这是一种轻描淡写的说法)而不是组成/授权

一个典型的“初学者错误”示例是从某个内置集合类型(例如list)继承,并尝试将其“限制”到特定的需要。这从来没有像预期的那样有效,通常需要比使用组合/委派多得多的工作。然后他们意识到这一点(仍然是例如)对于他们自己的用例来说,OrderedDict会是一个更好的基础,然后他们会遇到一个问题,客户机代码现在依赖于继承的list接口…从一开始就使用组合/委派可以避免很多麻烦——将接口限制在相关特性上,而不是泄漏继承的接口,从而保持“实现重用”真正的意义在于:客户机代码永远不应该知道的实现细节

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

长话短说:就像任何其他“黄金法则”一样,当你不了解每种解决方案的利弊,并且每种解决方案都更合适时,支持组合/委派而不是继承只是一条“法则”——瞧,就像任何“黄金法则”一样,你不想盲目地接受和应用它(这将导致愚蠢的设计选择),但是——正如你正确地做的那样——质疑它,直到你理解它的真正含义,甚至不必再去想它

哦,是的:您可能还想了解一下the ^{} magic method(用于委派)和the descriptor protocol以及内置property类型(用于计算属性支持)(提示:Python中不需要这些私有属性/公共访问器)

相关问题 更多 >

    热门问题