ООП. Абстрактный класс. Декомпозиция программы.

Абстрактные классы

Абстрактным называется класс, который содержит один и более абстрактных методов. Абстрактным называется объявленный, но не реализованный метод. Абстрактные классы не могут быть инстанциированы, от них нужно унаследовать, реализовать все их абстрактные методы и только тогда можно создать экземпляр такого класса.

В python существует стандартная библиотека abc, добавляющая в язык абстрактные базовые классы (АБК). АБК позволяют определить класс, указав при этом, какие методы или свойства обязательно переопределить в классах-наследниках.

Возьмем для примера, шахматы. У всех шахматных фигур есть общий функционал, например - возможность фигуры ходить и быть отображенной на доске. Исходя из этого, мы можем создать абстрактный класс Фигура, определить в нем абстрактный метод (в нашем случае - ход, поскольку каждая фигура ходит по-своему) и реализовать общий функционал (отрисовка на доске).

from abc import ABC, abstractmethod

class ChessPiece(ABC):
    # общий метод, который будут использовать все наследники этого класса
    def draw(self):
        print("Drew a chess piece")

    # абстрактный метод, который будет необходимо переопределять для каждого подкласса
    @abstractmethod
    def move(self):
        pass
a = ChessPiece() # Если мы попытаемся инстанциировать данный класс, логично получим ошибку.
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-91-1f9727e5cc03> in <module>()
----> 1 a = ChessPiece() # Если мы попытаемся инстанциировать данный класс, логично получим ошибку.


TypeError: Can't instantiate abstract class ChessPiece with abstract methods move

Как видите, система не дает нам создать экземпляр данного класса. Теперь нам необходимо создать конкретный класс, например, класс ферзя, в котором мы реализуем метод move.

class Queen(ChessPiece):
    def move(self):
        print("Moved Queen to e2e4")

# Мы можем создать экземпляр класса
q = Queen()
# И нам доступны все методы класса
q.draw()
q.move()
Drew a chess piece
Moved Queen to e2e4

Обратите внимание, абстрактный метод может быть реализован сразу в абстрактном классе, однако, декоратор abstractmethod, обяжет программистов, реализующих подкласс либо реализовать собственную версию абстрактного метода, либо дополнить существующую. В таком случае, мы можем переопределять метод как в обычном наследовании, а вызывать родительский метод при помощи super().

from abc import ABC, abstractmethod

class Basic(ABC):
    @abstractmethod
    def hello(self):
        print("Hello from Basic class")


class Advanced(Basic):
    def hello(self):
        super().hello()
        print("Enriched functionality")


a = Advanced()
a.hello()
Hello from Basic class
Enriched functionality

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

подробности можно найти в документации: https://docs.python.org/3/library/abc.html

Декомпозиция программы на модули

Модули и пакеты в Python – это прекрасные инструменты для управления сложностью в программном проекте.

Создадим модуль с именем simplemath.py, который будет содержать функции для выполнения простых арифметических действий.

Создадим ещё один модуль worker.py, который будет использовать функции из simplemath.py. Если мы хотим импортировать все функции, то оператор import для нас отлично подойдет. Это будет выглядеть так.

# представим, что эта ячейка - текстовый редактор, который мы сохраним под именем simplemath.py
def add(a, b):
    return a + b

def sub(a, b):
    return a - b

def mul(a, b):
    return a * b

def div(a, b):
    return a / b
# представим, что эта ячейка - текстовый редактор, который мы сохраним под именем simplemath.py

#import simplemath
#from simplemath inpord add,sub,mul,div

#print(simplemath.add(1, 2)) # = 3
#print(simplemath.sub(1, 2)) # = -1
#print(simplemath.mul(1, 2)) # = 2
#print(simplemath.div(1, 2)) # = 0.5

Задачи:

Задача 1:

В файле вам даны 3 класса A, B, C, имеющие сходный (но не одинаковый) интерфейс. Вам необходимо создать абстрактный базовый класс Base и построить корректную схему наследования. При выполнении следует избегать дублирования кода, и стараться следовать SOLID принципам ООП.

Задача 2:

