ООП: попытка спроектировать хорошую структуру классов

#python #python-3.x #oop #inheritance

#python #python-3.x #ооп #наследование

Вопрос:

Последующий вопрос по обзору фрагмента кода, который я опубликовал: https://codereview.stackexchange.com/questions/143576/oop-designing-class-inheritances

Это домашнее задание, поэтому с места в карьер я не прошу сделать это за меня, а разъяснить мне, чтобы я понял.

Вот что сказано в задании о программе, которую я должен написать:

Требуется: напишите программу OO на Python, показывающую, как ваши подклассы наследуются от суперклассов. У вас может быть один класс (предпочтительный, но не обязательный) или два или более суперклассов.

Что мне нужно сделать, это написать программу oo python, показывающую подклассы, унаследованные классом или двумя или более суперклассами. Программа должна быть о формах. В качестве примера того, что я имею в виду, это:

Форма: квадратная

Атрибуты:

  • длина
  • ширина

Методы:

  • область
  • периметр

Конечно, у меня больше форм, но из этого я нахожу общие атрибуты и методы из всех форм и создаю суперклассы и вспомогательные классы.

Мои суперклассы похожи на: 2dShapes, circles и 3dShapes. Мои подклассы похожи на length и width. Мои методы — area и perimeter. Обратите внимание, что на данный момент я сбиваюсь с толку. Приведенный ниже фрагмент кода этого не показывает, вместо этого я думал о создании суперкласса для атрибутов и методов, а не подклассов для фигур? может быть?

Вопрос: это хорошая структура классов? Есть ли лучший способ структурировать классы здесь? Вот пример того, что я думаю о том, как это сделать.

 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

Комментарии:

1. Это класс, наследуемый от другого класса. Что вас смущает?

2. Хорошая работа — быть открытым и конструктивным в отношении домашней работы 🙂 Тем не менее, вам, вероятно, придется сделать ваш вопрос более локализованным. Есть ли у вас конкретный вопрос? Также: вы говорите: » Мои подклассы похожи на length и width «, но это не имело бы особого смысла, и это не то, что указывает ваш фрагмент. Также: вы действительно хотите вложить эти классы в нижний блок? Наконец, знаете ли вы, что это attributes должен быть сам класс, если вы пишете его таким образом?

3. @AndrasDeak спасибо. мой конкретный вопрос заключается в том, как создать хороший дизайн класса для атрибутов и методов, не повторяясь.

4. проясните проблему, пожалуйста.

5. Я считаю, что стандартным способом было бы иметь один уровень классов (назовите их суперклассами, если хотите), которые представляют собой 2d и 3d формы. Они могут быть подразделены на круги, прямоугольники (в случае 2d) и сферы, октаэдры (в случае 3d). И у всех этих классов есть методы, некоторые общие, некоторые нет. Это должно определить, где определены эти методы.

Ответ №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 , которая отслеживает, сколько экземпляров объекта у Vehicle, в основном, сколько транспортных средств мы «сделали». У нас также есть некоторые методы класса, которые получают доступ и используют атрибуты, которые мы определили __init__ . Вы можете выполнять с ними математические вычисления, а что нет, но пока мы просто используем их как операторы печати, чтобы показать, что они работают. У нас есть специальный метод класса, который вызывает другие методы в том же классе. So get_stats вызывает другие get_x методы в экземпляре. Это позволяет мне вызывать эти 4 метода с помощью всего лишь «одного» вызова метода из моего объекта, см. a.get_stats() . Мы все еще можем вызывать другие методы самостоятельно get_color .

У нас есть подкласс с именем Car, который является транспортным средством, поэтому мы наследуем его. Только автомобили могут переходить в режим vroom, но все автомобили могут переходить в режим vroom, поэтому у нас есть метод vroom только на уровне car. Хитрость заключается в том, чтобы подумать, что у этого класса есть, что уникально только для экземпляров этого класса, и если нет, могу ли я поместить его в родительский класс. У всех транспортных средств есть колеса и так далее, но не все транспортные средства могут работать vroom (только для этого примера).

У нас есть подкласс Car, cooper (mini cooper), который только он может дрейфовать (еще раз для этого примера, только в реальной жизни метод дрейфа будет использоваться в vehicle, потому что все транспортные средства могут тягаться, но потерпите меня). Так что этот cooper — единственный автомобиль, который может дрифтовать, поэтому он находится здесь, а не в классе автомобилей.

