В чем преимущество шаблона посетителя в API? - PullRequest
0 голосов
/ 05 января 2019

Я пытаюсь понять преимущества использования шаблона посетителя в API. Ниже приведен пример, который я видел, и я хотел получить пример того, почему шаблон выгоден, то есть выгоден. Какая будет альтернативная реализация, которая будет негативной и почему по сравнению с этим. Какую пользу можно извлечь из приведенной ниже реализации. В этом API он связывается с несколькими университетами, чтобы получить курсы, которые они предлагают. Затем каждый сервис курса get имеет определенное количество ответов с использованием шаблона посетителя:

Контроллер

[HttpGet]
public async Task<IActionResult> Get()
{
    // CourseService already retrieved for a given uni 
    var result = await courseService.GetCourses(userSession);
    return result.Accept(new CourseVisitor());
}

Сервис - у каждого Uni есть свой сервис GetCourses, но все они имеют настроенные ответы из-за шаблона посетителя

public async Task<CoursesResult> GetCourses(UserSession userSession) {

// Depending on response from a given uni a set number of responses can be returned across ass uni services e.g
return new CoursesResult.BadRequest(); **or**
return new CoursesResult.Success(); etc
}

Элемент Абстрактный / Бетонный элемент

  public abstract class GetCourses
    {
        public abstract T Accept<T>(ICourseVisitor<T> visitor);

        public class Successful : CoursesResult
        {
            public CourseList Response { get; }

            public Successful(CourseList response)
            {
                Response = response;
            }

            public override T Accept<T>(ICourseVisitor<T> visitor)
            {
                return visitor.Visit(this);
            }
        }
   // Other responses then defined e.g Bad Request

IVisitor

    public interface ICourseVisitor<out T>
{
    T Visit(GetCoursesResult.Successful result);
    T Visit(GetCoursesResult.BadRequest result);

1024 * посетителей *

    internal class CourseVisitor : ICourseVisitor<IActionResult>
{
    public IActionResult Visit(GetCourses.Successful result)
    {
        return new OkObjectResult(result.Response);
    }

ОБНОВЛЕННЫЙ ЗАПРОС Кроме того, я пытаюсь понять, почему служба не может вернуть что-то вроде этого:

//Service returns this: return new Successful(listResponse)

 public interface ICoursesResult
    {
      IActionResult Accept();
    }

 public class Successful : ICoursesResult
    {
        public CourseList Response { get; }

        public Successful(CourseList response)
        {
            Response = response;
        }

        public IActionResult Accept()
        {
            return OkObjectResult(Response);
        }
    }

Ответы [ 2 ]

0 голосов
/ 08 января 2019

Шаблон посетителя обычно используется, когда у вас есть полиморфный тип, и вы хотите выполнить внешне определенную операцию на основе определенного подтипа объекта. В вашем примере CoursesResult - это полиморфный тип, и посетитель позволяет вам преобразовать ответ Successful в OkObjectResult без непосредственной связи этих двух типов.

Ваш альтернативный подход, когда CoursesResult напрямую возвращает IActionResult, является традиционным полиморфизмом, который проще, но связывает доменную логику со слоем MVC.

Теперь я не знаю, как выглядит ваш полный набор ответов, но если у вас есть только один успешный ответ, а остальные являются ошибками, то самый простой подход здесь - напрямую вернуть успешный ответ и выдать исключения для другие случаи:

public async Task<CourseList> GetCourses(UserSession userSession) {
  return courseList; /* or */ throw new BadRequestException();
}

Тогда ваш контроллер может просто перехватывать исключения и преобразовывать их в соответствующие IActionResult.

0 голосов
/ 06 января 2019

Существует обширное исследование по этому вопросу в проекте кода - Шаблон посетителя повторно объяснен .

Я предоставлю заголовок.

Шаблон посетителя здесь, чтобы решить вещи, представив два аспекта:

  • Существует итерационный механизм, который знает, как перебирать объекты. hirerachy.it ничего не знает о поведении объектов в иерархия.
  • Новое поведение, которое необходимо реализовать, ничего не знает о механизм итерации, они не знают, как перебрать объект иерархия.

Теперь Оба этих аспектов не зависят друг от друга, и их не следует смешивать друг с другом. Итак, это все о принципале ООП, известном как принципал единой ответственности , возвращающий вас к ТВЕРДОМУ архитектуре .


Ключевые игроки в этой функции:

  • Посетитель - Интерфейс, который определяет операцию посещения. Это ядро шаблона посетителя. Он определяет операцию посещения для каждого типа бетонного элемента в структуре объекта.
  • ConcreateVisitor - Реализует операции, определенные в Visitor интерфейс.
  • ElementBase : Это абстрактный / интерфейс, который определяет Accept операция, которая принимает посетителя в качестве аргумента.
  • ConcreateElement - В этих типах реализован метод принятия элемента. интерфейс.
  • Структура объекта - Содержит все элементы структуры данных как коллекция, список или что-то, что может быть перечислено и использовано гости. Это обеспечивает интерфейс для всех посетителей, чтобы посетить его элемент. Эти элементы включают в себя метод под названием «Принять». коллекция затем перечисляется

Теперь цель всего этого шаблона, ключа , состоит в том, чтобы создать модель данных с ограниченными функциональными возможностями и набор посетителей с определенными функциональными возможностями, которые будут работать с данными. Шаблон позволяет посетителю посетить каждый элемент структуры данных, передавая объект в качестве аргумента методам посетителя.


Преимущества в конце концов-

Ключ отделения алгоритма от его модели данных - это возможность легко добавлять новые поведения. Классы модели данных создаются с помощью общего метода Visit, который может принимать объект посетителя во время выполнения. Затем можно создать другой объект посетителя и передать его этому методу, затем этот метод вызвал обратный вызов метода посетителя, передав ему себя в качестве параметра.

Стоит упомянуть еще одну вещь: :

Добавление нового типа в иерархию объектов требует изменений для всех посетителей, и это следует рассматривать как преимущество, поскольку оно определенно заставляет нас добавлять новый тип во все места, где вы сохранили некоторый код, специфичный для типа. По сути, это не просто позволяет забыть об этом.

Шаблон посетителя полезен только:

  • Если интерфейс, который вы хотите реализовать, довольно статичен и не изменить много.
  • Если все типы известны заранее, то есть во время проектирования все объекты должен быть известен.

В нижней строке:

Посетитель реализует следующие принципы проектирования :

  • Разделение интересов - Шаблон посетителя продвигает этот принцип, несколько аспектов / проблем разделены на несколько других классов, как это способствует более чистому коду и повторному использованию кода, а также коду более проверяемый.
  • Принцип единой ответственности - образец посетителя также обеспечивает это принцип. Объект должен иметь почти одну ответственность. несвязанный поведение должно быть отделено от него другим классом.
  • Принцип Открытого Закрытия - шаблон посетителя также следует этому принципу, как будто мы хотим расширить поведение объекта, исходный код не изменен. Шаблон Visitor предоставляет нам механизм для separaэто другой класс и применить эти операции для объекта во время выполнения.

Преимущества реализации Visitor: :

  • Отделите поведение структуры данных от них. Отдельные посетители Объект создан для реализации такого поведения.
  • Это решает проблему Double Dispatch, с которой редко сталкиваются, но имеет важное влияние.

Вы можете углубиться в статью, чтобы понять весь смысл этого, но если я приведу пример:

Прежде всего мы определим интерфейс, который мы называем IVisitable. Он определит единственный метод Accept, который будет принимать аргумент IVisitor. Этот интерфейс будет служить основой для всех типов в списке продуктов. Все типы, такие как Book, Car и Wine (в нашем примере) будут реализовывать этот тип.

/// <summary>
/// Define Visitable Interface.This is to enforce Visit method for all items in product.
/// </summary>
internal interface IVisitable
{
    void Accept(IVisitor visit);
}   

Тогда мы это осуществим:

  #region "Structure Implementations"


    /// <summary>
    /// Define base class for all items in products to share some common state or behaviors.
    /// Thic class implement IVisitable,so it allows products to be Visitable.
    /// </summary>
    internal abstract class Product : IVisitable
    {
        public int Price { get; set; }
        public abstract void Accept(IVisitor visit);
    }

    /// <summary>
    /// Define Book Class which is of Product type.
    /// </summary>
    internal class Book : Product
    {
        // Book specific data

        public Book(int price)
        {
            this.Price = price;
        }
        public override void Accept(IVisitor visitor)
        {
            visitor.Visit(this);
        }
    }

    /// <summary>
    /// Define Car Class which is of Product type.
    /// </summary>
    internal class Car : Product
    {
        // Car specific data

        public Car(int price)
        {
            this.Price = price;
        }

        public override void Accept(IVisitor visitor)
        {
            visitor.Visit(this);
        }
    }

    /// <summary>
    /// Define Wine Class which is of Product type.
    /// </summary>
    internal class Wine : Product
    {
        // Wine specific data
        public Wine(int price)
        {
            this.Price = price;
        }

        public override void Accept(IVisitor visitor)
        {
            visitor.Visit(this);
        }
    }

    #endregion "Structure Implementations"  

Создайте интерфейс посетителя и внедрите его:

/// <summary>
/// Define basic Visitor Interface.
/// </summary>
internal interface IVisitor
{
    void Visit(Book book);
    void Visit(Car car);
    void Visit(Wine wine);
}

#region "Visitor Implementation"


/// <summary>
/// Define Visitor of Basic Tax Calculator.
/// </summary>
internal class BasicPriceVisitor : IVisitor
{
    public int taxToPay { get; private set; }
    public int totalPrice { get; private set; }

    public void Visit(Book book)
    {
        var calculatedTax = (book.Price * 10) / 100;
        totalPrice += book.Price + calculatedTax;
        taxToPay += calculatedTax;
    }

    public void Visit(Car car)
    {
        var calculatedTax = (car.Price * 30) / 100;
        totalPrice += car.Price + calculatedTax;
        taxToPay += calculatedTax;
    }

    public void Visit(Wine wine)
    {
        var calculatedTax = (wine.Price * 32) / 100;
        totalPrice += wine.Price + calculatedTax;
        taxToPay += calculatedTax;
    }
}


#endregion "Visitor Implementation"

Исполнение:

 static void Main(string[] args)
    {
        Program.ShowHeader("Visitor Pattern");

        List<Product> products = new List<Product>
        { 
            new Book(200),new Book(205),new Book(303),new Wine(706)
        };

        ShowTitle("Basic Price calculation");
        BasicPriceVisitor pricevisitor = new BasicPriceVisitor();
        products.ForEach(x =>
        {
            x.Accept(pricevisitor);
        });

        Console.WriteLine("");
    }     
...