Спроектируйте простую систему банковских счетов (открыть счёт, снять/пополнить, перевод) тремя разными подходами: процедурным, объектно-ориентированным и функциональным; опишите плюсы и минусы каждого подхода с точки зрения читаемости, тестируемости и сопровождения.
Ниже — три простые реализации системы банковских счётов open,deposit/withdraw,transferopen, deposit/withdraw, transferopen,deposit/withdraw,transfer в трёх парадигмах: процедурной, объектно‑ориентированной и функциональной. Для каждой — краткий код на Python чтобыбылонаглядночтобы было наглядночтобыбылонаглядно и разбор плюсов/минусов с точки зрения читаемости, тестируемости и сопровождения.
1) Процедурный подход Описание: глобальное состояние словарьсловарьсловарь + функции, которые его мутируют.
Код PythonPythonPython:
# procedural.py accounts = {} # id -> {"owner": str, "balance": int} _next_id = 1 def open_accountowner,initial=0owner, initial=0owner,initial=0: global _next_id acc_id = _next_id _next_id += 1 accountsaccidacc_idaccid = {"owner": owner, "balance": initial} return acc_id def depositaccid,amountacc_id, amountaccid,amount: if amount < 0 or acc_id not in accounts: return False accountsaccidacc_idaccid"balance""balance""balance" += amount return True def withdrawaccid,amountacc_id, amountaccid,amount: if amount < 0 or acc_id not in accounts: return False if accountsaccidacc_idaccid"balance""balance""balance" < amount: return False accountsaccidacc_idaccid"balance""balance""balance" -= amount return True def transferfromid,toid,amountfrom_id, to_id, amountfromid,toid,amount: # простая реализация: сначала снять, потом положить; откат в случае ошибки if withdrawfromid,amountfrom_id, amountfromid,amount: if deposittoid,amountto_id, amounttoid,amount: return True else: # откат depositfromid,amountfrom_id, amountfromid,amount
return False
Плюсы:
Очень простая и понятная реализация для малого кода.Мало шаблонного кода — легко писать быстро.
Минусы:
Читаемость: при небольших скриптах — OK, при росте код становится хаотичным глобальныепеременныеглобальные переменныеглобальныепеременные.Тестируемость: сложнее запускать параллельные тесты общийглобальныйстейтобщий глобальный стейтобщийглобальныйстейт, нужно вручную чистить/мокать глобальные переменные.Сопровождение: высокая связность через глобальное состояние, риск побочных эффектов, трудно управлять транзакциями и конкурентностью.
2) Объектно‑ориентированный подход Описание: объекты Account инкапсуляцияинкапсуляцияинкапсуляция и Bank менеджерсчётов,операциименеджер счётов, операциименеджерсчётов,операции.
Код PythonPythonPython:
# oop.py from dataclasses import dataclass @dataclass class Account: id: int owner: str balance: int = 0 def depositself,amountself, amountself,amount: if amount < 0: raise ValueError("amount < 0") self.balance += amount def withdrawself,amountself, amountself,amount: if amount < 0: raise ValueError("amount < 0") if self.balance < amount: raise ValueError"insufficientfunds""insufficient funds""insufficientfunds"
self.balance -= amount class Bank: def __init__selfselfself: self._accounts = {} self._next_id = 1 def open_accountself,owner,initial=0self, owner, initial=0self,owner,initial=0: acc = Accountself.nextid,owner,initialself._next_id, owner, initialself.nextid,owner,initial
self._accountsself.nextidself._next_idself.nextid = acc self._next_id += 1 return acc.id def getself,accidself, acc_idself,accid: return self._accounts.getaccidacc_idaccid def transferself,fromid,toid,amountself, from_id, to_id, amountself,fromid,toid,amount: a_from = self.getfromidfrom_idfromid
a_to = self.gettoidto_idtoid
if a_from is None or a_to is None: raise ValueError"accountnotfound""account not found""accountnotfound"
# здесь можно добавить блокировку по счетам в многопоточном окружении a_from.withdrawamountamountamount
a_to.depositamountamountamount
Плюсы:
Читаемость: код структурирован, доменные сущности Account,BankAccount, BankAccount,Bank очевидны.Тестируемость: можно тестировать методы классов, легко мокать/заменять объекты, изолировать сценарии.Сопровождение: хорошая инкапсуляция, расширяемость наследование,композициянаследование, композициянаследование,композиция, удобно добавлять правила/валидации, логирование.
Минусы:
Мутируемые объекты: состояние меняется внутри объектов, что требует внимания при многопоточности нужныблокировкинужны блокировкинужныблокировки.Возможна чрезмерная сложность при неправильной модели слишкомглубокиеиерархиислишком глубокие иерархиислишкомглубокиеиерархии.Иногда больше шаблонного кода, чем в функциональном варианте.
3) Функциональный подход Описание: состояние неизменяемо; операции — чистые функции, возвращающие новое состояние и результат безглобальныхпобочныхэффектовбез глобальных побочных эффектовбезглобальныхпобочныхэффектов.
Код PythonPythonPython:
# functional.py from dataclasses import dataclass, replace @dataclassfrozen=Truefrozen=Truefrozen=True
class Account: id: int owner: str balance: int = 0 # state: {"accounts": {id: Account}, "next_id": int} def open_accountstate,owner,initial=0state, owner, initial=0state,owner,initial=0: nid = state"nextid""next_id""nextid"
acc = Accountnid,owner,initialnid, owner, initialnid,owner,initial
new_accounts = dictstate["accounts"]state["accounts"]state["accounts"]
new_accountsnidnidnid = acc new_state = {"accounts": new_accounts, "next_id": nid + 1} return new_state, nid def depositstate,accid,amountstate, acc_id, amountstate,accid,amount: if amount < 0 or acc_id not in state"accounts""accounts""accounts": return state, "error","badrequest""error", "bad request""error","badrequest"
acc = state"accounts""accounts""accounts"accidacc_idaccid
new_acc = replaceacc,balance=acc.balance+amountacc, balance=acc.balance + amountacc,balance=acc.balance+amount
new_accounts = dictstate["accounts"]state["accounts"]state["accounts"]
new_accountsaccidacc_idaccid = new_acc new_state = {**state, "accounts": new_accounts} return new_state, "ok","ok","ok", def withdrawstate,accid,amountstate, acc_id, amountstate,accid,amount: if amount < 0 or acc_id not in state"accounts""accounts""accounts": return state, "error","badrequest""error", "bad request""error","badrequest"
acc = state"accounts""accounts""accounts"accidacc_idaccid
if acc.balance < amount: return state, "error","insufficient""error", "insufficient""error","insufficient"
new_acc = replaceacc,balance=acc.balance−amountacc, balance=acc.balance - amountacc,balance=acc.balance−amount
new_accounts = dictstate["accounts"]state["accounts"]state["accounts"]
new_accountsaccidacc_idaccid = new_acc new_state = {**state, "accounts": new_accounts} return new_state, "ok","ok","ok", def transferstate,fromid,toid,amountstate, from_id, to_id, amountstate,fromid,toid,amount: state2, r1 = withdrawstate,fromid,amountstate, from_id, amountstate,fromid,amount
if r1000 != "ok": return state, r1 state3, r2 = depositstate2,toid,amountstate2, to_id, amountstate2,toid,amount
if r2000 != "ok": # откат: вернуть предыдущее состояние state безснятиябез снятиябезснятия
return state, "error","depositfailed""error", "deposit failed""error","depositfailed"
return state3, "ok","ok","ok",
Плюсы:
Читаемость: функции небольшие и предсказуемые; поведение легче понимать, если привыкли к FP.Тестируемость: очень легко тестировать чистые функции — вход -> выход; тесты не зависят от внешнего состояния.Сопровождение: отсутствие скрытых побочных эффектов упрощает рефакторинг; хорошо для конкурентной среды нетобщеймутируемойпамятинет общей мутируемой памятинетобщеймутируемойпамяти.
Минусы:
Требуется передавать и возвращать состояние, что может добавить шаблонного кода и усложнить API.Может быть менее интуитивно для программистов, привыкших к OOP.Копирование структур впростомпримереdict.copy()в простом примере dict.copy()впростомпримереdict.copy() может быть затратным по памяти/времени; в больших системах нужны оптимизированные неизменяемые структуры persistentdatastructurespersistent data structurespersistentdatastructures.
Сравнение по критериям
Читаемость
Процедурный: читаемо для простых сценариев; быстро становится хаотичным при усложнении.OOP: хорошая читаемость для моделирования предметной области Accounts,BankAccounts, BankAccounts,Bank. При правильной декомпозиции — очень понятен.Функциональный: читаем при небольших чистых функциях; требует привычки к явной передаче состояния.
Тестируемость
Процедурный: труднее — глобальный стейт, тесты должны контролировать/сбрасывать состояние.OOP: легко тестировать отдельные объекты/методы; можно мокать внешние зависимости.Функциональный: самое простое тестирование — чистые функции, нет скрытых побочных эффектов.
Процедурный: плохо масштабируется; риск регрессий из‑за глобального состояния.OOP: хорош для сложной логики, легко вводить новые правила/стратегии; при правильном дизайне — лучший баланс.Функциональный: безопасный для рефакторинга и параллельного выполнения; но требует дисциплины и может потребовать инфраструктуры для управления состоянием например,потокизмененийнапример, поток измененийнапример,потокизменений.
Рекомендации
Для небольшого скрипта или демо: процедурный подход — быстрый старт.Для типичного бизнес‑приложения с богатой предметной логикой и долгим сроком поддержки: OOP — наиболее привычный и удобный вариант.Если важны корректность, простота тестирования и конкурентность многопотоков,события,распределённостьмного потоков, события, распределённостьмногопотоков,события,распределённость, или команда ориентирована на функциональный стиль — выбирайте функциональный подход илигибрид:immutableмодели+сервисывOOPили гибрид: immutable модели + сервисы в OOPилигибрид:immutableмодели+сервисывOOP.
Если нужно, могу:
Сделать «промышленную» версию собработкойошибокчерезисключения/Result,логированием,транзакциямис обработкой ошибок через исключения/Result, логированием, транзакциямисобработкойошибокчерезисключения/Result,логированием,транзакциями.Показать пример unit‑тестов для каждого подхода.Добавить обработку многопоточности блокировки,optimisticlockingблокировки, optimistic lockingблокировки,optimisticlocking и примеры использования реальной БД.
Ниже — три простые реализации системы банковских счётов open,deposit/withdraw,transferopen, deposit/withdraw, transferopen,deposit/withdraw,transfer в трёх парадигмах: процедурной, объектно‑ориентированной и функциональной. Для каждой — краткий код на Python чтобыбылонаглядночтобы было наглядночтобыбылонаглядно и разбор плюсов/минусов с точки зрения читаемости, тестируемости и сопровождения.
1) Процедурный подход
Описание: глобальное состояние словарьсловарьсловарь + функции, которые его мутируют.
Код PythonPythonPython:
# procedural.pyaccounts = {} # id -> {"owner": str, "balance": int}
_next_id = 1
def open_accountowner,initial=0owner, initial=0owner,initial=0:
global _next_id
acc_id = _next_id
_next_id += 1
accountsaccidacc_idacci d = {"owner": owner, "balance": initial}
return acc_id
def depositaccid,amountacc_id, amountacci d,amount:
if amount < 0 or acc_id not in accounts:
return False
accountsaccidacc_idacci d"balance""balance""balance" += amount
return True
def withdrawaccid,amountacc_id, amountacci d,amount:
if amount < 0 or acc_id not in accounts:
return False
if accountsaccidacc_idacci d"balance""balance""balance" < amount:
return False
accountsaccidacc_idacci d"balance""balance""balance" -= amount
return True
def transferfromid,toid,amountfrom_id, to_id, amountfromi d,toi d,amount:
# простая реализация: сначала снять, потом положить; откат в случае ошибки
if withdrawfromid,amountfrom_id, amountfromi d,amount:
if deposittoid,amountto_id, amounttoi d,amount:
return True
else:
# откат
depositfromid,amountfrom_id, amountfromi d,amount return False
Плюсы:
Очень простая и понятная реализация для малого кода.Мало шаблонного кода — легко писать быстро.Минусы:
Читаемость: при небольших скриптах — OK, при росте код становится хаотичным глобальныепеременныеглобальные переменныеглобальныепеременные.Тестируемость: сложнее запускать параллельные тесты общийглобальныйстейтобщий глобальный стейтобщийглобальныйстейт, нужно вручную чистить/мокать глобальные переменные.Сопровождение: высокая связность через глобальное состояние, риск побочных эффектов, трудно управлять транзакциями и конкурентностью.2) Объектно‑ориентированный подход
Описание: объекты Account инкапсуляцияинкапсуляцияинкапсуляция и Bank менеджерсчётов,операциименеджер счётов, операциименеджерсчётов,операции.
Код PythonPythonPython:
# oop.pyfrom dataclasses import dataclass
@dataclass
class Account:
id: int
owner: str
balance: int = 0
def depositself,amountself, amountself,amount:
if amount < 0:
raise ValueError("amount < 0")
self.balance += amount
def withdrawself,amountself, amountself,amount:
if amount < 0:
raise ValueError("amount < 0")
if self.balance < amount:
raise ValueError"insufficientfunds""insufficient funds""insufficientfunds" self.balance -= amount
class Bank:
def __init__selfselfself:
self._accounts = {}
self._next_id = 1
def open_accountself,owner,initial=0self, owner, initial=0self,owner,initial=0:
acc = Accountself.nextid,owner,initialself._next_id, owner, initialself.n exti d,owner,initial self._accountsself.nextidself._next_idself.n exti d = acc
self._next_id += 1
return acc.id
def getself,accidself, acc_idself,acci d:
return self._accounts.getaccidacc_idacci d
def transferself,fromid,toid,amountself, from_id, to_id, amountself,fromi d,toi d,amount:
a_from = self.getfromidfrom_idfromi d a_to = self.gettoidto_idtoi d if a_from is None or a_to is None:
raise ValueError"accountnotfound""account not found""accountnotfound" # здесь можно добавить блокировку по счетам в многопоточном окружении
a_from.withdrawamountamountamount a_to.depositamountamountamount
Плюсы:
Читаемость: код структурирован, доменные сущности Account,BankAccount, BankAccount,Bank очевидны.Тестируемость: можно тестировать методы классов, легко мокать/заменять объекты, изолировать сценарии.Сопровождение: хорошая инкапсуляция, расширяемость наследование,композициянаследование, композициянаследование,композиция, удобно добавлять правила/валидации, логирование.Минусы:
Мутируемые объекты: состояние меняется внутри объектов, что требует внимания при многопоточности нужныблокировкинужны блокировкинужныблокировки.Возможна чрезмерная сложность при неправильной модели слишкомглубокиеиерархиислишком глубокие иерархиислишкомглубокиеиерархии.Иногда больше шаблонного кода, чем в функциональном варианте.3) Функциональный подход
Описание: состояние неизменяемо; операции — чистые функции, возвращающие новое состояние и результат безглобальныхпобочныхэффектовбез глобальных побочных эффектовбезглобальныхпобочныхэффектов.
Код PythonPythonPython:
# functional.pyfrom dataclasses import dataclass, replace
@dataclassfrozen=Truefrozen=Truefrozen=True class Account:
id: int
owner: str
balance: int = 0
# state: {"accounts": {id: Account}, "next_id": int}
def open_accountstate,owner,initial=0state, owner, initial=0state,owner,initial=0:
nid = state"nextid""next_id""nexti d" acc = Accountnid,owner,initialnid, owner, initialnid,owner,initial new_accounts = dictstate["accounts"]state["accounts"]state["accounts"] new_accountsnidnidnid = acc
new_state = {"accounts": new_accounts, "next_id": nid + 1}
return new_state, nid
def depositstate,accid,amountstate, acc_id, amountstate,acci d,amount:
if amount < 0 or acc_id not in state"accounts""accounts""accounts":
return state, "error","badrequest""error", "bad request""error","badrequest" acc = state"accounts""accounts""accounts"accidacc_idacci d new_acc = replaceacc,balance=acc.balance+amountacc, balance=acc.balance + amountacc,balance=acc.balance+amount new_accounts = dictstate["accounts"]state["accounts"]state["accounts"] new_accountsaccidacc_idacci d = new_acc
new_state = {**state, "accounts": new_accounts}
return new_state, "ok","ok","ok",
def withdrawstate,accid,amountstate, acc_id, amountstate,acci d,amount:
if amount < 0 or acc_id not in state"accounts""accounts""accounts":
return state, "error","badrequest""error", "bad request""error","badrequest" acc = state"accounts""accounts""accounts"accidacc_idacci d if acc.balance < amount:
return state, "error","insufficient""error", "insufficient""error","insufficient" new_acc = replaceacc,balance=acc.balance−amountacc, balance=acc.balance - amountacc,balance=acc.balance−amount new_accounts = dictstate["accounts"]state["accounts"]state["accounts"] new_accountsaccidacc_idacci d = new_acc
new_state = {**state, "accounts": new_accounts}
return new_state, "ok","ok","ok",
def transferstate,fromid,toid,amountstate, from_id, to_id, amountstate,fromi d,toi d,amount:
state2, r1 = withdrawstate,fromid,amountstate, from_id, amountstate,fromi d,amount if r1000 != "ok":
return state, r1
state3, r2 = depositstate2,toid,amountstate2, to_id, amountstate2,toi d,amount if r2000 != "ok":
# откат: вернуть предыдущее состояние state безснятиябез снятиябезснятия return state, "error","depositfailed""error", "deposit failed""error","depositfailed" return state3, "ok","ok","ok",
Плюсы:
Читаемость: функции небольшие и предсказуемые; поведение легче понимать, если привыкли к FP.Тестируемость: очень легко тестировать чистые функции — вход -> выход; тесты не зависят от внешнего состояния.Сопровождение: отсутствие скрытых побочных эффектов упрощает рефакторинг; хорошо для конкурентной среды нетобщеймутируемойпамятинет общей мутируемой памятинетобщеймутируемойпамяти.Минусы:
Требуется передавать и возвращать состояние, что может добавить шаблонного кода и усложнить API.Может быть менее интуитивно для программистов, привыкших к OOP.Копирование структур впростомпримереdict.copy()в простом примере dict.copy()впростомпримереdict.copy() может быть затратным по памяти/времени; в больших системах нужны оптимизированные неизменяемые структуры persistentdatastructurespersistent data structurespersistentdatastructures.Сравнение по критериям
Читаемость
Процедурный: читаемо для простых сценариев; быстро становится хаотичным при усложнении.OOP: хорошая читаемость для моделирования предметной области Accounts,BankAccounts, BankAccounts,Bank. При правильной декомпозиции — очень понятен.Функциональный: читаем при небольших чистых функциях; требует привычки к явной передаче состояния.Тестируемость
Процедурный: труднее — глобальный стейт, тесты должны контролировать/сбрасывать состояние.OOP: легко тестировать отдельные объекты/методы; можно мокать внешние зависимости.Функциональный: самое простое тестирование — чистые функции, нет скрытых побочных эффектов.Сопровождение расширение,багфикс,рефакторинграсширение, багфикс, рефакторинграсширение,багфикс,рефакторинг
Процедурный: плохо масштабируется; риск регрессий из‑за глобального состояния.OOP: хорош для сложной логики, легко вводить новые правила/стратегии; при правильном дизайне — лучший баланс.Функциональный: безопасный для рефакторинга и параллельного выполнения; но требует дисциплины и может потребовать инфраструктуры для управления состоянием например,потокизмененийнапример, поток измененийнапример,потокизменений.Рекомендации
Для небольшого скрипта или демо: процедурный подход — быстрый старт.Для типичного бизнес‑приложения с богатой предметной логикой и долгим сроком поддержки: OOP — наиболее привычный и удобный вариант.Если важны корректность, простота тестирования и конкурентность многопотоков,события,распределённостьмного потоков, события, распределённостьмногопотоков,события,распределённость, или команда ориентирована на функциональный стиль — выбирайте функциональный подход илигибрид:immutableмодели+сервисывOOPили гибрид: immutable модели + сервисы в OOPилигибрид:immutableмодели+сервисывOOP.Если нужно, могу:
Сделать «промышленную» версию собработкойошибокчерезисключения/Result,логированием,транзакциямис обработкой ошибок через исключения/Result, логированием, транзакциямисобработкойошибокчерезисключения/Result,логированием,транзакциями.Показать пример unit‑тестов для каждого подхода.Добавить обработку многопоточности блокировки,optimisticlockingблокировки, optimistic lockingблокировки,optimisticlocking и примеры использования реальной БД.