📌 L – Liskov Substitution Principle (LSP)
Предыдущий пост.
Принцип подстановки Лисков
«Если в коде вместо базового класса подставить его наследника — поведение не должно ломаться.»
На самом деле было довольно сложно было придумать пример в контексте автотестирования, но давайте попробуем.
🔍 Как это работает?
Допустим, есть базовый API-клиент с аутентификацией:
class BaseApiClient:
# Возвращает токен для аутентификации.
def get_auth_token(self) -> str:
raise NotImplementedError
# Авторизованный запрос
def send_request(self, url: str) -> Response:
token = self.get_auth_token()
headers = {"Authorization": token}
return requests.get(url, headers=headers)
Ок, теперь реализуем двух клиентов:
class AuthClient(BaseApiClient):
def get_auth_token(self) -> str:
return "Bearer token" # Корректный токен
class NoAuthClient(BaseApiClient):
def get_auth_token(self) -> str:
return None # вот тут уже подозрительно
А дальше кто-то пишет код, который работает с клиентом:
def test_send_request(client: BaseApiClient, url: str):
url = "http://some-api.com/endpoint"
return client.send_request(url)
С AuthClient все ок. С NoAuthClient словим баг: {"Authorization": None }.
Почему?
Потому, что поведение наследника не соответствует ожиданиям.
✅ Как правильно:
Если NoAuthClient не поддерживает аутентификацию, не нужно заставлять его реализовывать метод, который делает вид, что всё ок.
Лучше изменить архитектуру:
- вынести токен в отдельный класс (AuthProvider)
- если часть клиентов не поддерживает аутентификацию, вынесем эту логику в отдельный класс/интерфейс:
class AuthProvider(ABC):
@abstractmethod
def get_auth_token(self) -> str:
pass
class Auth(AuthProvider):
def get_auth_token(self) -> str:
return "Bearer token" # Корректный токен
class BaseApiClient:
def send_request(
self,
url: str,
headers: Optional[dict] = None
) -> requests.Response:
return requests.get(url, headers=headers)
class AuthApiClient(BaseApiClient):
def __init__(self, auth: AuthProvider):
self.auth = auth
def send_request(self, url: str, **kwargs) -> requests.Response:
token = self.auth.get_auth_token()
headers = kwargs.get("headers", {})
headers["Authorization"] = token
return super().send_request(url, headers=headers)
Теперь клиент отработает без ошибок.
# и этот клиент
client = AuthApiClient(RealAuth())
# и этот клиент отработает
client = BaseApiClient()
def test_send_request(client: BaseApiClient, url: str):
url = "http://some-api.com/endpoint"
return client.send_request(url)
Вывод:
Соблюдение LSP:
🔹 Делает поведение предсказуемым
🔹 Спасает от скрытых багов
🔹 Позволяет переиспользовать код без костылей
——————————-
📱 TG-сообщество
📱 Обучение
📱 Отзывы