В файле вам дана программа. Необходимо провести её рефакторинг.

Для работы программы необходима библиотека PyGame. В открывшемся окне программы доступны следующие команды управления:

  • <F1>  - показать справку по командам
  • <R>  - рестарт
  • <P>  - пауза, снять/поставить
  • <num->  - увеличить количество точек «сглаживания»
  • <num+>  - уменьшить количество точек «сглаживания»
  • <mouse left>  - добавить «опорную» точку

По умолчанию при старте программы «опорные» точки отсутствуют и программа находится в состоянии паузы (движение кривой выключено). Для добавления точек сделайте несколько кликов левой клавишей мыши в любом месте окна программы. Отрисовка кривой произойдет, когда точек на экране станет больше двух. Нажмите клавишу <P>, чтобы включить движение кривой.

Ваша задача:

  1. Изучить документацию к библиотеке pygame и код программы. Понять механизм работы программы (как происходит отрисовка кривой, перерасчет точек сглаживания и другие нюансы реализации программы)
  2. Провести рефакторниг кода, переписать программу в ООП стиле с использованием классов и наследования. Реализовать класс 2-мерных векторов Vec2d . В классе следует определить методы для основных математических операций, необходимых для работы с вектором. Добавить возможность вычислять длину вектора с использованием функции len(a) и метод int_pair, который возвращает кортеж из двух целых чисел (текущие координаты вектора).

Реализовать класс замкнутых ломаных Polyline с методами отвечающими за добавление в ломаную точки (Vec2d) c её скоростью, пересчёт координат точек (set_points) и отрисовку ломаной (draw_points). Арифметические действия с векторами должны быть реализованы с помощью операторов, а не через вызовы соответствующих методов.

Реализовать класс Knot (наследник класса Polyline), в котором добавление и пересчёт координат инициируют вызов функции get_knot для расчёта точек кривой по добавляемым «опорным» точкам.

Все классы должны быть самостоятельными и не использовать внешние функции.

Задача 3* ДНК

Реализуйте классы для ДНК (двойная цепочк) и РНК (одинарная цепочка).
Данные структуры данных должны поддерживать следующие возможности:

1. Создавать структуру из строк. Обратите внимание, что в ДНК встречаются только азотистые основания ATGC, а в РНК (AUGC) поэтому если во входной строке содержались другие символы, необходимо поднимать ошибку (Exception). 2. Поддерживают индексацию. РНК по индексу возвращает i-ое азотистое основание, ДНК - пару азотистых оснований (соответствующие первой и второй цепочке) 3. РНК может возвращать комплиментарную ДНК (каждому азотистому основанию из РНК соответсвует соответсвующее основание для первой цепочки ДНК: A → T, U → A, G → C, C → G. Вторая цепочка ДНК строится комплиментарной первой строчке ДНК: A → T, T → A, G → C, C → G) 4. РНК, как и ДНК, могут складываться путем склеивания ("AUUGAACUA" + "CGGAAA" = "AUUGAACUACGGAAA"). У ДНК склеиваются соответствующие цепочки (["ACG", "TGC"] + ["TTTAAT", "AAATTA"] = ["ACGTTTAAT", "TGCAAATTA"]) 5. РНК могут перемножаться друг с другом: каждое азотистое основание результирующей РНК получается случайным выбором одного из двух соответсвующих родительских азотистых оснований. Если одна из цепочек длиннее другой, то перемножение происходит с начала, когда одна из цепочек закончится оставшийся хвост другой переносится без изменений. Умножение РНК 6. ДНК могут перемножаться друг с другом: ПЕРВЫЕ цепочки каждой из ДНК перемножаются по такому же приницпу, как перемножаются РНК выше. Вторая цепочка результирующей ДНК строится как комплиментарная первой 7. Цепочки РНК и первую и вторую у ДНК можно проверять на равенство 8. Оба класса должны давать осмысленный вывод как при print, так и просто при вызове в ячейке | Обдумайте и создайте необходимые и, возможно, вспомогательные классы, настройте наследование, если требуется. Полученная структура должна быть адекватной и удобной, готовой к простому расширению функционала, если потребуется