Подкласс Tank интересен. Здесь у нас есть базовая часть транспортного средства, но у нас есть кое-что новое, пистолет! Итак, наш класс vehicle не может обращаться с оружием, поэтому мы должны создать новый __init__ метод. Мы присваиваем объектной переменной gun_size , а затем передаем остальные атрибуты танка в Vehicle __init__ , поскольку остальные атрибуты совпадают с Vehicle . Мы вызываем super(Tank,self).__init__(*args) , который в основном говорит: я танк, это я, пожалуйста, обработайте остальную часть моего атрибута, моего родителя. Поскольку у танков есть специальный атрибут пушки, нам приходится модифицировать наш get_stats метод, чтобы справиться с gun_size атрибутом, но остальная статистика танка такая же, как и у транспортного средства, поэтому мы просто вызываем наших родителей, чтобы разобраться с остальным после того, как разберемся с нашим оружием.

Теперь я знаю, что это очень глупый пример, но я надеюсь, что вы найдете здесь полезную информацию. Есть и другие основные моменты, которые я не затронул, но это отправная точка. Итак, вернемся к вашему первоначальному вопросу. Подумайте абстрактно, самым высоким уровнем будет форма, тогда прямоугольники — это тип формы, поэтому они будут наследовать ее. Квадраты — это специальные прямоугольники, поэтому они будут наследовать прямоугольники и так далее.

Если у вас есть вопросы, не стесняйтесь спрашивать.

Ответ №2:

Я не хочу давать слишком конкретные ответы, потому что домашнее задание, но вот пример, который, я думаю, может направить вас в правильном направлении.

В объектно-ориентированном программировании существует концепция полиморфизма: когда экземпляры многих разных подклассов связаны некоторым общим суперклассом. Вы часто видите, что это объясняется как «a B — это A», как в «Груша — это фрукт», «Кошка — это животное», «Треугольник — это форма». Все подклассы имеют общий набор методов и членов, присутствующих в суперклассе, но их реализация этих методов может различаться.

Вот пример (извините, стиль C, а не человек с Python) с животными. Метод hearNoise() принимает animal , но также будет работать корректно, если ему будет передан подтип:

 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% уверенностью, что сможете вызвать метод для вычисления периметра. Однако реализация, способ, которым конкретный подкласс shape обрабатывает вычисление периметра, — это то, что отличается от формы к форме.

Ответ №3:

Давайте начнем с класса Shapes . В __init__ вы должны установить атрибуты. Сначала вам нужны 2 параметра, отличные от self . Вы можете использовать те же имена, что и атрибуты, или выбрать разные имена.

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

Если вы хотите наследовать от класса Shapes, кроме помещения его в () в определение класса 2dShapes, вам нужно вызвать __init__ класса Shapes и передать ему ссылку и другие параметры, например:

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

Если вы хотите использовать метод фигур в 2dShapes, вы должны вызвать его точно так же, как мы сделали для __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)
  

Комментарии:

1. Вам не нужно def __init__ использовать 2dShapes, он перейдет к родителям, если он не найдет его.

Ответ №4:

Одна из главных идей использования наследования — иметь возможность повторно использовать код.. Если у вас есть большое количество функций, одинаковых для нескольких классов, наличие одного родительского класса, который содержит эти функции, позволяет вам записывать их только один раз и изменять их все одновременно. например, если вы хотите реализовать классы для фигур: Квадрат, прямоугольник и параллелограмм, вы можете создать родительский класс: четырехугольник, который содержит такие вещи, как общая область или функции периметра:

 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:

Будьте осторожны, чтобы не путать «атрибуты» с «методами». Существует синтаксис @property, который переносит доступ к атрибутам в вызов метода, но вы должны пока игнорировать это.

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

Для этой части просто впитайте аргументы здесь. например:

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

Причина в том, что вы используете формы только для передачи их в подкласс. *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.
  

То, что вы сделали выше, — это определить метод «высота», а не атрибут «высота». То, что вы хотите, больше похоже на это:

 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
  

Теперь вы можете подклассировать Square с помощью Rectangle, добавляя новые аргументы по ходу дела. Просто следуйте аналогичному шаблону инициализации, как указано выше. Rectangle не нужно будет добавлять метод height, поскольку он уже доступен у родительского элемента.