“Патерн Вівторка” #5: Специфікація (Specification)
|(Перш за все замічу, що Specification не належить до GoF патернів. То на випадок, якщо у вас виникло здивування.)
Загально кажучи Специфікація це предикат, який відповідає на питання чи об’єкт задовольняє, або ж не задовольняє деякий критерій. Використовуючи специфікатори ми можемо переписати нашу складну бізнес логіку словами булевої логіки.
Чи Ви коли небуть задумувалися, що bool TryParse(string s, out int result) може бути якимось дизайн патерном? Так, ми можемо дивитися на цей метод як на специфікацію інтеджера представленого стрінгом – ми валідуємо (Validation) чи воно дійсно так є. Цей патерн може бути використовуваний не тільки для Валідації (Validation), але також і для Запитів (Queuring) та Будування (Building).
Нумо уявімо собі, що нам слід перевірити чи Пацієнт може приймати наркотичні препарати дома під час візиту медичної сестри.
В нашому прикладі знадобляться два специфікатори. Якщо вони два є задоволені ми можемо сказати, що пацієнту можна проводити наркотичні процедури на дому.
Перша специфікація
}
Друга специфікація
Як ви уже здогадалися інтерфейс ISpecification виглядає так:
Використання може бути таким:
Ви зараз, мабуть, хочете сказати, що ми можемо запхати усі перевірки в один метод FetchPatientsForVisitWithDrugs. Так, але це не зовсім правильно, тому що специфікація може й має бути використана у багатьох місцях. Також якщо ми будемо дивитися на це із точки зору DDD, то ми повинні виносити основні моменти так, щоб їх було видно, що ми й зробили, виділивши декілька перевірок.
Але ще одне питання: чи взагалі вам подобається такий синтаксис?
if(drugsSpec.IsSatisfiedBy(patient) && nurseVisit.IsSatisfiedBy(patient))
Як на мене то він якийсь надлишковий і може бути складним, особливо якщо у вас багато специфікацій і ми захочемо їх комбінувати.
Нумо удосконалимо наш дизайн.
Перш за все додамо пару методів до нашого інтерфейсу:
}
А також добавимо клас CompositeSpecification, що буде базовим для двох уже існуючих специфікацій.
Давайте тепер поміняємо уже існуючі специфікації у відповідність із новим дизайном (просто добамимо наслідування від базового класу):
}
І що це нам дає? А дає воно можливість будувати нові специфікації використовуючи існуючі дуже красивим способом.
Новий спосіб використання
Тепер ми можемо працювати із цією специфікацією як із однією-єдиною: if(drugsAtHomeSpec.IsSatisfiedBy(patient))
А використовуючи OrSpecification та NotSpecification ми можемо побудувати дуже закручені запити.
Для прикладу поглянемо на один із варіантів використання цього патерну в NHibernate:
Користь із використання Специфікацій:
- Спеціфікація декларує вимоги до об’єктів, але не розголошує як результати були досягнуті.
- Правила визначаються явно. Це означає, що програміст чітко певен що очікувати від специфікації, навіть не здогадуючись як воно реалізовано всередині.
- Отримується гнучкий інтерфейс, який може бути легко доповнений. Також можна побудувати складену специфікацію для запитів.
- Ще однією із переваг є те, що тестувати все дуже зручно. Ви просто визначаєте fail, або non-fail стани об’єкту для певних ситуацій і перевіряєте використовуючи булівські результати.
Буду вдячний за будь-які коментарі!
мне нравится, тока я вот никогда его не использовал:)
Невже ніяке 3-д парті не мало подібного API ?
а черт его знает. мож и было, но я видать его с точки зрениия паттерна не рассматривал, потому в голове и не отложилось:)
Автор видалив цей коментар.
Круто! Ніколи не чув про цей паттерн. Так тримати!
>>Але ще одне питання: чи взагалі вам подобається такий синтаксис?
Можна закинути всі специфікації у масив і додати тут вкладений цикл. При додаванні нової специфікації просто буде плюс один елемент до масиву. Так можливо нічим не краще, але я б так робив:)
Прикольно, але тобі не завжди треба робити AND, тобі ще може треба буде робити OR, або інакше комбінувати!
Гарна стаття, дякую
Класна стаття, як раз вчасно зустрів! Дякую!
А можно использовать LINQ и вообще обойтись без кастомных експрешенов. Семантика спецификации сохраняется в названии класса спецификации, реализация использует LINQ. Плюс можно реализовать чейнинг спецификаций – с LINQ это тоже просто