1

I need help with an abstract factory pattern design.I have and calculation engine which calculates for different channels like captive, captiveTemplate or Headquarter and in the future different channel can be added. Attached is the drawing on how I am setting up the pattern:

enter image description here

The problem is some items are calculated only for specific channels and some items calculated for all type of channels.For example, CaptiveManagementFee only calculates in CaptiveCalculatorFactory and InterestMargin calculated only in HeadQuarterFactory but ServiceMarkup should be calculated in all type of calculatorFactories.How should I design my classes to solve this problem?

  • 3
    The point of having a factory is usually to decouple client code from having to distinguish between different concrete types. But in your diagram you have a separate factory type for every calculator type. Are you sure you are actually implementing a factory behaviour? – Kilian Foth May 11 '17 at 9:31
  • 2
    It would help me to understand if you could provide a code example demonstrating how you intend to use these factories, and why you need so many. – John Wu May 11 '17 at 19:42
0

Maybe something along the lines of:

public class CalculatorItem
{
}

public class CaptiveManagementFee : CalculatorItem
{
}

public class InterestMargin : CalculatorItem
{
}

public class ServiceMarkup : CalculatorItem
{
}

public interface ICalculator
{
    void Calculate(CalculatorItem item);
}

public interface ICalculator<TCalculatorItem> : ICalculator
    where TCalculatorItem : CalculatorItem
{
    void Calculate(TCalculatorItem item);
}

public class CalculatorBase : ICalculator
{
    protected void Witness(string method, object item)
    {
        Console.WriteLine($"{GetType()}.{method}({item.GetType()})");
    }

    #region ICalculator
    public void Calculate(CalculatorItem item)
    {
        if (item == null)
        {
            throw new ArgumentNullException("undecidable item type for null", nameof(item));
        }
        var itemType = item.GetType();
        var specialized = GetType().GetMethod(nameof(Calculate), new[] { itemType });
        if (specialized == null)
        {
            throw new ArgumentException($"unsupported item type: {itemType}", nameof(item));
        }
        specialized.Invoke(this, new object[] { item });
    }
    #endregion
}

public class CaptiveCalculator : CalculatorBase,
    ICalculator<CaptiveManagementFee>,
    ICalculator<ServiceMarkup>
{
    protected virtual void CalculateFee(CaptiveManagementFee item)
    {
        Witness(nameof(CalculateFee), item);
    }

    protected virtual void CalculateMarkup(ServiceMarkup item)
    {
        Witness(nameof(CalculateMarkup), item);
    }

    #region ICalculator<CaptiveManagementFee>
    public void Calculate(CaptiveManagementFee item)
    {
        CalculateFee(item);
    }
    #endregion

    #region ICalculator<ServiceMarkup>
    public void Calculate(ServiceMarkup item)
    {
        CalculateMarkup(item);
    }
    #endregion
}

public class HeadquarterCalculator : CalculatorBase,
    ICalculator<InterestMargin>,
    ICalculator<ServiceMarkup>
{
    protected virtual void CalculateMargin(InterestMargin item)
    {
        Witness(nameof(CalculateMargin), item);
    }

    protected virtual void CalculateMarkup(ServiceMarkup item)
    {
        Witness(nameof(CalculateMarkup), item);
    }

    #region ICalculator<InterestMargin>
    public void Calculate(InterestMargin item)
    {
        CalculateMargin(item);
    }
    #endregion

    #region ICalculator<ServiceMarkup>
    public void Calculate(ServiceMarkup item)
    {
        CalculateMarkup(item);
    }
    #endregion
}

public class CalculatorFactory
{
    private static Type[] CalculatorTypes =
        new[] { typeof(CaptiveCalculator), typeof(HeadquarterCalculator) };

    public ICalculator<TCalculatorItem> CreateCalculator<TCalculatorItem>()
        where TCalculatorItem : CalculatorItem
    {
        var found =
            CalculatorTypes.
            Single // (throws if we don't find exactly *one* matching type)
            (
                type =>
                    typeof(ICalculator<>).MakeGenericType(typeof(TCalculatorItem)).IsAssignableFrom(type)
            );
        return (ICalculator<TCalculatorItem>)Activator.CreateInstance(found);
    }
}

class Program
{
    static void Main(string[] args)
    {
        var calcFactory = new CalculatorFactory();

        var headQuarterCalc = calcFactory.CreateCalculator<InterestMargin>();
        headQuarterCalc.Calculate(new InterestMargin());
        //headQuarterCalc.Calculate(new CaptiveManagementFee()); // throws an exception (unsupported item type)
        headQuarterCalc.Calculate(new ServiceMarkup()); // late-bound, dispatched via ICalculator.Calculate(CalculatorItem)

        var captiveCalc = calcFactory.CreateCalculator<CaptiveManagementFee>();
        captiveCalc.Calculate(new CaptiveManagementFee());
        //captiveCalc.Calculate(new InterestMargin()); // throws an exception (unsupported item type)
        captiveCalc.Calculate(new ServiceMarkup()); // late-bound, dispatched via ICalculator.Calculate(CalculatorItem)

        //var ambiguousCalc = calcFactory.CreateCalculator<ServiceMarkup>(); // throws an exception

        Console.ReadKey();
    }
}

That's a bit too much of boilerplate to my taste (YMMV), but I can't really think of anything simpler considering your requirements, taken as-is.

'Hope this helps.

  • Thank you so much i will use builder pattern instead of abstract factory beacause i have different calulation items per calculation chanels. But thank you a lot for your recomendations and solutions.I can use some part of your solution. – Fikret Asma May 15 '17 at 5:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.