【问题标题】:Appropriate design pattern for the payment modules c#支付模块的适当设计模式c#
【发布时间】:2018-02-11 13:47:23
【问题描述】:

我正在学习设计模式概念,并且还想使用适当的设计模式在我的项目中实施支付模块。因此,为此我创建了一些示例代码。

目前我有两个具体的付款方式PayPalCredit Card。但具体的实现将在项目中进一步添加。

支付服务

public interface IPaymentService
{
    void MakePayment<T>(T type) where T : class;
}

信用卡和 Pay Pal 服务

public class CreditCardPayment : IPaymentService
{
    public void MakePayment<T>(T type) where T : class
    {
        var creditCardModel = (CreditCardModel)(object)type;
        //Implementation CreditCardPayment
    }
}

class PayPalPayment : IPaymentService
{
    public void MakePayment<T>(T type) where T : class
    {
        var payPalModel = (PayPalModel)(object)type;
        //Further Implementation will goes here
    }
}

客户端代码实现

var obj = GetPaymentOption(payType);
obj.MakePayment<PayPalModel>(payPalModel);

获取付款选项

private static IPaymentService GetPaymentOption(PaymentType paymentType)
{
        IPaymentService paymentService = null;

        switch (paymentType)
        {
            case PaymentType.PayPalPayment:
                paymentService = new PayPalPayment();
                break;
            case PaymentType.CreditCardPayment:
                paymentService = new CreditCardPayment();
                break;
            default:
                break;
        }
        return paymentService;
}

我想用策略设计模式来实现这个模块,但我偏离了策略,最终这样做了。

这是创建支付模块的正确方法吗?有没有更好的方法来解决这种情况。这是一种设计模式吗?

编辑:

客户代码:

static void Main(string[] args)
{
    PaymentStrategy paymentStrategy = null;


    paymentStrategy = new PaymentStrategy(GetPaymentOption((PaymentType)1));
    paymentStrategy.Pay<PayPalModel>(new PayPalModel() { UserName = "", Password = "" });

    paymentStrategy = new PaymentStrategy(GetPaymentOption((PaymentType)2));
    paymentStrategy.Pay<CreditCardModel>(
       new CreditCardModel()
    {
        CardHolderName = "Aakash"
    });

    Console.ReadLine();

}

策略:

public class PaymentStrategy
{
    private readonly IPaymentService paymentService;
    public PaymentStrategy(IPaymentService paymentService)
    {
        this.paymentService = paymentService;
    }

    public void Pay<T>(T type) where T : class
    {
        paymentService.MakePayment(type);
    }
}

此更新是否与策略模式内联?

【问题讨论】:

  • 这看起来更像是代码审查,也是基于意见的。
  • @Nkosi 你的意思是问题不应该在这里?
  • 如果您以 CreditCardPayment 的形式实现了 IPaymentService 的具体实现,那么将 MakePayment 耦合到更具体的类型是否没有意义?您可以使用 IPaymentService,其中 T 是用于 MakePayment 的类型。
  • 接下来,让 CreditCard 和 PayPal 模型继承的 IPayModel 接口以及 IPaymentService 上的 MakePayment 方法采用 IPayModel 作为参数可能是有意义的
  • CreditCard 和 PayPal 模型具有不同的属性,它们没有任何共同的属性。既然他们没有共享任何共同的东西,那么从 IPayModel 接口继承类是否有意义?

标签: c# design-patterns


【解决方案1】:

为此使用抽象工厂的一个主要缺点是它包含一个 switch case 语句。这本质上意味着如果你想添加支付服务,你必须更新工厂类中的代码。这违反了Open-Closed Principal,其中规定实体应该对扩展开放但对修改关闭。

请注意,出于同样的原因,使用Enum 在支付提供商之间切换也会出现问题。这意味着每次添加或删除支付服务时,服务列表都必须更改。更糟糕的是,可以从策略中删除支付服务,但即使它无效,它仍然是一个 Enum 符号。

另一方面,使用策略模式不需要 switch case 语句。因此,当您添加或删除支付服务时,现有类不会发生任何变化。这一点,以及支付选项的数量可能会被限制在一个很小的两位数,这一事实使得该策略模式更适合这种情况。

接口

// Empty interface just to ensure that we get a compile
// error if we pass a model that does not belong to our
// payment system.
public interface IPaymentModel { }

