ООП. Абстрактный класс. Декомпозиция программы.
Содержание
- Абстрактные классы
- Декомпозиция программы на модули
- Задачи:
- Задача 1:
- Задача 2:
- Задача 3* ДНК
Абстрактным называется класс, который содержит один и более абстрактных методов. Абстрактным называется объявленный, но не реализованный метод. Абстрактные классы не могут быть инстанциированы, от них нужно унаследовать, реализовать все их абстрактные методы и только тогда можно создать экземпляр такого класса.
В 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>
— добавить «опорную» точку
По умолчанию при старте программы «опорные» точки отсутствуют и программа находится в состоянии паузы (движение кривой выключено). Для добавления точек сделайте несколько кликов левой клавишей мыши в любом месте окна программы. Отрисовка кривой произойдет, когда точек на экране станет больше двух. Нажмите клавишу
, чтобы включить движение кривой.
Ваша задача:
- Изучить документацию к библиотеке pygame и код программы. Понять механизм работы программы (как происходит отрисовка кривой, перерасчет точек сглаживания и другие нюансы реализации программы)
- Провести рефакторниг кода, переписать программу в ООП стиле с использованием классов и наследования.
Реализовать класс 2-мерных векторов
Vec2d
. В классе следует определить методы для основных математических операций, необходимых для работы с вектором. Добавить возможность вычислять длину вектора с использованием функции len(a) и метод
, который возвращает кортеж из двух целых чисел (текущие координаты вектора).
Реализовать класс замкнутых ломаных Polyline
с методами отвечающими за добавление в ломаную точки (Vec2d
) c её скоростью, пересчёт координат точек (set_points
) и отрисовку ломаной (draw_points
). Арифметические действия с векторами должны быть реализованы с помощью операторов, а не через вызовы соответствующих методов.
Реализовать класс
(наследник класса Polyline
), в котором добавление и пересчёт координат инициируют вызов функции get_knot
для расчёта точек кривой по добавляемым «опорным» точкам.
Все классы должны быть самостоятельными и не использовать внешние функции.
Задача 3* ДНК
- Реализуйте классы для ДНК (двойная цепочк) и РНК (одинарная цепочка).
- Данные структуры данных должны поддерживать следующие возможности:
1. Создавать структуру из строк. Обратите внимание, что в ДНК встречаются только азотистые основания ATGC, а в РНК (AUGC) поэтому если во входной строке содержались другие символы, необходимо поднимать ошибку (Exception). 2. Поддерживают индексацию. РНК по индексу возвращает i-ое азотистое основание, ДНК — пару азотистых оснований (соответствующие первой и второй цепочке) 3.
Абстрактные классы. Курс «ООП в Kotlin»
В программировании абстрактные классы – это такие классы, от которых нельзя создавать объекты. Они используются лишь в качестве суперкласса, в который вынесено все общее из дочерних классов.
Например, в программе есть различные классы юнитов – пехотинцы, всадники, герои. Их общие свойства и методы можно вынести в один общий класс «юнит». Поскольку в программе не может быть просто юнита, такой класс имеет смысл сделать абстрактным.
В Kotlin абстрактные классы имеют модификатор abstract
вместо open
, то есть абстрактные классы всегда открыты для наследования, иначе в них не было бы смысла. Сделаем наш класс NumInc абстрактным:
abstract class NumInc(n: Int, s: Int) { var number = n var step = s fun inc() {number += step} fun dec() {number -= step} }
Теперь мы не сможем создать объект от NumInc, хотя переменную такого типа создать можем. Ее можно связать с объектами дочерних от NumInc неабстрактных классов.
val a = NumMult(3,4) val b: NumInc = NumDouble(2, 2)
Здесь переменная a будет иметь тип дочернего класса, b хоть и связана с объектом дочернего класса, будет иметь тип абстрактного родительского класса. Следующее выражение недопустимо:
val c = NumInc(1, 4)
Оно содержит ошибку, так как мы пытаемся создать объект абстрактного класса.
Если класс NumInc определен так, как представлено выше, в дочерних классах мы не можем переопределять его свойства и методы, только добавлять новые. Чтобы иметь возможность переопределения, их по-прежнему надо делать открытыми.
abstract class NumInc(n: Int, s: Int) { var number = n var step = s open fun inc() {number += step} open fun dec() {number -= step} }
class NumDouble(n: Int, s: Int): NumInc(n, s) { override fun inc() { super.inc() super.inc() } }
Однако кроме такого варианта, методы можно объявлять абстрактными. В этом случае они не должны содержать тела, то есть должны быть без реализации. В свою очередь дочерний класс обязан реализовать эти методы. То есть абстрактный метод родителя в дочернем классе всегда должен быть переопределен.
abstract class NumInc(n: Int, s: Int) { var number = n var step = s abstract fun inc() abstract fun dec() }
class NumDouble(n: Int, s: Int): NumInc(n, s) { override fun inc() { number += 2 * step } override fun dec() { number -= 2 * step } }
В другом дочернем классе реализация функций inc()
и dec()
может быть совсем другой.
class NumMult(n: Int, s: Int, q: Int): NumInc(n, s) { val coefficient = q override fun inc() { number += step * coefficient } override fun dec() { number -= step * coefficient } }
Одинаковыми являются лишь параметры (в данном случае их нет) и тип возвращаемого значения. Все это определено в родительском абстрактном классе.
Обратим внимание еще раз. Когда мы делаем методы абстрактного класса просто открытыми или оставляем закрытыми, то можем их не переопределять в дочерних. Хотя при необходимости можем и переопределить. Если метод абстрактный, то не переопределить его нельзя. Это будет ошибкой. Мы обязаны создавать его переопределение в каждом дочернем классе.
В чем выгода от этого? Мы могли бы вообще не описывать эти абстрактные методы в абстрактном классе и реализовывать только в дочерних при необходимости. Выгода – в одинаковой сигнатуре дочерних классов. Гарантируется, что у всех них есть что-то общее – одинаковые методы, что позволит выполнять групповую обработку объектов, созданных от разных классов.
В IntelliJ IDEA, когда вы создаете класс и хотите (или это требуется в случае абстрактных) переопределить свойства и методы родительского класса, можно нажать Ctrl + O, появится окно, где следует выбрать то, что вам требуется. IDEA сама сформирует заголовок.
Абстрактными могут быть не только функции-члены, но и свойства. Однако в этом случае их нельзя инициировать значениями. Пример полностью абстрактного класса:
abstract class NumInc { abstract var number: Int abstract var step: Int abstract fun inc() abstract fun dec() }
Обратите внимание, что у класса нет собственного конструктора. Пример его дочернего класса:
class NumMult(n: Int, s: Int, q: Int): NumInc() { override var number = n override var step = s val coefficient = q override fun inc() { number += step * coefficient } override fun dec() { number -= step * coefficient } }
Практическая работа:
Проверьте, можно ли в неабстрактном классе определить абстрактный метод. Объясните результат.
Проверьте, может ли абстрактный класс сам быть наследником другого класса. Обязан ли последний быть также абстрактным. Результат объясните.
PDF-версия курса с ответами к практическим работам
Как использовать абстрактные классы в Python
Мы можем создать абстрактный класс, наследуя от класса ABC
, который является частью модуля abc
.
из импорта abc (
ABC,
abstractmethod,
)
class BasicPokemon ( ABC ):
def __init__(self, name):
self.name = name
self._level = 1 @abstractmethod
по определению main_attack(self):
...
В приведенном выше коде мы создаем новый абстрактный класс с именем Базовый покемон
. Мы указываем, что метод main_attack
является абстрактным методом, используя декоратор abstractmethod
, что означает, что мы ожидаем, что он будет реализован в каждом подклассе BasicPokemon
.
Что произойдет, если вы попытаетесь создать объект из BasicPokemon
напрямую? Типовая ошибка
!
Теперь вы заметите, что метод __init__
BasicPokemon
ожидает аргумент name
, поэтому приведенный выше код все равно не будет работать. Здесь следует отметить, что для проверки этого даже не используется метод __init__
. Тот факт, что у него есть абстрактный метод, который не был реализован, имеет приоритет, и из-за этого он терпит неудачу!
Вот как можно было бы использовать класс BasicPokemon
.
из коллекций импорта namedtuple
Attack = namedtuple('Attack', ('name', 'damage'))
class Pikachu(BasicPokemon):
def main_attack(self):
return Attack('Thunder Shock', 5)
class Charmander (BasicPokemon):
def main_attack(self):
return Attack('Flame Thrower', 5)
Теперь мы можем без проблем создавать объекты из этих классов.
Создание нового Пикачу по имени… Джордж. Изображение автора. Обратите внимание, что вы также можете создавать абстрактные свойства, используя те же abstractmethod
декоратор.
из импорта abc (
ABC,
abstractmethod,
)
class BasicPokemon ( ABC ):
def __init__(self, name):
self.name = name @property
@abs tractmethod
уровень защиты (самостоятельно ):
... @abstractmethod
def main_attack(self):
...
Теперь нам нужно реализовать свойство уровня
, а также метод main_attack
в дочернем классе.
class Pikachu(BasicPokemon):
@property
def level(self):
return 1 def main_attack(self):
return Attack('Thunder Shock', 5)
Некоторым из вас может быть интересно, почему мы не мог просто использовать обычный класс (т.е. не наследовать от ABC
) и вызвать NotImplementerError
для методов, которые не были реализованы, как в примере ниже.
класс NotAbstractBasicPokemon:
def __init__(self, name):
self.name = name
self._level = 1 def main_attack(self):
поднять NotImplementedError()
Приведенный выше код позволит вам создавать объекты из NotAbstractBasicPokemon
и потерпит неудачу только при попытке использовать main_attack
. Обычно это нежелательное поведение.
Напротив, попытка создать объект из BasicPokemon
немедленно приводит к ошибке, как мы видели ранее.
В примере, который мы использовали в этом посте, целью BasicPokemon
было использование в качестве плана для создания других классов для определенных видов покемонов. Есть несколько очевидных преимуществ наличия абстрактного класса, от которого все должны наследоваться для создания нового покемона:
- Избегайте дублирования кода.
- Обеспечить согласованность в том, как другие реализуют подклассы.
- Убедитесь, что никто не забывает реализовать критические методы и свойства в подклассах.
Приведенные выше пункты особенно важны, когда вы работаете в команде и ожидаете, что другие люди будут повторно использовать/расширять код.
В этом посте мы рассмотрели абстрактные классы Python и то, как они дают нам способ описать ожидаемую реализацию подклассов и предупреждают пользователей (то есть других программистов или нас самих в будущем), когда критические части не определены.
Получайте электронное письмо всякий раз, когда Artemis Nika публикует. Зарегистрировавшись, вы создадите учетную запись Medium, если у вас ее еще нет…
eminik355.medium.com
Абстрактные занятия | Scala Book
Уведомление об устаревшей версии
Эта страница имеет новую версию.
Информация: JavaScript в настоящее время отключен, вкладки с кодом будут по-прежнему работать, но предпочтения не будут запоминаться.
В Scala также есть концепция абстрактного класса, аналогичная абстрактному классу в Java. Но поскольку трейты настолько сильны, вам редко нужно использовать абстрактный класс. На самом деле вам нужно использовать абстрактный класс только когда:
- Вы хотите создать базовый класс, для которого требуются аргументы конструктора
- Ваш код Scala будет вызываться из кода Java
Трейты Scala не допускают параметров конструктора
Что касается первой причины, трейты Scala не допускают параметров конструктора:
// это не скомпилируется черта Животное (имя: Строка)
Поэтому вам нужно использовать абстрактный класс всякий раз, когда базовое поведение должно иметь параметры конструктора:
абстрактный класс Animal (name: String)
Однако имейте в виду, что класс может расширять только один абстрактный класс.
Когда код Scala будет вызываться из кода Java
Что касается второго пункта — второй раз, когда вам нужно будет использовать абстрактный класс — потому что Java ничего не знает о трейтах Scala, если вы хотите вызвать свой Scala код из кода Java, вам нужно будет использовать абстрактный класс, а не трейт.
Синтаксис абстрактного класса
Синтаксис абстрактного класса аналогичен синтаксису типажа. Например, вот абстрактный класс с именем 9.0003 Pet , который похож на черту Pet
, которую мы определили в предыдущем уроке:
abstract class Pet (name: String) { def speak(): Unit = println("Yo") // конкретная реализация def comeToMaster(): Unit // абстрактный метод }
Учитывая этот абстрактный класс Pet
, вы можете определить класс Dog
следующим образом:
class Dog(name: String) extends Pet(name) { переопределить def speak() = println("Гав") def comeToMaster() = println("А вот и я!") }
REPL показывает, что все это работает так, как рекламируется:
scala> val d = new Dog("Ровер") d: Собака = Собака@51f1fe1c scala> д.говорить Гав scala> d.comToMaster Вот и я!
Обратите внимание, как
имя
передавалось вместе сВесь этот код похож на Java, поэтому мы не будем объяснять его подробно.