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

Это грязный поступок, и я чувствую себя грязным за это:

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 с соответствующей сигнатурой для работы вышеуказанного отражения.

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


person jason    schedule 10.11.2009    source источник
comment
Что (если есть) лучший способ справиться с этой ситуацией?   -  person jason    schedule 10.11.2009
comment
Должны ли методы быть статичными?   -  person Rohan West    schedule 10.11.2009
comment
В чем причина использования здесь дженериков? Кажется, что иерархия классов, реализующая (один или несколько) интерфейсов, подойдет для этой задачи.   -  person rsp    schedule 10.11.2009


Ответы (6)


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

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);
}
person smartcaveman    schedule 14.02.2011

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

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

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

person alexdej    schedule 10.11.2009
comment
Ваше первое предложение на самом деле не вариант, поскольку у меня есть общие Blah<T>, а в методах экземпляра в Blah я вызываю InterestRate.ImpliedRate<T>. Второй мне нравится, и я его обдумал, но он тоже кажется хакерским. В этом решении я бы создал два экземпляра объекта, один из которых использовался для создания хранителя. Что, если бы создание объекта было дорогостоящим? И я согласен, третье предложение слишком обременительно для использования. - person jason; 10.11.2009
comment
Понятно. Чтобы прояснить мое второе предложение: я должен был создать абстрактные методы экземпляра void Initialize () для двух базовых классов, которые принимают те же аргументы, что и текущие фабричные методы. Это не были бы фабричные методы, поэтому вы не создавали бы одноразовые экземпляры. - person alexdej; 10.11.2009

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

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);
    }
    // ...
}
person Dmitry Osinovskiy    schedule 10.11.2009

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

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

person Tim Jarvis    schedule 10.11.2009

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

person Samuel Meacham    schedule 10.11.2009

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

person nitzmahone    schedule 10.11.2009
comment
Скажем, у меня есть общий класс Blah<T>, и в каком-то экземпляре метода в Blah я вызываю InterestRate.ImpliedRate<T>(factor, time, dayCounter). Здесь я бы использовал общий фабричный метод для базового класса InterestRate. - person jason; 10.11.2009