Pregunta

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?

¿Fue útil?

Solución

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.

Licenciado bajo: CC-BY-SA con atribución
scroll top