【问题标题】:OOP : Trying to design a good class structureOOP:试图设计一个好的类结构
【发布时间】:2017-02-16 18:23:48
【问题描述】:

跟进我发布的代码 sn-p 审核问题:https://codereview.stackexchange.com/questions/143576/oop-designing-class-inheritances

这是家庭作业,所以我不是要求为我做,而是为我澄清,以便我理解。

以下是作业中关于我必须编写的程序的内容:

必填: 编写一个 OO Python 程序,展示您的子类如何从超类继承您可以拥有一个类(首选但不是必须)或两个或多个超类。

我要做的是编写一个 oo python 程序,显示由一个类或两个或多个超类继承的子类。该程序必须与形状有关。作为一个例子,我的意思是:

形状:方形

属性:

  • 长度
  • 宽度

方法:

  • 地区
  • 周长

我当然有更多的形状,但我从中找到了所有形状的共同属性和方法,并创建了超类和子类。

我的超类是:2dShapes、circles 和 3dShapes。我的子类就像长度和宽度。我的方法是面积和周长。注意我在这一点上漫无目的。下面的代码 sn-p 没有显示这一点,而是我正在考虑为属性和方法创建一个超类,而不是为形状创建子类?也许吧?

问题:这是一个好的类结构吗?有没有更好的方法来组织这里的类?这是我正在考虑如何执行此操作的示例。

class Shapes(attributes):
    def __init__(self):
        attributes.__init__(self)
        # not sure how to go on to make the attributes like height and length, radius ect. Maybe like this???
    def height(self):
        raise NotImplementedError # again this program is not supose to actually do anything. The program is just for us to understand inheritance with classes and super classes.

class 2dShapes(Shapes):
    class Square(self):
        def height(self):
            # ???

所以在这一点上,我很困惑从哪里开始。另外我对python很陌生,所以对我温柔一点:p

【问题讨论】:

  • 这是一个继承自另一个类的类。你对什么感到困惑?
  • 对家庭作业保持开放和建设性的态度很好:) 但是您可能必须使您的问题更加本地化。你有一个具体的问题吗?另外:您是在说“我的子类就像长度和宽度”,但这没有多大意义,而且这不是您的 sn-p 所表示的。另外:您真的想将这些类嵌套在底部块中吗?最后,你知道如果你这样写attributes 本身应该是一个类吗?
  • @AndrasDeak 谢谢。我的具体问题是如何在不重复的情况下为属性和方法进行良好的类设计。
  • 请澄清问题。
  • 我相信标准的方法是拥有一个 2d 和 3d 形状的类(如果你愿意,可以称之为超类)。这些可以细分为圆形、矩形(在 2d 的情况下)和球体、八面体(在 3d 的情况下)。所有这些类都有方法,有些是共同的,有些不是。这应该确定这些方法的定义位置。

标签: python python-3.x oop inheritance


【解决方案1】:
class Vehicle(object):
    #class variable shared between all instances of objects. 
    number_of_vehicles = 0 

    def __init__(self,length,width,color,wheels):
        self.length = length
        self.width = width
        self.color = color
        self.wheels = wheels
        Vehicle.number_of_vehicles += 1 #increasing class varaible count

    def get_length(self):
        print("I am %d meters long!"%self.length)

    def get_wdith(self):
        print("I am %d meters wide!"%self.width)

    def get_color(self):
        print("My color is %s!"%self.color)

    def get_wheels(self):
        print("I have %d number of wheels"%self.wheels)

    #calling my methods so I don't need to call each of their own
    def get_stats(self):
        self.get_length()
        self.get_wheels()
        self.get_wdith()
        self.get_color()

    def honk(self):
        print("beep beep")

class Car(Vehicle):
    def vroom(self):
        print("Cars go vroom vroom")

class Cooper(Car):
    def drift(self):
        print("Mini Coopers can drift well")

class Airplanes(Vehicle):
    def fly(self):
        print("weeeeee I'm flying")

class Tank(Vehicle):
    #custom init because tanks have guns!~ 
    #taking the gun size and tossing the rest of the arguments to the parent. 
    #if the parent doesn't find a __init__ it will keep going up until one is found or unelss we call it. 
    #Here we made a new __init__ so it doesn't go looking for one, but we call super() which is calling for the 
    #parent's __init__
    def __init__(self,gun_size,*args):
        self.gun_size = gun_size
        super(Tank,self).__init__(*args)
    def fire(self):
        print("pew pew pew")

    #I have my custom get_stats but still calls parent's one so I don't repeat code.
    def get_stats(self):
        print("my gun is this big: %d " %self.gun_size)
        super(Tank,self).get_stats()

