Создание разных типов на основе некоторых критериев - PullRequest
3 голосов
/ 11 февраля 2012

У меня есть некоторый код в приложении, который мне сейчас не очень нравится. Я создал несколько классов примерно так:

class Base
{
   // base properties ...
}

class DerivedA : Base
{
}

class DerivedB : Base
{
}

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

class BaseCreator
{
    Base Create(string name)
    {
        if (name == "DerivedA" )
            return CreateDerivedA();
        else if(name == "DerivedB")
            return CreateDerivedB();
    }
}

Каким образом я могу реорганизовать этот код, чтобы он был более понятным и облегчал добавление новых типов в будущем? Я использую внедрение зависимостей (Ninject) в своем приложении, если это имеет какое-либо значение.

Ответы [ 6 ]

1 голос
/ 11 февраля 2012

Общий случай можно решить с помощью небольшого количества композиции и использования шаблона спецификации:

public class Base
{
    public abstract bool IsSatisfiedBy(string name);

    // base properties ...
}

public class DerivedA : Base
{
    public override bool IsSatisfiedBy(string name)
    {
        return name == "DerivedA";
    }
}

public class DerivedB : Base
{
    public override bool IsSatisfiedBy(string name)
    {
        return name == "DerivedB";
    }
}

public class BaseCreator
{
    private readonly IEnumerable<Base> candidates;

    public BaseCreator(IEnumerable<Base> candidates)
    {
        this.candidates = candidates;
    }

    public Base Create(string name)
    {
        return this.candidates.First(c => c.IsSatisfiedBy(name));
    }
}
1 голос
/ 11 февраля 2012

Замечу, что ваша структура if/else или switch неплохая вещь. Плохо, когда у вас есть одно и то же if/else или switch, выраженное несколько раз.

Когда вы хорошо отсоединили свой код и программируете интерфейс или абстрактную базу, а не конкретную, знайте, что где-то в вашем приложении, что-то знает, как создать конкретный конкретный экземпляр, который вам нужен. Это может быть код, это может быть конфигурация, это может быть какой-то контейнер и т. Д. Но это что-то должно существовать. Идея состоит в том, чтобы что-то существовало один раз.

Ваш подход хорош, пока это единственный метод, где он существует. Причина существования этого класса заключается в том, что он создает конкретные экземпляры, которые удовлетворяют некоторому интерфейсу. Причина его изменения в том, что была добавлена ​​(или удалена) какая-то другая конкретная реализация.

1 голос
/ 11 февраля 2012

Я думаю, вы должны использовать абстрактный шаблон фабрики, который решает эту проблему. Он предоставляет интерфейс для создания семейств связанных или зависимых объектов без указания их конкретных классов. http://www.dofactory.com/Patterns/PatternAbstract.aspx

или просто заводская модель http://www.dotnetperls.com/factory

1 голос
/ 11 февраля 2012

Если вы действительно должны использовать строки, вы можете использовать отражение:

object GetInstance(string typeName)
{
    Type.GetType(typeName).GetConstructor(Type.EmptyTypes).Invoke(new object[0]);
}

Вы также можете использовать словарь:

IDictionary<string, Func<object>> TypeMap = new Dictionary<string, Func<object>>()
{
    { "TypeA", () => new TypeA() },
    { "TypeB", () => new TypeB() },
    { "TypeC", () => new TypeC() },
};

object GetInstance(string typeName)
{
    return TypeMap[typeName]();
}

Для других, заходящих на эту страницу, рассмотрите возможность использования обобщений, если у вас нет для использования строк:

T CreateInstance<T>()
    where T : new()
{
    return new T();
}
1 голос
/ 11 февраля 2012

Деревья наследования всегда трудно поддерживать, когда они растут. Если вы заранее знаете, что дерево будет большим - серьезно подумайте об использовании композиции вместо наследования. Особенно, если вы уже используете инфраструктуру DI, лучше всего использовать интерфейсы.

0 голосов
/ 12 февраля 2012

Нет общего ответа на этот вопрос.Abstract Factory может быть верным, но все зависит от того, в чем разница между этими реализациями и тем, как вы их используете.

Вполне возможно, что вы должны использовать Template, Strategy, State или любой другой подобный шаблон.Изучите их, и определенно Абстрактную Фабрику, и определитесь с шаблоном, который соответствует вашему конкретному сценарию.

...