自定义访客模式实施

Custom visitor pattern implementation

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

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

2) 我应该总是调用我的方法 "visit" 吗?

这里是我的代码的概述,我省略了其中的一些部分以仅关注对我的问题重要的部分。

    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);
            }

        }

    }

1) Is this a wrong implementation of the pattern?

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

  • 这两种方法都是单一职责的一部分吗,也就是说,它们会因为同样的原因而改变,还是一种方法可能会独立于另一种方法而改变?
  • 潜在客户是否总是依赖于这两种方法? 接口隔离 将解耦 CalculateFeeCalculateExtraCharge,因此客户可以有一个没有另一个。

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

2) Should i always call my method "visit"?

不,在任何设计模式中,您可以随意命名您的方法。最重要的是代码对您及其领域有意义。为了与其他开发人员使用通用词汇表,您可以考虑使用模式的已发布术语添加文档,如果您认为这样可以阐明代码的意图。

2) Should i always call my method "visit"?

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

1) Is this a wrong implementation of the pattern?

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

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);
    }
}

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

因为内部数据没有暴露给对象的外部,访问者需要"visit inside the object",其中对象将能够向访问者提供所需的值而不暴露这些值(不使这些值public).

对于问题的情况,我们可以将 calculator(visitor) 传递给 CreditCard class,其中 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
    }
}

使用这种方法,计算器(访问者)class 将不依赖于它可以访问的 classes。实际上它可以访问任何可以提供所需信息的class。

CreditCard 公开数据的特定情况下(有 public 属性 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);
    }
}