Спецификация¶
Спецификация - это паттерн, который описывает бизнес-правило, которое можно комбинировать с другими бизнес-правилами при помощи булевой логики. Таким образом, сложные правила легко поддерживать, они так же легко читаются.
Паттерн часто используется в контексте domain driven design.
Реализация¶
Допустим, имеем класс товара:
Делаем базовый класс спецификации:
Например, наша цель проверить, можно ли купить товар. Для этого нам надо проверять, является ли товар видимым и остался ли он на складе. Сделаем спецификации для этого.Спецификация для проверки, видимый ли товар:
class IsProductVisible(Specification):
def satisfy(self, candidate: Product) -> bool:
return candidate.visible
А теперь спецификация для проверки, остались ли ещё товары:
class IsProductAvailable(Specification):
def satisfy(self, candidate: Product) -> bool:
return candidate.quantity > 0
Теперь эти 2 спецификации мы можем объеденить в одну:
class IsProductAvailableForPurchase(Specification):
def __init__(self):
self._is_visible = IsProductVisible()
self._is_available = IsProductAvailable()
def satisfy(self, candidate: Product) -> bool:
return self._is_visible.satisfy(candidate) and self._is_available.satisfy(candidate)
И проверить:
spec = IsProductAvailableForPurchase()
product = Product(price=10, name="A", quantity=10, visible=True)
spec.satisfy(product) # True
Улучшаем работу с при помощи магических методов¶
Всё таки писать следующий код:
намного лучше, чем делать для каждого такого сложного правила отдельный класс, как показано было выше.
К тому же, рано или поздно, количество правил и логики будет расти. А комбинировать маленькие правила лучше, чем писать новые большие.
Для этого нам необходимо добавить в класс Specification реализации магических методов __and__
, __or__
и __invert__
. Их суть проста - мы оборачиваем каждую из этих операций в специальный класс, который у себя реализует необходимую операцию:
class Specification(object):
def satisfy(self, candidate: Product) -> bool: ...
def __and__(self, other: "Specification") -> "Specification":
return AndSpecification(spec_a=self, spec_b=other)
def __or__(self, other: "Specification") -> "Specification":
return OrSpecification(self, other)
def __invert__(self, other: "Specification") -> "Specification":
return NotSpecification(other)
А теперь реализуем их:
class AndSpecification(Specification):
def __init__(self, spec_a: Specification, spec_b: Specification):
self._spec_a = spec_a
self._spec_b = spec_b
def satisfy(self, candidate: Product) -> bool:
return self._spec_a.satisfy(candidate) and self._spec_b.satisfy(candidate)
class OrSpecification(Specification):
def __init__(self, spec_a: Specification, spec_b: Specification):
self._spec_a = spec_a
self._spec_b = spec_b
def satisfy(self, candidate: Product) -> bool:
return self._spec_a.satisfy(candidate) or self._spec_b.satisfy(candidate)
class NotSpecification(Specification):
def __init__(self, spec: Specification):
self._spec = spec
def satisfy(self, candidate: Product) -> bool:
return not self._spec.satisfy(candidate)
Тестируем!
product = Product(100, "test", 1, True)
spec = IsProductVisible() & IsProductAvailable()
spec.satisfy(product) # True