Касса «Кофейной лавки» принимает наличные, банковскую карту и баллы лояльности. Каждый способ оплаты по-своему проверяет, хватает ли денег, и по-своему пишет строку в чек. А заказу всё равно, чем платят: он содержит способ оплаты и просит его заплатить нужную сумму. В проекте собраны все три идеи модуля: абстрактный класс, альтернативный конструктор и композиция.
Шаг 1. Договор для способов оплаты
Объявите абстрактный класс PaymentMethod. __init__(self, title) записывает название способа в title,
str() возвращает это название. Абстрактный метод pay(amount) подклассы реализуют так: провести оплату
и вернуть строку для чека, а если денег не хватает — выбросить ValueError и ничего не менять.
class PaymentMethod(ABC):, @abstractmethod над pay, тело — ....Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
from abc import ABC, abstractmethod
class PaymentMethod:
def __init__(self, title):
...
def __str__(self):
...
def pay(self, amount):
...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
from abc import ABC, abstractmethod
class PaymentMethod(ABC):
def __init__(self, title):
self.title = title
def __str__(self):
return self.title
@abstractmethod
def pay(self, amount):
...Шаг 2. Три способа оплаты
Объявите подкласс Cash: Cash(1000) — покупатель дал 1000 ₽ (атрибут given), название — "наличные".
pay(640) возвращает "наличные 1000 ₽, сдача 360 ₽", а если дали меньше суммы —
ValueError("недостаточно наличных").
super().__init__("наличные"), затем self.given = given.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
class Cash(PaymentMethod):
def __init__(self, given):
...
def pay(self, amount):
...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
class Cash(PaymentMethod):
def __init__(self, given):
super().__init__("наличные")
self.given = given
def pay(self, amount):
if self.given < amount:
raise ValueError("недостаточно наличных")
return f"наличные {self.given} ₽, сдача {self.given - amount} ₽"Объявите подкласс Card: Card("2200 1234 5678 9012", 2000) — номер карты (number) и остаток на счёте
(balance), название — "карта *9012" (последние четыре цифры номера). pay(amount) списывает сумму с
balance и возвращает "карта *9012: списано 1250 ₽"; если денег на счёте меньше суммы —
ValueError("недостаточно средств на карте"), остаток не меняется.
number[-4:].Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
class Card(PaymentMethod):
def __init__(self, number, balance):
...
def pay(self, amount):
...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
class Card(PaymentMethod):
def __init__(self, number, balance):
super().__init__(f"карта *{number[-4:]}")
self.number = number
self.balance = balance
def pay(self, amount):
if amount > self.balance:
raise ValueError("недостаточно средств на карте")
self.balance -= amount
return f"{self.title}: списано {amount} ₽"Объявите подкласс LoyaltyPoints: LoyaltyPoints(500) — баллов на карте (points), 1 балл = 1 ₽, название —
"баллы". pay(380) списывает баллы и возвращает "баллы: списано 380, осталось 120"; если баллов меньше
суммы — ValueError("недостаточно баллов"), баллы не меняются.
Card: проверка, списание, строка для чека.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
class LoyaltyPoints(PaymentMethod):
def __init__(self, points):
...
def pay(self, amount):
...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
class LoyaltyPoints(PaymentMethod):
def __init__(self, points):
super().__init__("баллы")
self.points = points
def pay(self, amount):
if amount > self.points:
raise ValueError("недостаточно баллов")
self.points -= amount
return f"баллы: списано {amount}, осталось {self.points}"Шаг 3. Заказ
Объявите класс Order: Order(17, 640, method) — номер number, сумма total и способ оплаты method
(любой PaymentMethod); у нового заказа paid = False.
pay()просит способ оплаты заплатитьtotal, ставитpaid = Trueи возвращает строку чека"заказ №17: наличные 1000 ₽, сдача 360 ₽". Если способ оплаты выбросилValueError, исключение уходит дальше, аpaidостаётсяFalse.- Метод класса
from_line(line, method)создаёт заказ из строки"17;640"— номер и сумма, оба целые.
pay: receipt = self.method.pay(self.total) — если тут выброшено исключение, следующие строки не выполнятся сами.from_line: number, total = line.split(";") и return cls(int(number), int(total), method).Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
class Order:
def __init__(self, number, total, method):
...
def pay(self):
...
@classmethod
def from_line(cls, line, method):
...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
class Order:
def __init__(self, number, total, method):
self.number = number
self.total = total
self.method = method
self.paid = False
def pay(self):
receipt = self.method.pay(self.total)
self.paid = True
return f"заказ №{self.number}: {receipt}"
@classmethod
def from_line(cls, line, method):
number, total = line.split(";")
return cls(int(number), int(total), method)Шаг 4. День на кассе
В lines — четыре заказа за утро, в methods — чем расплачивался каждый покупатель (первый и четвёртый —
одной и той же картой card). Создайте заказы методом from_line, оплатите их по порядку и сохраните
в receipts строки чеков оплаченных заказов, а в failed — номера заказов, оплата которых не прошла.
Пройти два списка параллельно поможет встроенная zip(lines, methods): она выдаёт пары «первый с первым,
второй со вторым…».
for line, method in zip(lines, methods): — пары «строка заказа, способ оплаты».try: receipts.append(order.pay()), except ValueError: failed.append(order.number).Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
card = Card("2200 1234 5678 9012", 2000)
lines = ["17;1250", "18;640", "19;380", "20;1800"]
methods = [card, Cash(1000), LoyaltyPoints(500), card]
receipts = ...
failed = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
card = Card("2200 1234 5678 9012", 2000)
lines = ["17;1250", "18;640", "19;380", "20;1800"]
methods = [card, Cash(1000), LoyaltyPoints(500), card]
receipts = []
failed = []
for line, method in zip(lines, methods):
order = Order.from_line(line, method)
try:
receipts.append(order.pay())
except ValueError:
failed.append(order.number)Итог
print("Касса за утро")
for line in receipts:
print(" ", line)
print("не оплачены:", ", ".join(f"№{n}" for n in failed))
print(f"на карте *9012 осталось {card.balance} ₽")Касса за утро заказ №17: карта *9012: списано 1250 ₽ заказ №18: наличные 1000 ₽, сдача 360 ₽ заказ №19: баллы: списано 380, осталось 120 не оплачены: №20 на карте *9012 осталось 750 ₽
Три заказа из четырёх оплачены. Заказ №20 не прошёл: та же карта уже заплатила за заказ №17, и на счёте
осталось 750 ₽ — меньше 1800. Касса узнала об этом из ValueError, а не проверкой «какой это способ оплаты».
Обратите внимание, сколько кода касса знает о способах оплаты: ни одного isinstance. Новый способ —
скажем, оплата по QR-коду — это ещё один подкласс PaymentMethod, а Order и цикл дня не меняются.
Абстрактный класс гарантирует, что у нового способа будет pay, иначе его не создать.