Специфікація (шаблон проєктування)

Шаблон проєктування у вигляді UML діаграми

Специфікація — це шаблон проєктування, який представляє бізнес логіку у вигляді ланцюжка об'єктів зв'язних операцій булевої логіки.

Переваги та недоліки

[ред. | ред. код]

Переваги

[ред. | ред. код]
  • логіка фільтрації об'єктів винесена в окремі класи-специфікацій, які можна, без втрат в гнучкості системи, об'єднювати між собою

Недоліки

[ред. | ред. код]
  • важкий в реалізації

Опис мовою C#

[ред. | ред. код]

Додамо деякі класи, які будуть симулювати реальні об'єкти.

public class User {     public string Name { get; set; }     public bool IsAdmin { get; set; }      public override string ToString()     {         return $"{Name}. Admin = {IsAdmin}";     } } 

Запишемо стандартну реалізацію, яку згодом покращимо для конкретної мови програмування.

public interface ISpecification<TEntity> {     bool IsSatisfiedBy(TEntity entity);      // об'єднання     ISpecification<TEntity> And(ISpecification<TEntity> other);     ISpecification<TEntity> Or(ISpecification<TEntity> other);     ISpecification<TEntity> Not(); } 

Додамо абстрактний клас, який дозволить нам об'єднювати специфікації в ланцюжки за допомогою операторів булевої логіки. У C# цей клас можна замінити на перевантаження операцій чи методами розширень до ISpecification.

public abstract class CompositeSpecification<TEntity> : ISpecification<TEntity> {     public abstract bool IsSatisfiedBy(TEntity entity);      public ISpecification<TEntity> And(ISpecification<TEntity> other)     {         return new AndSpecification<TEntity>(this, other);     }          public ISpecification<TEntity> Or(ISpecification<TEntity> other)     {         return new OrSpecification<TEntity>(this, other);     }          public ISpecification<TEntity> Not()     {         return new NotSpecification<TEntity>(this);     } } 

Реалізацій конкретних декораторів

public class AndSpecification<TEntity> : CompositeSpecification<TEntity> {     private readonly ISpecification<TEntity> spec1;     private readonly ISpecification<TEntity> spec2;          public AndSpecification(ISpecification<TEntity> spec1, ISpecification<TEntity> spec2)     {         this.spec1 = spec1;         this.spec2 = spec2;     }      public override bool IsSatisfiedBy(TEntity candidate)     {         return spec1.IsSatisfiedBy(candidate) && spec2.IsSatisfiedBy(candidate);     } }  public class OrSpecification<TEntity> : CompositeSpecification<TEntity> {     private readonly ISpecification<TEntity> spec1;     private readonly ISpecification<TEntity> spec2;      public OrSpecification(ISpecification<TEntity> spec1, ISpecification<TEntity> spec2)     {         this.spec1 = spec1;         this.spec2 = spec2;     }      public override bool IsSatisfiedBy(TEntity candidate)     {         return spec1.IsSatisfiedBy(candidate) || spec2.IsSatisfiedBy(candidate);     } }  public class NotSpecification<TEntity> : CompositeSpecification<TEntity> {     private readonly ISpecification<TEntity> wrapped;      public NotSpecification(ISpecification<TEntity> spec)     {         wrapped = spec;     }      public override bool IsSatisfiedBy(TEntity candidate)     {         return !wrapped.IsSatisfiedBy(candidate);     } } 

Припустимо, що виникли наступні задачі:

  • знайти користувачів, за їх статусом
  • знайти користувачів по імені, за введеним значенням

Тоді конкретні специфікації матимуть наступний вигляд

public class RoleSpecification : CompositeSpecification<User> {     private readonly bool isUserAdmin;      public RoleSpecification(bool isUserAdmin)     {         this.isUserAdmin = isUserAdmin;     }      public override bool IsSatisfiedBy(User entity)     {         return entity.IsAdmin == isUserAdmin;     } }   public class SearchByNameSpecification : CompositeSpecification<User> {     private readonly string searchSubstring;      public SearchByNameSpecification(string searchSubstring)     {         this.searchSubstring = searchSubstring;     }      public override bool IsSatisfiedBy(User entity)     {         return entity.Name.Contains(searchSubstring);     } } 

Використання матиме наступний вигляд:

// задана предметна область User[] users = new User[] {     new User { IsAdmin = false, Name = "User 1" },     new User { IsAdmin = false, Name = "User 2" },     new User { IsAdmin = true,  Name = "User 3" }, };              // конкретні специфікації ISpecification<User> roleSpecification = new RoleSpecification(isUserAdmin: false); ISpecification<User> nameSpecification = new SearchByNameSpecification(searchSubstring: "User");  // композиції специфікації ISpecification<User> andSpecification = nameSpecification.And(roleSpecification); ISpecification<User> orSpecification = nameSpecification.Or(roleSpecification);              // результати вибірки Console.WriteLine("AND Specification"); foreach (User user in users) {     if (andSpecification.IsSatisfiedBy(user))     {         Console.WriteLine(user);     } }  Console.WriteLine("OR Specification"); foreach (User user in users) {     if (orSpecification.IsSatisfiedBy(user))     {         Console.WriteLine(user);     } } 

Покращена версія

[ред. | ред. код]

При використанні із LINQ специфікації можна обгортати у функції, або ж забезпечити специфікації такою функціональністю:

// інтерфейс public interface ISpecification<TEntity> {     bool IsSatisfiedBy(TEntity entity);      Func<TEntity, bool> AsExpression();                    .  .  .  }  // абстрактний клас public abstract class CompositeSpecification<TEntity> : ISpecification<TEntity> {     public abstract Func<TEntity, bool> AsExpression();     public bool IsSatisfiedBy(TEntity entity) => AsExpression().Invoke(entity);                         .  .  .  }  // оператори булевої логіки public class AndSpecification<TEntity> : CompositeSpecification<TEntity> {                    .  .  .       public override Func<TEntity, bool> AsExpression()     {         return (entity) => spec1.IsSatisfiedBy(entity) && spec2.IsSatisfiedBy(entity);     } }  // конкретні специфікації public class RoleSpecification : CompositeSpecification<User> {     private readonly Func<User, bool> isUserAdminPredicate;      public RoleSpecification(bool isUserAdmin)     {         this.isUserAdminPredicate = (user) => user.IsAdmin == isUserAdmin;     }      public override Func<User, bool> AsExpression()     {         return isUserAdminPredicate;     } }  // використання foreach (User user in users.Where(specification.AsExpression())) {     Console.WriteLine(user); } 

Див. також

[ред. | ред. код]

Джерела

[ред. | ред. код]