web-dev-qa-db-fra.com

Modèle de conception approprié pour les modules de paiement c #

En apprenant par le biais du concept de modèle de conception, je souhaitais également mettre en œuvre les modules de paiement dans mon projet en utilisant le modèle de conception approprié. Donc, pour cela, j'ai créé un exemple de code.

Actuellement, j'ai deux implémentations concrètes pour le paiement Paypal et Credit Card. Mais la mise en œuvre concrète sera ajoutée plus loin dans le projet.

Service de paiement

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

Service de carte de crédit et paypal

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

Implémentation du code client

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

Obtenir une option de paiement

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

Je pensais mettre en œuvre ces modules en utilisant un modèle de conception de stratégie, et je me suis écarté de Strategy pour finalement le faire.

Est-ce une bonne façon de créer les modules de paiement? Existe-t-il une meilleure approche pour résoudre ce scénario? Est-ce un motif de design?

Édité:

Code client:

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

}

Stratégie:

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

Cette mise à jour est-elle conforme au modèle de stratégie?

7
aakash

Un inconvénient majeur de l’utilisation d’une fabrique abstraite à cet effet est le fait qu’elle contient une instruction case switch. Cela signifie que si vous souhaitez ajouter un service de paiement, vous devez mettre à jour le code dans la classe fabrique. Ceci est une violation du Principal ouvert-fermé qui stipule que les entités doivent être ouvertes pour extension mais fermées pour modification.

Notez que l'utilisation d'une Enum pour changer de fournisseur de paiement pose également problème pour la même raison. Cela signifie que la liste des services devra être modifiée chaque fois qu'un service de paiement est ajouté ou supprimé. Pire encore, un service de paiement peut être supprimé de la stratégie, tout en restant un symbole Enum, même s'il n'est pas valide.

D'autre part, l'utilisation d'un modèle de stratégie ne nécessite pas d'instruction de cas de commutation. Par conséquent, il n'y a aucune modification des classes existantes lorsque vous ajoutez ou supprimez un service de paiement. Ceci, et le fait que le nombre d'options de paiement sera probablement limité à un petit nombre à deux chiffres, rend le modèle de stratégie plus adapté à ce scénario.

Des interfaces

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

Des modèles

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

Abstraction de service de paiement

Voici une classe abstraite utilisée pour masquer les détails laids de la conversion au type de modèle concret dans les implémentations 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);
}

Mise en œuvre des services de paiement

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

Stratégie de paiement

Voici la classe qui lie tout cela ensemble. Son objectif principal est de fournir la fonctionnalité de sélection du service de paiement en fonction du type de modèle transmis. Mais contrairement à d’autres exemples ici, il couple vaguement les implémentations IPaymentService afin qu’elles ne soient pas directement référencées ici. Cela signifie que sans modifier la conception, les fournisseurs de paiement peuvent être ajoutés ou supprimés.

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

    public PaymentStrategy(IEnumerable<IPaymentService> paymentServices)
    {
        if (paymentServices == null)
            throw new ArgumentNullException(nameof(paymentServices));
        this.paymentServices = 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;
    }
}

Usage

// 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" };
paymentStrategy.MakePayment(cc);

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

Références supplémentaires:

6
NightOwl888

C'est une approche que vous pourriez prendre. Il n’ya pas grand-chose à faire de votre part, et j’aimerais vraiment reconsidérer le fait que MakePayment est un vide au lieu d’avoir un résultat similaire à 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
}

Donc, suivant votre approche de mise en œuvre, cela pourrait ressembler à:

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

Je pense aussi que, pour une approche modèle/stratégie, créer manuellement un type IPayModel n'a pas beaucoup de sens. Par conséquent, vous pouvez développer IPaymentService en tant qu’usine 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);
    }
} 

L'utilisation serait alors:

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

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

À mon avis, c’est une bonne utilisation du modèle Strategy. Je dirais que vous écrivez une interface appelée PaymentStrategy et créez deux implémentations concrètes de celle-ci. Un pour Paypal et un autre pour les paiements par carte de crédit. Ensuite, à l’intérieur de votre client, vous pouvez déterminer quelle stratégie de paiement doit être utilisée sur la base d’une sélection d’utilisateurs transmise du serveur frontal. Ensuite, transmettez cette PaymentStrategy à votre classe context, qui effectue le processus de paiement.

Dans l'exemple ci-dessus, vous n'utilisez ni modèle FactoryMethod, ni modèle AbstractFactory. Je ne vois pas cela comme un bon candidat pour le modèle factory non plus.

Non, ce que vous faites n'est pas un motif Strategy. Il devrait être changé comme ça.

public interface PaymentStrategy {
   void doPayment();
}

public class PaypalStrategy implements PaymentStrategy {
   @Override
   void doPayment() {
      // implement this.
   }
}
public class PaymentService {
    private final PaymentStrategy paymentStrategy;

    public PaymentService(PaymentStrategy paymentStrategy) {
        this.paymentStrategy = paymentStrategy;
    }
    public void pay() {
      this.paymentStrategy.doPayment();
      // Do some more here.
    }
}

Et votre client devrait ressembler à ceci.

new PaymentService(new PaypalStrategy()).pay();
0
Ravindra Ranwala

Votre code utilise essentiellement le modèle d'usine. C'est un bon moyen de gérer plus d'un mode de paiement.

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

0
Ken Tucker