Отвратительный взлом (фабричный метод в базовом классе, использующий отражение) - PullRequest
3 голосов
/ 10 ноября 2009

Это грязная вещь, и я чувствую себя грязной из-за этого:

public abstract class InterestRate {

    // irrelevant details

    public static T ImpliedRate<T>(
        double factor,
        double time,
        DayCounter dayCounter
    ) where T : NonCompoundedInterestRate {
        MethodInfo methodInfo = typeof(T).GetMethod(
            "ImpliedRate",
            BindingFlags.Static);
        return (T)methodInfo.Invoke(
            null,
            new object[] { factor, time, dayCounter }
        );
    }

    public static T ImpliedRate<T>(
        double factor,
        double time,
        DayCounter dayCounter,
        Frequency frequency
    ) where T : CompoundedInterestRate {
        MethodInfo methodInfo = typeof(T).GetMethod(
            "ImpliedRate",
            BindingFlags.Static);
        return (T)methodInfo.Invoke(
            null,
            new object[] { factor, time, dayCounter, frequency }
        );
}

Здесь у меня есть классы NonCompoundedInterestRate (аннотация) и CompoundedInterestRate, происходящие из абстрактного класса InterestRate. У меня есть несколько конкретных реализаций NonCompoundedInterestRate, которые имеют статические методы с именем ImpliedRate с соответствующей сигнатурой для работы вышеупомянутого отражения.

Использование отражения для вызова статического метода, который даже не гарантированно присутствует в производном классе, просто пахнет. Есть ли лучший способ справиться с этим?

Ответы [ 6 ]

2 голосов
/ 14 февраля 2011

Ты должен чувствовать себя грязным. Вот немного мыла:

public static class InterestRateFactories
{
    static InterestRateFactories()
    {
        _factories = new List<IInterestRateFactory>();
        //  register default factories, although you can also register them elsewhere, like in your ioc setup
    }
    private static readonly List<IInterestRateFactory> _factories;
    public static void RegisterFactory(IInterestRateFactory factory)
    {
        _factories.Add(factory);
    }
    public static T ImpliedRate<T>(double factor, double time, DayCounter dayCounter)
        where T : NonCompoundedInterestRate
    {
        var factory = _factories.FirstOrDefault(x => x.CanCreate(typeof(T), false));
        if (factory == null)
        {
            throw new NotSupportedException("Cannot create a non-compounded implied interest rate of type " + typeof(T).Name);
        }
        return (T)factory.Create(factor, time, dayCounter);
    }
    public static T ImpliedRate<T>(double factor, double time, DayCounter dayCounter, Frequency frequency)
        where T : CompoundedInterestRate
    {
        var factory = _factories.FirstOrDefault(x => x.CanCreate(typeof(T), false));
        if (factory == null)
        {
            throw new NotSupportedException("Cannot create a compounded implied interest rate of type " + typeof(T).Name);
        }
        return (T)factory.Create(factor, time, dayCounter, frequency);
    }
}

public interface IInterestRateFactory
{
    bool CanCreate(Type nonCompoundedInterestRateType, bool compounded);
    NonCompoundedInterestRate Create(double factor, double time, DayCounter dayCounter);
    CompoundInterestRate Create(double factor, double time, DayCounter dayCounter, Frequency frequency);
}
1 голос
/ 10 ноября 2009

У вас всегда будут конфликты, пытаясь смешать статические (фабричные) методы с наследованием. Трудно получить полиморфное поведение, которое вы ищете. У меня была похожая проблема, и в настоящее время я использую рефлексию. Другой вариант, как уже упоминалось, состоит в том, чтобы не использовать статические методы, если это не требуется. Тогда вы сможете использовать шаблонный метод или любую другую стратегию, которая будет хорошо работать с наследованием.

1 голос
/ 10 ноября 2009

По моему опыту, вы можете создать экземпляр конструктора без параметров общего типа.

То, чего вы пытаетесь достичь, можно сделать только с помощью отражения.

1 голос
/ 10 ноября 2009

Вместо статических методов вы можете использовать обычные методы и что-то вроде модифицированного шаблона Clone / Prototype. Например:

public static class InstanceMap
{
    private static readonly Dictionary<Type,object> instances = 
        new Dictionary<Type,object>();

    public static void AddInstance(object instance)
    {
        instances[instance.GetType()] = instance;
    }

    public static T GetInstance<T>() { return (T) instances[typeof(T)]; }  
}

public interface INonCompoundedInterestRate
{
    INonCompoundedInterestRate ImpliedRate(double factor,
        double time,
        DayCounter dayCounter);
}

public class MyNonCompoundedInterestRate: INonCompoundedInterestRate
{
    public INonCompoundedInterestRate ImpliedRate(double factor,
        double time,
        DayCounter dayCounter) { /* do smth*/ }

    static MyNonCompoundedInterestRate()
    {
        InstanceMap.AddInstance(new MyNonCompoundedInterestRate());
    } 
} 

public abstract class InterestRate {
    public static T ImpliedRate<T>(
        double factor,
        double time,
        DayCounter dayCounter
    ) where T : INonCompoundedInterestRate 
    {
        return InstanceMap.GetInstance<T>().
            ImpliedRate(factor, time, dayCounter);
    }
    // ...
}
1 голос
/ 10 ноября 2009

Похоже, что вызывающая сторона может так же легко вызвать фабричный метод в производном классе, как и вызвать этот метод, передав производный тип как T.

Немного более явный контракт заключается в том, чтобы добавить новое ограничение () к T, вызвать ctor по умолчанию, а затем вызвать абстрактный метод Init, определенный для базового класса.

Заводской шаблон имеет преимущество в тестируемости, но не так, как вы использовали его здесь. Третья альтернатива заключается в том, чтобы вызывающая сторона передавала экземпляр класса фабрики для использования (метод ImpliedRate был бы в интерфейсе фабрики). Это было бы удобно для модульного тестирования, но, возможно, обременительно для потребителя API.

0 голосов
/ 10 ноября 2009

Есть какая-то причина не просто определять их на неуниверсальном интерфейсе, который объявлен на общих базах, а затем выполнять приведение к T? Я не вижу каких-либо обобщенных аргументов ...

...