自定义访问者模式实现

时间:2019-02-19 15:51:42

标签: c# design-patterns interface visitor-pattern

我正在尝试实现一种访客模式。 Web上的大多数示例都显示了具有“访问”方法和该方法的多个重载的访问者类。在这种情况下,我将其重载称为“访问”方法CalculateFee(这是语义问题)。到目前为止,一切都还可以,但是现在我需要再次实现一个访问者,以执行另一个方法“ CalculateExtraCharge”,因此我添加了另一个名为CalculateExtraCharge的方法,并带有重载。但是现在我有两个问题

1)这是该模式的错误实现吗?

2)我应该始终将我的方法称为“访问”吗?

这是我的代码的概述,我省略了部分代码,只专注于对我的问题重要的事情。

    public class CreditCard : IPaymentMethod
    {
        public decimal Amount { get; set; }

        public decimal GetFee(IPaymentCalculationsVisitor visitor)
        {
            return visitor.CalculateFee(this);
        }

        public decimal GetExtraCharge(IPaymentCalculationsVisitor visitor)
        {
            return visitor.CalculateExtraCharge(this);
        }

    }

    public class Check : IPaymentMethod
    {
        public decimal Amount { get; set; }

        public decimal GetFee(IPaymentCalculationsVisitor visitor)
        {
            return visitor.CalculateFee(this);
        }

        public decimal GetExtraCharge(IPaymentCalculationsVisitor visitor)
        {
            return visitor.CalculateExtraCharge(this);
        }
    }

    public interface IPaymentCalculationsVisitor
    {
        decimal CalculateFee(CreditCard creditCard);
        decimal CalculateFee(Check check);

        decimal CalculateExtraCharge(CreditCard creditCard);
        decimal CalculateExtraCharge(Check check);
    }

    public class PaymentCalculationsVisitor: IPaymentCalculationsVisitor
    {

        public decimal CalculateFee(CreditCard creditCard)
        {
            return creditCard.Amount * 0.15m;

        }

        public decimal CalculateFee(Check check)
        {
            return check.Amount * 0.10m;
        }

        public decimal CalculateExtraCharge(CreditCard creditCard)
        {
            return 15;
        }

        public decimal CalculateExtraCharge(Check check)
        {
            return 10;
        }

    }

    public class PaymentProcessor
    {

        public void ProcessPayment()
        {
            var paymentMethods = new List<IPaymentMethod>()
            {
                new CreditCard(),
                new Check()
            };

            var calculationsVisitor = new PaymentCalculationsVisitor();

            foreach (var paymentMethod in paymentMethods)
            {

                //First i need to get the fee
                var fee = paymentMethod.GetFee(calculationsVisitor);

                //Then i do do some other stuff, validations, other calculations etc

                //Finally i get the extra charge
                var extraCharge = paymentMethod.GetExtraCharge(calculationsVisitor);
            }

        }

    }

2 个答案:

答案 0 :(得分:3)

  

2)我是否应该始终将我的方法称为“访问”?

否,以更特定于域的方式命名方法。

  

1)这是该模式的错误实现吗?

看着您的实现,我发现它有点不同。

public class CreditCard : IPaymentMethod
{
    public decimal Amount { get; set; }

    public decimal GetFee(IPaymentCalculationsVisitor visitor)
    {
        return visitor.CalculateFee(this);
    }

    public decimal GetExtraCharge(IPaymentCalculationsVisitor visitor)
    {
        return visitor.CalculateExtraCharge(this);
    }
}

封装是一种面向对象的编程,其中对象欠其数据(不暴露给外界)。
使用Visitor模式,我们可以为对象提供额外的功能,而无需将其数据暴露在外部。

由于内部数据没有暴露给对象的外部,因此访问者需要“访问对象内部”,其中对象将能够向访问者提供所需的值,而不会在外部暴露这些值(而不会公开这些值)。

对于问题的情况,我们可以将Calculator(visitor)传递到CreditCard类中,其中Calculator仅将必需的数据作为参数(仅注意必需的值-而非整个对象)。

public class CreditCard : IPaymentMethod
{
    // Following OOP principles and keep data private
    private decimal _amount;

    public CreditCard(decimal amount) => _amount;

    public decimal GetFee(IPaymentCalculationsVisitor visitor)
    {
        return visitor.CalculateFee(_amount); // provide only required data
    }

    public decimal GetExtraCharge(IPaymentCalculationsVisitor visitor)
    {
        return visitor.CalculateExtraCharge(_amount); // provide only required data
    }
}

使用这种方法,Calculator(visitor)类将不会依赖于它可以访问的类。实际上,它可以访问任何可以提供所需信息的课程。

在特定情况下,CreditCard公开数据(具有公共财产Amount)-您可以删除多余的步骤并将信用卡对象直接传递给计算

 public void ProcessPayment()
 {
     var paymentMethods = new List<IPaymentMethod>()
     {
            new CreditCard(),
            new Check()
     };

     var calculations = new PaymentCalculationsVisitor();

     foreach (var paymentMethod in paymentMethods)
     {
        //First i need to get the fee
        var fee = calculations.GetFee(paymentMethod);

        //Then i do do some other stuff, validations, other calculations etc

        //Finally i get the extra charge
        var extraCharge = calculations.GetExtraCharge(paymentMethod);
    }
}

答案 1 :(得分:1)

  

1)这是该模式的错误实现吗?

否,这仍然是GoF访问者模式。 IPaymentCalculationsVisitor访问两种不同方法的能力不会改变模式的性质。因为它结合了两次不同访问的逻辑,所以您可能要考虑SOLID原则。

  • 这两种方法是否都是单一职责的一部分,即它们是否会由于相同的原因而改变,或者一种方法有可能独立于另一种方法而改变?
  • 潜在客户会始终依赖于这两种方法吗? 接口隔离将使CalculateFeeCalculateExtraCharge分离,因此客户端可以拥有一个而没有另一个。

请注意,访问者模式可以允许将新行为添加到IPaymentMethod类型的层次结构中,而无需修改诸如CreditCardCheck之类的子类。通过将访问者界面分为FeeVisitorExtraChargeVisitor,两者都可以传递到单个访问方法中。

  

2)我应该始终将我的方法称为“访问”吗?

否,在任何设计模式下,您都可以根据自己的喜好命名方法。最重要的是,代码对您及其领域具有意义。为了与其他开发人员通俗易懂,如果您认为可以澄清代码的意图,则可以考虑使用该模式的已发布术语添加文档。