【问题标题】:Trying to understand object oriented programming. How to use object composing?试图理解面向对象的编程。如何使用对象组合?
【发布时间】:2020-07-17 16:54:53
【问题描述】:

成为下面定义的Point()Circle() 两个类:

class Point:
    def __init__(self, x, y):
        self._x = x
        self._y = y

    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, x):
        self._x = x

    @property
    def y(self):
        return self._y

    @y.setter
    def y(self, y):
        self._y = y

    def __repr__(self):
        return f"{self._x}, {self._y}"

    def move(self, x, y):
        self._x = x
        self._y = y
        return self._x, self._y


class Circle:
    def __init__(self, radius, x, y):
        self._radius = radius
        self.x = x
        self.y = y

    def move(self, x, y):
        Point.move(self, x, y)

    def __repr__(self):

        return f"radius = {self._radius}, \nx = {self.x},\ny = {self.y}"


point1 = Point(12, 3)
print(point1)
point1.y = 0
print(point1)

point1.move(55, 7)
print(point1)

circle1 = Circle(4, 1, 1)
print(circle1)
circle1.move(2, 2)
print(circle1)

我尝试在 Circle 中开发方法 move(x,y),从类 Point 调用方法 move(x,y),而不使用继承。首先初始化一个对象:

circle1 = Circle(4,1,1)

但是当你使用circle1.move(2,2)时,圆圈位置仍然是(1,1):怎么了? 我想用_x和_y来模拟私有变量!

【问题讨论】:

  • 您是否将circle1.xcircle1._x 进行了比较?另外,虽然我认为这只是任意示例代码,但值得指出的是,在 Python 中像这里一样使用属性是一种不好的做法。
  • python 中不需要 setter/getter 函数。这不是 java :)
  • 你可能想要self.center = Point(x, y)而不是self.x = x ; self.y = y
  • 拥有property 没有任何好处,因为它所做的只是提供对隐藏属性的读/写访问权限。直接使用xy 属性即可。 property 的要点是,您可以在如果需要添加getter/setter,而不会破坏属性的外观。

标签: python python-3.x oop


【解决方案1】:

@jsbueno 有最明智的设计正确答案。如果您继续使用 Point.move(),您将使用 move() 作为静态方法,而不是实例方法。这会将 Circle 'self' 对象传递给 Point.move() 方法。如果您按照其他帖子的建议将 self.x 更改为 self._x 等,它会起作用,但这是一个非常糟糕的设计。它之所以有效,是因为它将 Point 内的“self”变量与 x 和 y 一起视为第三个参数,而不是实际的 Point 对象。例如,您可以将 Point.move() 方法重写为类似的方法,该方法仍可用于移动 Circle 对象,但设计不佳:

def move2(foo,x,y):
    foo._x = x
    foo._y = y
    print(foo._x,foo._y)
    return foo._x,foo._y

以下是我在尝试回答您的问题时发现静态方法与类方法的区别的链接:) CLASS METHOD VS STATIC METHOD 2020

【讨论】:

    【解决方案2】:

    问题在于你没有使用“组合”——你只是在你的循环类的一个方法中随机调用Point.move。该调用将失败,因为如果将其放在其他任何地方都会失败:Point.move 是一个实例方法,它需要一个点的实例才能工作。

    对于“组合”,您需要拥有作为您的类属性的另一个类的实例 - 然后在需要时调用这些实例上的方法。

    例如,您的“圆”可能有一个“.center”属性,即一个点。然后,您只需在实例中调用 circle.center.move - 或者,如果您想要在 circle 类本身中使用 .move 方法:

    
    class Circle:
        def __init__(self, radius, x, y):
            self._radius = radius
            self.center = Point(x, y)
    
        def move(self, x, y):
            self.center.move(x, y)
    
        def __repr__(self):
            return f"Circle center at ({self.point}), radius: {self.radius}"
    

    你想做什么

    您的代码尝试调用Point 中的方法,传递Circle 的实例。虽然 Python 3 允许这样做,但这完全是巧合,它几乎可以工作(如果 Circle 中的“x”和“y”名称与“Point”中的名称相同,那么它会像 Dani 的回答中所展示的那样工作) - 但是这不是“OOP”也不是“Composition”,并且不会引发运行时错误,因为 Python 3 将类中的实例方法视为函数。

    “圆”不是“点”。考虑几何,你可以说“Circle”和“Point”有一些属性和方法——Python允许你通过使用多重继承来共享这些,我们称之为“mixins”——所以你可以有一个“LocationMixin”类这将具有“x”和“y”属性,以及“move”方法,并且是“Point”和“Circle”的祖先,这将起作用。

    简化代码

    除了你的组合问题,重要的是要注意,在 Python 中尝试将属性设为“私有”并不重要,并为公共消耗定义 getter 和 setter - 如果您的点类简单地写成:

    class Point:
        def __init__(self, x, y):
            self.x = x
            self.y = y
    
        def __repr__(self):
            return f"{self.x}, {self.y}"
    
        def move(self, x, y):
            self.x = x
            self.y = y
            return self.x, self.y
    

    如果您想验证 x 和 y 是否设置为数字或验证值范围,getter 和 setter 将是有意义的 - 否则,它们只是多余的。

    有趣的是,Python 设计了它的“属性”,以便您可以更改属性以具有 getter 和 setter,并在稍后添加这些验证,而不会破坏与类的先前版本的任何兼容性 (Point) .

    【讨论】:

      【解决方案3】:

      您没有操纵正确的变量。
      你圈课使用self.xself.y,但是在对Point.move(self, x, y)的调用中你操纵self._xself._y而不是

      【讨论】:

      • 正确。如何在整个解码中使用 _x 和 _y?
      • @Laurinda Souza 见 Dani Mesejo 的回答。只需将 circle 的 __init__() 中的 self.x 更改为 self._x (y 相同)
      【解决方案4】:

      Circle 类更改为:

      class Circle:
          def __init__(self, radius, x, y):
              self._radius = radius
              self._x = x
              self._y = y
      
          def move(self, x, y):
              Point.move(self, x, y)
      
          def __repr__(self):
              return f"radius = {self._radius}, \nx = {self._x},\ny = {self._y}"
      

      示例

      circle1 = Circle(4, 1, 1)
      print(circle1)
      circle1.move(2, 2)
      print(circle1)
      

      输出

      radius = 4, 
      x = 1,
      y = 1
      radius = 4, 
      x = 2,
      y = 2
      

      否则,当执行Point.move 时,会创建两个新字段(_x, _y),请参阅:

      circle1 = Circle(4, 1, 1)
      circle1.move(2, 2)
      print(circle1._x, circle1._y)
      

      输出

      2 2
      

      【讨论】:

      • 您将 _x_y 设为“private” - 这在语言中不是一个好的做法,但没有解决在实例中调用类方法的问题不相关的圈子,好像它应该起作用一样——它起作用只是巧合。
      • @jsbueno 这不是巧合。每个 Python 类方法都接收 self 作为第一个参数,如果传递的 self 对象具有该方法需要工作的字段,它将起作用。如果它像鸭子一样走路,那就是鸭子......
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-20
      • 1970-01-01
      • 2015-03-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多