a = Cooper(150,150,"blue",4)
a.drift()
a.vroom()
a.honk()
a.get_stats()
print(a.number_of_vehicles)

b = Airplanes(200,150,"white",2)
b.fly()
print(b.number_of_vehicles)

c = Tank(500,500,250,"Green",18)
c.fire()
print(c.number_of_vehicles)
c.get_stats()

输出:

Mini Coopers can drift well
Cars go vroom vroom
beep beep
I am 150 meters long!
I have 4 number of wheels
I am 150 meters wide!
My color is blue!
1                     #vehicle count
weeeeee I'm flying    # start of plan section
2                     #vehicle count
pew pew pew           #start of tank section
3                     #vehicle count
my gun is this big: 500  
I am 500 meters long!
I have 18 number of wheels
I am 250 meters wide!
My color is Green!

所以这篇文章的重点是向您展示继承关系。

我们有一个名为Vehicle 的基类,它是object 的子类。如果您愿意,请不要担心object,您可以阅读它。

Vehicle 类具有所有车辆都会具有的一些属性,例如长度、宽度、颜色、车轮。它还有一个名为number_of_vehicles 的类变量,它跟踪车辆有多少对象实例,基本上是我们“制造”了多少车辆。我们还有一些类方法,可以访问和使用我们在__init__ 中定义的属性。您可以对它们进行数学运算,但现在我们只是将它们用作打印语句以显示它们有效。我们有一个特殊的类方法,它调用同一个类中的其他方法。所以get_stats 调用实例中的其他get_x 方法。这使我可以从我的对象中仅调用“一个”方法来调用这 4 个方法,请参阅a.get_stats()。我们仍然可以像get_color 一样自己调用其他方法。

我们有一个名为 Car 的子类,它是一个车辆,所以我们继承它。只有汽车可以去 vroom,但所有汽车都可以去 vroom,所以我们只有汽车级别的 vroom 方法。诀窍是想想这个类有什么是只有这个类的实例才有的,如果没有,我可以把它放在父类中吗?所有车辆都有轮子等等,但并非所有车辆都可以行驶(仅用于此示例)。

我们有一个 Car 的子类,即 cooper(迷你 cooper),只有它可以漂移(再一次在这个例子中,只有在现实生活中,漂移方法才适用于车辆,因为所有车辆都可以牵引,但请耐心等待) .所以这个库珀是唯一可以漂移的汽车,所以它在下面而不是在 Car 类中。

坦克的子类很有趣。在这里,我们有了车辆的基本知识,但我们有了新的东西,一把枪!所以我们的车辆类不能处理枪,所以我们必须创建一个新的__init__ 方法。我们分配对象变量gun_size,然后将坦克的其余属性传递给Vehicle 的__init__,因为其余属性与Vehicle 相同。我们打电话给super(Tank,self).__init__(*args),这基本上是在说,我是坦克,这就是我,请处理我的其余属性,我的父母。由于坦克有枪这个特殊属性,所以我们要修改我们的get_stats方法,来处理gun_size属性,但是坦克的其他属性和载具一样,所以我们只调用我们的父母在我们处理完枪支后处理剩下的事情。

现在我知道这是一个非常愚蠢的例子,但我希望你能在这里找到一些有用的信息。我还没有涉及其他专业点,但这是一个起点。所以回到你原来的问题。想想抽象,最高级别是一个形状,然后矩形是一种形状,所以他们会继承它。正方形是特殊的矩形,所以它们会继承矩形等等。

如果您有任何问题,请不要犹豫。