public interface IPaymentService
{
    void MakePayment<T>(T model) where T : IPaymentModel;
    bool AppliesTo(Type provider);
}

public interface IPaymentStrategy
{
    void MakePayment<T>(T model) where T : IPaymentModel;
}

型号

public class CreditCardModel : IPaymentModel
{
    public string CardHolderName { get; set; }
    public string CardNumber { get; set; }
    public int ExpirtationMonth { get; set; }
    public int ExpirationYear { get; set; }
}

public class PayPalModel : IPaymentModel
{
    public string UserName { get; set; }
    public string Password { get; set; }
}

支付服务抽象

这是一个抽象类,用于隐藏 IPaymentService 实现中转换为具体模型类型的丑陋细节。

public abstract class PaymentService<TModel> : IPaymentService
    where TModel : IPaymentModel
{
    public virtual bool AppliesTo(Type provider)
    {
        return typeof(TModel).Equals(provider);
    }

    public void MakePayment<T>(T model) where T : IPaymentModel
    {
        MakePayment((TModel)(object)model);
    }

    protected abstract void MakePayment(TModel model);
}

支付服务实现

public class CreditCardPayment : PaymentService<CreditCardModel>
{
    protected override void MakePayment(CreditCardModel model)
    {
        //Implementation CreditCardPayment
    }
}

public class PayPalPayment : PaymentService<PayPalModel>
{
    protected override void MakePayment(PayPalModel model)
    {
        //Implementation PayPalPayment
    }
}

支付策略

这是将这一切联系在一起的类。其主要目的是根据传递的模型类型提供支付服务的选择功能。但与此处的其他示例不同,它松散地耦合了 IPaymentService 实现,因此此处不直接引用它们。这意味着在不改变设计的情况下,可以添加或删除支付提供商。

public class PaymentStrategy : IPaymentStrategy
{
    private readonly IEnumerable<IPaymentService> paymentServices;

    public PaymentStrategy(IEnumerable<IPaymentService> paymentServices)
    {  
        this.paymentServices = paymentServices ?? throw new ArgumentNullException(nameof(paymentServices));
    }

    public void MakePayment<T>(T model) where T : IPaymentModel
    {
        GetPaymentService(model).MakePayment(model);
    }

    private IPaymentService GetPaymentService<T>(T model) where T : IPaymentModel
    {
        var result = paymentServices.FirstOrDefault(p => p.AppliesTo(model.GetType()));
        if (result == null)
        {
            throw new InvalidOperationException(
                $"Payment service for {model.GetType().ToString()} not registered.");
        }
        return result;
    }
}

用法

// I am showing this in code, but you would normally 
// do this with your DI container in your composition 
// root, and the instance would be created by injecting 
// it somewhere.
var paymentStrategy = new PaymentStrategy(
    new IPaymentService[]
    {
        new CreditCardPayment(), // <-- inject any dependencies here
        new PayPalPayment()      // <-- inject any dependencies here
    });


// Then once it is injected, you simply do this...
var cc = new CreditCardModel() { CardHolderName = "Bob" /* Set other properties... */ };
paymentStrategy.MakePayment(cc);

// Or this...
var pp = new PayPalModel() { UserName = "Bob" /* Set other properties... */ };
paymentStrategy.MakePayment(pp);

其他参考资料:

【讨论】:

  • 在客户端代码中,如果我没有 switch case 语句,我如何知道要实例化哪些支付模型实例?因为在我的情况下,用户只选择了一种付款方式,我需要确定选择了哪种付款方式。
  • 这是 UI 的实现细节。没有规定不能使用Enum 来确定要实例化的模型。但是,您可能希望 UI 上的选择器是根据策略中的支付服务生成的(您可以为每个提供商添加一个属性),而不是一些硬编码的Enum。然后可以根据在策略中注册的实际提供程序在 UI 上更新可用提供程序的列表。
  • 优秀的帖子。您能解释一下 PaymentService 类的 MakePayment 方法中的 (TModel)(object)model 转换吗?在这里创建 IPaymentService 和 IPaymentService 是否有意义,其中 IPaymentService.MakePayment 在 IPaymentService 上显式实现?
  • (TModel)(object)model 是必需的,因为从TTModel 没有直接转换,所以我们首先对object 进行中间转换。
  • @deadManN - 如果您的意思是您希望分离多个职责,那么我建议将这些职责放入注入服务的各个类中(我表示“在此处注入任何依赖项” )。如果您的意思是您需要启动一个包含多个步骤的工作流程,那么您需要更多模式。不过,您也许可以使用该策略开始第一步。
