1
votes

I'm looking for solution to create factory that will be Open/Closed compliant. We can achieve it really simple by reflection (instantiate all classes implementing specific interface, that exists in ex. current assembly, store them in some dictionary with key based on static property and return specific instance based on parameter passed to CreateInstance method).

I wonder if this is possible using Ninject. I bought book "Mastering Ninject for Dependency Injection" there is a chapter "Meeting real-world requirements" with Telcom Switch example. Unfortunately, the IStatusCollectorFactory and all factories that implement this interface violate Open/Close principle- you have to alter interface if you want to add support for new class.

Any help?:)

/// Interface for classes that define factory able to create currency defaltion instances. 
public interface ICurrencyDeflationFactory
{
    ICurrencyDeflation CreateInstance(string currencyCode);
}


/// <summary>
/// Interface for classes that define deflation table in specific currency.
/// </summary>
public interface ICurrencyDeflation
{
    /// <summary>
    /// Current currency code as defined in ISO 4217
    /// </summary>
    string CurrencyCode { get; }

    /// <summary>
    /// Deflation table used during conversion.
    /// </summary>
    string[,] GetDeflationTable { get; }
}
1
What is the expected interface for such factory? And how should it know which object to return? Can you provide some details? Also, can you explain why do you need it? Specifically, who is its client? - Yacoub Massad
I edited my post to include information. - Tom Ash
Although not about NInject, you might find this article helpful - Yacoub Massad
This can be done using custom instance providers, have a look at the docs - BatteryBackupUnit

1 Answers

1
votes

Thank you for your feedback. I managed to achieve what I wanted (in my opinion:)) Here is a potential solution: Kernel registration:

kernel.Bind<ICurrencyDeflationFactory>().To<CurrencyDeflationFactory>(); 
kernel.Bind(x => x.FromAssemblyContaining<ICurrencyDeflation>()
  .SelectAllClasses()
  .InheritedFrom<ICurrencyDeflation>()
  .BindAllInterfaces());

The factory interface:

/// <summary>
/// Interface for classes that define factory able to create currency defaltion instances.
/// </summary>
public interface ICurrencyDeflationFactory
{
    List<ICurrencyDeflation> CurrencyList { get; }
    ICurrencyDeflation CreateInstance(string currencyCode);
}

Deflation Interface:

    /// <summary>
/// Interface for classes that define deflation table in specific currency.
/// </summary>
public interface ICurrencyDeflation
{
    /// <summary>
    /// Current currency code as stands in ISO 4217
    /// </summary>
    string CurrencyCode { get; }

    /// <summary>
    /// Deflation table used during conversion.
    /// </summary>
    string[,] GetDeflationTable { get; }
}

Factory Concrete:

public class CurrencyDeflationFactory : ICurrencyDeflationFactory
{ 
    List<ICurrencyDeflation> deflationCollection;
    public CurrencyDeflationFactory(List<ICurrencyDeflation> definedDeflations)
    {
        this.deflationCollection = definedDeflations;
    }
    public List<ICurrencyDeflation> CurrencyList
    {
        get
        {
            return deflationCollection;
        }
    }

    public ICurrencyDeflation CreateInstance(string currencyCode)
    {
        return deflationCollection.Find(x => x.CurrencyCode.Equals(currencyCode));
    }
}

In order to get USD deflation:

 var currencyDeflation = kernel.Get<ICurrencyDeflationFactory>().CreateInstance("USD");

Because of that I don't have to:
1) alter main program to register new class to kernel
2) new deflation is just a new class that implements IDeflationCurrency
3) every class that implements IDeflationCurrency will be available on runtime, no need to change existing code (closed for modification)
4) I can add new deflation if needed without touching existing code (open for extension)