web-dev-qa-db-de.com

Angemessenes Designmuster für die Zahlungsmodule c #

Da ich durch Design Pattern Konzept lerne und auch die Zahlungsmodule in meinem Projekt mit dem richtigen Design Pattern implementieren wollte. Deshalb habe ich ein paar Beispielcodes erstellt.

Derzeit habe ich zwei konkrete Implementierungen für die Zahlungsvariablen Paypal und Credit Card. Die konkrete Umsetzung wird jedoch im weiteren Verlauf des Projekts ergänzt.

Zahlungsservice

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

Kreditkarte und Pay Pal Service

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

Client Code Implementierung

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

Zahlungsoption erhalten

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

Ich habe überlegt, diese Module mit Hilfe des Strategy-Design-Musters zu implementieren, und bin von Strategy abgewichen und habe dies auch getan.

Ist dies eine geeignete Methode zum Erstellen der Zahlungsmodule? Gibt es einen besseren Lösungsansatz für dieses Szenario? Ist das ein Entwurfsmuster?

Bearbeitet:

Kundencode:

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

}

Strategie:

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

Entspricht dieses Update dem Strategiemuster?

7
aakash

Ein Hauptnachteil der Verwendung einer abstrakten Factory ist die Tatsache, dass sie eine switch case-Anweisung enthält. Das heißt, wenn Sie einen Zahlungsdienst hinzufügen möchten, müssen Sie den Code in der Factory-Klasse aktualisieren. Dies ist eine Verletzung des Open-Closed Principal , der besagt, dass Entitäten zur Erweiterung geöffnet, aber zur Änderung geschlossen sein sollten.

Beachten Sie, dass die Verwendung einer Enumzum Wechseln zwischen Zahlungsanbietern aus demselben Grund ebenfalls problematisch ist. Dies bedeutet, dass sich die Liste der Dienste jedes Mal ändern muss, wenn ein Zahlungsdienst hinzugefügt oder entfernt wird. Noch schlimmer ist, dass ein Zahlungsservice aus der Strategie entfernt werden kann, aber dennoch ein Enum-Symbol für ihn darstellt, obwohl er nicht gültig ist.

Andererseits erfordert die Verwendung eines Strategiemusters keine switch case-Anweisung. Wenn Sie einen Zahlungsdienst hinzufügen oder entfernen, werden daher keine Änderungen an vorhandenen Klassen vorgenommen. Dies und die Tatsache, dass die Anzahl der Zahlungsoptionen wahrscheinlich auf eine kleine zweistellige Zahl begrenzt wird, macht das Strategiemuster besser für dieses Szenario geeignet.

Schnittstellen

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

Modelle

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

Zahlungsservice Abstraktion

Hier ist eine abstrakte Klasse, die verwendet wird, um die unschönen Details der Umwandlung in den konkreten Modelltyp vor den Implementierungen von IPaymentServicezu verbergen.

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

Implementierungen von Zahlungsdiensten

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

Zahlungsstrategie

Hier ist die Klasse, die alles zusammenhält. Ihr Hauptzweck besteht darin, die Auswahlfunktionalität des Zahlungsdienstes basierend auf der Art des übergebenen Modells bereitzustellen. Im Gegensatz zu anderen Beispielen hier werden die Implementierungen von IPaymentServicejedoch lose gekoppelt, sodass hier nicht direkt auf sie verwiesen wird. Dies bedeutet, dass Zahlungsanbieter hinzugefügt oder entfernt werden können, ohne das Design zu ändern.

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

Verwendungszweck

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

Zusätzliche Referenzen:

6
NightOwl888

Dies ist ein Ansatz, den Sie ergreifen könnten. Es gibt nicht viel zu tun von Ihrer Quelle, und ich würde wirklich überdenken, dass MakePayment eine Lücke anstelle von so etwas wie einem IPayResult ist.

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
}

Wenn Sie also Ihrem Implementierungsansatz folgen, könnte dies ungefähr so ​​aussehen:

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

Ich denke auch, dass es für einen Strategie-/Factory-Pattern-Ansatz wenig Sinn macht, einen IPayModel-Typ manuell zu erstellen. Daher können Sie den IPaymentService als IPayModel-Factory erweitern:

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

Die Verwendung wäre dann:

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

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

Meiner Meinung nach ist dies eine gute Verwendung des Musters Strategyname__. Ich würde sagen, Sie schreiben eine Schnittstelle namens PaymentStrategyund erstellen zwei konkrete Implementierungen davon. Eine für Paypal und eine für Kreditkartenzahlungen. Anschließend können Sie in Ihrem Kunden anhand einer vom Frontend übergebenen Benutzerauswahl festlegen, welche Zahlungsstrategie verwendet werden soll. Übergeben Sie dann diese PaymentStrategyan Ihre contextname__-Klasse, die den eigentlichen Zahlungsvorgang durchführt.

Im obigen Beispiel verwenden Sie weder das Muster FactoryMethodnoch das Muster AbstractFactoryname__. Ich sehe dies auch nicht als einen guten Kandidaten für das Muster factoryname__.

Nein, was Sie tun, ist nicht Strategypattern. Es sollte so geändert werden.

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

Und Ihr Kunde sollte so aussehen.

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

Ihr Code verwendet grundsätzlich das Werksmuster. Dies ist eine gute Möglichkeit, mehr als eine Zahlungsmethode zu handhaben

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

0
Ken Tucker