【解决方案2】:

这是您可以采取的一种方法。从您的来源中没有太多可以继续,我真的会重新考虑让 MakePayment 无效,而不是像 IPayResult 这样的东西。

public interface IPayModel { }  // Worth investigating into common shared methods and properties for this 
public interface IPaymentService
{
    void MakePayment(IPayModel  payModel);
}
public interface IPaymentService<T> : IPaymentService where T : IPayModel
{
    void MakePayment(T payModel);  // Void here?  Is the status of the payment saved on the concrete pay model?  Why not an IPayResult?
}

public class CreditCardModel : IPayModel
{
    public string CardHolderName { get; set; }
}
public class PayPalModel : IPayModel
{
    public string UserName { get; set; }
    public string Password { get; set; }
}

public class CreditCardPayment : IPaymentService<CreditCardModel>
{
    public void MakePayment(CreditCardModel payModel)
    {
        //Implmentation CreditCardPayment
    }
    void IPaymentService.MakePayment(IPayModel payModel)
    {
        MakePayment(payModel as CreditCardModel);
    }
}
public class PayPalPayment : IPaymentService<PayPalModel>
{
    public void MakePayment(PayPalModel payModel)
    {
        //Implmentation PayPalPayment
    }
    void IPaymentService.MakePayment(IPayModel payModel)
    {
        MakePayment(payModel as PayPalModel);
    }
}

public enum PaymentType
{
    PayPalPayment = 1,
    CreditCardPayment = 2
}

所以按照你的实现方法,它可能看起来像:

static class Program
{
    static void Main(object[] args)
    {
        IPaymentService paymentStrategy = null;
        paymentStrategy = GetPaymentOption((PaymentType)1);
        paymentStrategy.MakePayment(new PayPalModel { UserName = "", Password = "" });

        paymentStrategy = GetPaymentOption((PaymentType)2);
        paymentStrategy.MakePayment(new CreditCardModel { CardHolderName = "Aakash" });

        Console.ReadLine();
    }

    private static IPaymentService GetPaymentOption(PaymentType paymentType) 
    {
        switch (paymentType)
        {
            case PaymentType.PayPalPayment:
                return new PayPalPayment();
            case PaymentType.CreditCardPayment:
                return new CreditCardPayment();
            default:
                throw new NotSupportedException($"Payment Type '{paymentType.ToString()}' Not Supported");
        }
    }
}

我还认为,对于策略/工厂模式方法,手动创建 IPayModel 类型没有多大意义。因此,您可以将 IPaymentService 扩展为 IPayModel 工厂:

public interface IPaymentService
{
    IPayModel CreatePayModel();
    void MakePayment(IPayModel payModel);
}
public interface IPaymentService<T> : IPaymentService where T : IPayModel
{
    new T CreatePayModel();
    void MakePayment(T payModel);
}

public class CreditCardPayment : IPaymentService<CreditCardModel>
{
    public CreditCardModel CreatePayModel()
    {
        return new CreditCardModel();
    }
    public void MakePayment(CreditCardModel payModel)
    {
        //Implmentation CreditCardPayment
    }

    IPayModel IPaymentService.CreatePayModel()
    {
        return CreatePayModel();
    }
    void IPaymentService.MakePayment(IPayModel payModel)
    {
        MakePayment(payModel as CreditCardModel);
    }
} 

然后用法是:

IPaymentService paymentStrategy = null;
paymentStrategy = GetPaymentOption((PaymentType)1);

var payModel = (PayPalModel)paymentStrategy.CreatePayModel();
payModel.UserName = "";
payModel.Password = "";
paymentStrategy.MakePayment(payModel);

【讨论】:

    【解决方案3】:

    您的代码基本上是使用工厂模式。这是处理多种付款方式的好方法

    http://www.dotnettricks.com/learn/designpatterns/factory-method-design-pattern-dotnet

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-02-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-20
    • 2013-10-21
    • 1970-01-01
    相关资源
    最近更新 更多