Building a plugin architecture with Managed Extensibility Framework - Part 2

In this post, we briefly explore the methods through which it becomes feasible to seamlessly integrate extensibility features into a software system with design patterns.

Incorporating extensibility features into a software system itself may not be inherently challenging. What proves more intricate is the discernment, assessment, or anticipation of when it is opportune and beneficial to introduce such extensibility points. Frequently, this need is implicitly conveyed by clients, even without their explicit awareness of it.

What methods can we employ to seamlessly integrate extensibility points into a software system ?

In this scenario, leveraging an object-oriented programming language such as C# or Java (which is not overly restrictive), we conventionally establish an interface from which multiple classes can inherit. Subsequently, it falls upon the client to invoke the appropriate class during runtime.

Information

In the realm of OOP, the objective is to encapsulate elements that exhibit variability.

We contemplate the straightforward scenario outlined previously, wherein we must define multiple payment methods for a virtual ecommerce website.

Thus, the initial step involves defining a contract, typically an interface, to showcase the available methods.

1public interface IPaymentMethod
2{
3    string Name { get; }
4	
5    bool TryProcessPayment(double amount);
6}
Information

The interface presented here is minimalist. In a real-world scenario, there would likely be additional methods and more intricate data structures (here, we are utilizing a simple double to denote the amount).

Once this contract is established, deriving the relevant classes to accommodate the specific characteristics of each payment method becomes straightforward.

 1public class PayPalPaymentMethod : IPaymentMethod
 2{
 3    public string Name => "PAYPAL";
 4	
 5    public bool TryProcessPayment(double amount)
 6    {
 7        // Process payment with PayPal
 8    	return true;
 9    }
10}
 1public class VisaPaymentMethod : IPaymentMethod
 2{
 3    public string Name => "VISA";
 4	
 5    public bool TryProcessPayment(double amount)
 6    {
 7        // Process payment with Visa
 8    	return true;
 9    }
10}

And so forth for each payment method...

It is then incumbent upon the client to invoke the appropriate class, which can be achieved through various methods. We propose the following approach here.

 1public class PaymentMethodFactory
 2{
 3    private static PaymentMethodFactory _instance;
 4
 5    private PaymentMethodFactory() { }
 6	
 7    public static PaymentMethodFactory Instance
 8    {
 9        get
10        {
11		    if (_instance == null) _instance = new PaymentMethodFactory();
12            return _instance;
13        }
14    }
15	
16    public IPaymentMethod Get(string name)
17    {
18        IPaymentMethod method = name switch
19        {
20            "PAYPAL" => new PayPalPaymentMethod(),
21            "VISA" => new VisaPaymentMethod(),
22            _ => throw new NotImplementedException()
23        };
24		
25        return method;
26    }	
27}
Information

Please note that this class is implemented as a singleton. It is unnecessary to instantiate it each time a payment method is required.

The main program can then assemble these components together.

 1internal class Program
 2{
 3    static void Main(string[] args)
 4    {        
 5        Console.WriteLine("Enter method:");
 6        var s = Console.ReadLine();
 7        var method = PaymentMethodFactory.Instance.Get(s);
 8
 9        // Process payment
10        var res = method.TryProcessPayment(200.0);
11    }
12}
Information

This approach to processing is founded on the Strategy design pattern.

What is the process for integrating a new payment method using this approach ?

Suppose we need to introduce a new payment method. How should we proceed in this scenario ?

Indeed, it's a straightforward process: we simply create a new class that inherits from IPaymentMethod and adjust the PaymentMethodFactory class accordingly.

 1public class MasterCardPaymentMethod : IPaymentMethod
 2{
 3    public string Name => "MASTERCARD";
 4	
 5    public bool TryProcessPayment(double amount)
 6    {
 7        // Process payment with MasterCard
 8    	return true;
 9    }
10}
 1public class PaymentMethodFactory
 2{
 3    // ...
 4	
 5    public IPaymentMethod Get(string name)
 6    {
 7        IPaymentMethod method = name switch
 8        {
 9            "PAYPAL" => new PayPalPaymentMethod(),
10            "VISA" => new VisaPaymentMethod(),
11            "MASTERCARD" => new MasterCardPaymentMethod(),
12            _ => throw new NotImplementedException()
13        };
14		
15        return method;
16    }	
17}

What are the limitations of this architecture ?

  • There is nothing flawed in the preceding approach. It is a highly effective and widely utilized method for extending software. Moreover, it would indeed be the preferable approach if we only needed to add a few payment methods.

  • This approach is also ideally suited if we do not require third parties to independently develop the extensions. On the contrary, indeed, we would need to expose our contracts to make them publicly available. In such a scenario, an architecture based on plugins would be more advantageous, and we will delve into this in the next article.

  • Nevertheless, as illustrated by the PaymentMethodFactory, there exists a requirement to manually register each method whenever a new class is introduced. This process could potentially become cumbersome and prone to errors, particularly in scenarios involving numerous modules.

How can we incorporate these considerations effectively ? Stay tuned for the answer !

Building a plugin architecture with Managed Extensibility Framework - Part 3