【讨论】:

    【解决方案2】:

    注意不要将“属性”与“方法”混淆。有一个 @property 语法将属性访问包装在方法调用中,但您现在应该忽略它。

    class Shapes(attributes):
        def __init__(self): 
    

    对于这一部分,只需吸收这里的论点。例如:

    def __init__(self, *args, **kwargs):
        pass
    

    原因是,您仅使用 Shapes 将它们传递给子类。 *args 吸收命名参数列表,而**kwargs 吸收字典。所以这个init()会接受my_shapes_instance = Shapes(length, width, height),因为它有*args,它也会接受Shapes(length, width, height, {'cost': '10'}),因为它也有**kwargs

    如果它是__init__(length, width, height) 而你通过了(length, width, height, color) 那么它就行不通了。但是如果你使用*args 那么它会接受任何东西。它会使用所有这些论点吗?只有当你定义它的时候。

    您现在可以忽略 **kwargs,因为您没有使用字典初始化这些对象。

    attributes.__init__(self)
            # not sure how to go on to make the attributes like height and length, radius ect. Maybe like this???
    
        def height(self):
            raise NotImplementedError # again this program is not supose to actually do anything. The program is just for us to understand inheritance with classes and super classes.
    

    你在上面所做的是定义一个方法“height”,而不是一个属性“height”。你想要的更像是这样的:

    def __init__(self, height):
        self.height = height
    

    更好的是,但在 Square 子类中这样做:

    class Square(Shapes):
        def __init__(self, height, *args, **kwargs):
            super(Square, self).__init__(height, *args, **kwargs)
            self.height = height
    

    现在您可以使用 Rectangle 子类化 Square,同时添加新的 args。只需遵循与上述类似的 init 模式即可。 Rectangle 不需要您添加 height 方法,因为它已经可以从父级获得。

    【讨论】:

      【解决方案3】:

      我不想给出太具体的答案,因为作业,但这里有一个例子,我认为它可能会让你朝着正确的方向前进。

      在面向对象的编程中,有一个多态的概念:当许多不同子类的实例被一些共同的超类关联时。您经常看到它被解释为“B 是 A”,例如“梨是水果”、“猫是动物”、“三角形是形状”。子类都共享父类中存在的一组通用方法和成员,但它们对这些方法的实现可能会有所不同。

      这是一个动物的例子(抱歉,C 风格,不是 Python 人)。 hearNoise() 方法接受动物,但如果将子类型传递给它,它也能正常工作:

      abstract class Animal {
          abstract String makeNoise();
      }
      
      class Cat extends Animal {
          String makeNoise() {
              return "Meow!";
          }
      }
      
      class Dog extends Animal {
          String makeNoise() {
              return "Woof!";
          }
      }
      
      void hearNoise(Animal a) {
          println(a.makeNoise());
      }
      
      int main() {
          hearNoise(new Cat()); //returns "Meow!"
          hearNoise(new Dog()); //returns "Woof!"
      }
      

      同样的原则也适用于几何形状。他们都有共同的方法和成员:他们的周长,他们的面积,他们的颜色等。当你有一个形状时,你可以100%确定你将能够调用一个方法来计算周长。但是,具体形状子类处理周长计算的实现方式因形状而异。

      【讨论】:

        【解决方案4】:

        使用继承的一个重要想法是能够重用代码。如果您有大量对多个类相同的函数,则拥有一个包含这些函数的父类允许您只编写一次,同时修改它们。例如,如果您想实现以下形状的类:Square、Rectangle 和 Parallelogram,您可以创建一个父类:Quadrangle,其中包含通用面积或周长函数:

        class quadrangle(object):
            def __init__(self, length, width):
                self.length = length
                self.width = width
        
            def area(self):
                return self.length * self.width
        
            def perimeter(self):
                return 2*self.length + 2*self.width
        
        class square(quadrangle):
            def __init__(self, length):
                super(square, self).__init__(length, length) #creates a quadrangle with equal lenght and width
        
        class rectangle(quadrangle): #renamed for convienience sake but otherwise the same
            pass
        
        class parallelogram(quadrangle):
            def __init__(self, length, width, angle): #angle in radians
                self.angle = angle
                super(parallelogram, self).__init__(length, width)
        
            def perimeter(self): #override parent's perimiter
                #do some math
                return 5 

        【讨论】:

          【解决方案5】:

          让我们从 Shapes 类开始。在 __init__ 中,您必须设置属性。首先,您需要除 self 之外的 2 个参数。您可以使用与属性相同的名称或选择不同的名称。

          class Shapes(attributes):
              def __init__(self, length, width):
                  self.length = length
                  self.width = width
          

          如果你想继承Shapes类,除了把它放在2dShapes类定义中的()中,你必须调用Shapes类的__init__并传递一个引用和其他参数给它,像这样:

          class 2dShapes(Shapes):
              def __init__(self, length, width):
                  Shapes.__init__(self, length, width)
          

          如果你想在 2dShapes 中使用 Shapes 的方法,你必须像我们为 __init__ 所做的那样调用它,假设在 Shapes 中有一个名为 area() 的方法。在 2dShapes 中,您可以通过 Shapes.area(self) 访问它。这是一个例子:

          class Shapes(attributes):
              def __init__(self, length, width):
                  self.length = length
                  self.width = width
              def area(self):
                  return self.length * self.width
          
          class 2dShapes(Shapes):
              def __init__(self, length, width):
                  Shapes.__init__(self, length, width)
              def area(self):
                  return Shapes.area(self)
          

          【讨论】:

          • 不必在 2dShapes 中必须def __init__,如果找不到,它将向父母提出。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2014-12-06
          • 2019-01-26
          • 1970-01-01
          • 1970-01-01
          • 2012-07-26
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多