Шаблон посетителей и инкапсуляция - PullRequest
8 голосов
/ 15 августа 2011

Шаблон дизайна посетителя - это способ отделить алгоритм от структуры объекта, с которой он работает. Это официальное определение этого. Я пытаюсь понять, как это не нарушает инкапсуляцию. Например, если у меня есть разные типы классов для разных типов банковских счетов [Saving / Fixed / Current], реализующих абстрактный класс Account, я должен поместить метод расчета процентов в качестве абстрактного метода в абстрактный класс Account или отправить учетную запись набрать в реализации посетителя и рассчитать там?

Метод 1: Должна ли реализация Посетителя нести ответственность за начисление процентов за разные типы счетов?

public interface IInterestVisitor
{
    void GetInterest(Savings AccountType);
    void GetInterest(Fixed AccountType);
    void GetInterest(Current AccountType);
}

Метод 2: Или это должны сделать разработчики класса Account?

public abstract class Account
{
    public abstract void AcceptCalculateInterestVisitor(IInterestVisitor iv);
    public abstract int CalculateInterestAmount();
}

Если я использую метод 1, который является реализацией посетителя, которая реализует IInterestVisitor, как указано выше, то задача расчета процентов будет делегирована классу посетителя. Используя этот подход, если я добавлю другой тип учетной записи, то мне придется изменять реализацию посетителя каждый раз, когда появляется новая учетная запись.

Однако, если я оставлю бит начисления процентов на реализацию абстрактных классов Account, как описано выше в методе 2, то, по моему мнению, [ исправьте меня, если я ошибаюсь ] Я не нарушаю инкапсуляцию. Кроме того, требуется меньше кода для модификации, так как все, что я делаю, это добавляю новый класс и заставляю посетителя реализовать интерфейс, подобный показанному ниже

public interface IInterestVisitor
{
    void GetInterest(Account AccountType);
}


public class InterestVisitor : IInterestVisitor
{
    public void GetInterest(Account AccountType)
    {
        int i = AccountType.CalculateInterestAmount();
        Console.WriteLine(i);
    }
}

Как видите, для класса заинтересованных посетителей, использующего метод 2, не требуется никаких модификаций. Не нарушает ли метод 1 инкапсуляцию? Может ли метод 2 все еще называться шаблоном посетителя?

Спасибо за чтение ...

Ответы [ 5 ]

5 голосов
/ 15 августа 2011

Посетитель позволяет определить новую операцию без изменения классов элементов, с которыми он работает.

В предоставленном коде я не вижу необходимости изменять объект вЧтобы соответствовать вашим потребностям, то, что в основном делает Visitor, это изменение состояния объекта с использованием предоставленного Properties / Methods / Fields самого объекта.

Итак, по моему мнению, ваш код может соответствовать шаблону Visitor, если это действительно был вопрос, также причиной являются шаблоны Guidelines и , а не жесткие правила.

Я бы лично выбрал второй путь, так как он гораздо более понятен и ориентирован на ООП.

С уважением.

5 голосов
/ 15 августа 2011

Я вообще не вижу необходимости в посетителе - ваш подход прекрасно иллюстрирует, что вы можете решить эту проблему просто с помощью полиморфизма, используя CalculateInterestAmount метод, который могут реализовать подклассы.

По моему мнениювам действительно нужен очень убедительный пример, чтобы рассмотреть возможность использования шаблона Visitor - в большинстве случаев другие решения более просты и подходят более естественно.

Сказав это, ваша версия 2 на самом деле просто использует полиморфизм - нет никакой выгодыиспользования посетителя таким образом.Версия 1 лучше иллюстрирует подход «двойной отправки» посетителя, именно так обычно работает посетитель.

2 голосов
/ 15 августа 2011

Вот пример посетителя, который сообщает о статусе разными способами, не влияя на исходные классы / объекты Аккаунта.Не стоит заменять возможности посетителей в виртуальных функциях, таких как PrintReport (), SmsReport () и т. Д., Где у вас будет слишком много методов для реализации.Используя Visitor, вы можете расширить возможности другим способом, не затрагивая объект. Я привел этот пример, так как вы спросили / упомянули, куда подходит посетитель .. По моему мнению, посетитель не подойдет обоим методам, которые вы упомянули.

public interface IAccount
{
    //Below are properties and methods which you expose outside
    // accoring to your class/domain
    double Interest { get;}
    //double Balance { get;}
    //ContactInfo Contact{ get;}
    void Visit(IAccountVisitor visitor);
}

public class AccountBase
{
}

public class FixedDeposit: AccountBase, IAccount
{
    double Interest { 
            get{
                return  CalculateInterest(); //don't change object state                    
            }
                    ;}

    protected double CalculateInterest()
    {
        return <<your expression to calculate>>;
    }

    public void Visit(IAccountVisitor visitor)
    {
        visitor.Visit(this);
    }
}

public class XYZBlashBlahDeposit: AccountBase, IAccount
{
    public void Visit(IAccountVisitor visitor)
    {
        visitor.Visit(this);
    }
}

...
...

public interface IAccountVisitor
{
    //void Report(IAccount account); //This is prefered and safe and 
    void Visit(Account account);
}

public class PrintReportVisitor: IAccountVisitor
{
    public void Visit(Account account)
    {
        //Get object information
        //Print to printer
    }
}

public class DisplayReportVisitor: IAccountVisitor
{
    public void Visit(Account account)
    {
        //Get object information
        //Display on monitor
    }
}

public class ReportAudioVisitor: IAccountVisitor
{
    public void Visit(Account account)
    {
        //Get object information
        //Read out the information
    }
}

public class SmsReportVisitor: IAccountVisitor
{
    public void Visit(Account account)
    {
        //Get object information
        //Send SMS to my mobile registered with account
    }
}

public class EmailReportVisitor: IAccountVisitor
{
    public void Visit(Account account)
    {
        //Get object information
        //Send Email to my email registered with account
    }
}

Посмотрите, есть ли у вас приложение, как использовать Visitor.

public class ReportViewer: UserControl
{
    IAccount _Account;
    public ReportViewer(IAccount account)
    {  
         this._Account = account;
         InitializeComponent();
    }
    void btnClick_Print()
    {
        _Account.Visit(new PrintReportVisitor());
    }

    void btnClick_ViewReport()
    {
        _Account.Visit(new DisplayReportVisitor(this));
    }

    void btnClick_SendSMS()
    {
        _Account.Visit(new SMSReportVisitor(this));
    }

    void btnClick_SendEmail()
    {
        _Account.Visit(new EmailReportVisitor(this));
    }
}
1 голос
/ 15 августа 2011

@ Тигран отметил, что Visitor полезен, когда вы не можете изменить посещаемые классы.

Поскольку вы используете C #, возможно, он более модульный и эффективный для использования Методы расширения .Они делают то же самое, добавляя функциональность к типу после факта, но вам больше не нужно носить с собой все возможные реализации в одном месте.

1 голос
/ 15 августа 2011

Посетитель помогает в ситуациях, когда логика может зависеть как от типа посещаемого, так и от типа посетителя - это способ структурирования двойной отправки.

В вашем сценарии у вас есть требование для начисления процентов, и это разумная операция со счетом, которая очень четко зависит от характера счета. Следовательно, как вы указали, посетитель не добавляет никакой ценности и кажется неуместным.

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

Нам также необходимо выполнить определенные расчеты: какова прогнозируемая стоимость счета на определенную дату? Какова стоимость аккаунта в качестве залога кредита сейчас? Какова стоимость счета, если закрыт сегодня. Такие расчеты необходимо выполнять по-разному (возможно, с использованием методов получения необработанных данных) для каждого типа счета.

Теперь мы видим некоторое значение в шаблоне Visitor. Поскольку мы разрабатываем новый интересный расчет, мы пишем нового посетителя, нам не нужно выпускать новые версии классов учетных записей.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...