c # generics класс фабричный вопрос - PullRequest
7 голосов
/ 22 июля 2011

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

Мои основные камни преткновения:

(1) для универсального метода длябыть в состоянии создавать экземпляры классов Мне нужно ограничение new (), которое означает, что у меня должен быть открытый конструктор для классов, что означает, что они могут быть созданы публично.

(2) Альтернативой будет для классовсами по себе иметь статический метод, который возвращает экземпляр класса.Но я не могу назвать это из своего общего класса, потому что мне нужно иметь дело с интерфейсами / типами, а у вас не может быть статики через интерфейсы.

Вот то, что у меня сейчас есть,но он использует ограничение new (), которое позволяет публично создавать мои классы:

internal static class MyClassFactory
{
    internal static T Create<T>(string args) where T : IMyType, new()
    {
        IMyType newThing = new T();
        newThing.Initialise(string args);
        return (T)newThing;
    }
}

public interface IMyType
{
    void Initialise(string args);
}

public class ThingA: IMyType
{
public void Initialise(string args)
{
        // do something with args
}
}

Любая помощь с благодарностью:)

Ответы [ 6 ]

3 голосов
/ 22 июля 2011

Похоже, вы пытаетесь свернуть свой собственный сервисный локатор.

Рассматривали ли вы подход внедрения зависимостей (DI)? * * * * * * * * * * * * Есть причины, по которым вы можете избежать поиска службы.

Я настоятельно рекомендую вам взглянуть на некоторые популярные контейнеры IOC, которые могут выполнять ту функциональность, которую вы пытаетесь создать. Оглядываясь назад, я очень рад, что выбрал DI вместо сервисного локатора.

Ninject

Autofac

Единство

2 голосов
/ 22 июля 2011

Есть кое-что, что вы можете сделать, но это действительно уродливо ...

public class ThingA: IMyType
{
    [Obsolete("This constructor must not be called directly", true)]
    public ThingA()
    {
    }

    public void Initialise(string args)
    {
            // do something with args
    }

}

Это вызовет ошибку компиляции, если вы попытаетесь вызвать конструктор явно, но не помешает вызвать его вуниверсальный метод с ограничением new().

1 голос
/ 22 июля 2011

Рассмотрим подход отражения.

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

Отражение влияет на производительность, но вызов этого конструктора не является большой операцией отражения.

Проверьте эту статью MSDN, чтобы узнать больше о том, как вызвать приватный конструктор с помощью отражения:

Но его можно обобщить следующим фрагментом кода:

typeof(T).GetConstructor(BindingFlags.Instance | BindingFlags.Private, Type.EmptyTypes, Type.DefaultBinder, null).Invoke(null);

UPDATE

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

Иногда хорошие рекомендации разработчиков лучше, чем любое ограничение кода. Я говорю это потому, что в вашем случае я бы реализовал этот метод фабрики с универсальным параметром T и тем же общим ограничением, и я, если в моих документах сказано: «если вам нужен экземпляр типа T, вам нужно использовать эту фабрику» и некоторые решают не следовать этому правилу, это не будет моей обязанностью, и на любую проблему с этим ответят: «В руководстве сказано, что вам нужно использовать завод».

Хорошие привычки лучше, чем дополнительный защитный код, для принятия человеческих решений.

1 голос
/ 22 июля 2011

Это может работать для вас - использовать отражение вместо ограничения new ().Также обратите внимание, что конструктор является закрытым, и вам нужно добавить метод к производному классу, который возвращает экземпляр самого себя (статический):

internal static class MyClassFactory
{
    internal static T Create<T>(string args) where T : IMyType
    {
        IMyType newThing = 
           (T)typeof(T).GetMethod("GetInstance").Invoke(default(object), null); 

        newThing.Initialise(args);

        return (T)newThing; 
    }
}

public interface IMyType 
{
    void Initialise(string args); 
}  

public class ThingA: IMyType 
{
    private ThingA() { }

    public static IMyType GetInstance()
    {
        return new ThingA();  // control creation logic here
    }

    public void Initialise(string args) 
    {   
        // do something with args 
    } 
} 

EDIT

Просто чтобы уточнить это, какбыло указано, что вы можете получить доступ к приватному конструктору через отражение, например так:

internal class MyClassFactory 
{
    internal static T Create<T>(string args) where T : IMyType
    {
        IMyType newThing = (T)typeof(T).GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, Type.DefaultBinder, Type.EmptyTypes, null).Invoke(null); 

        newThing.Initialise(args);

        return (T)newThing; 
    }
}

public interface IMyType 
{
    void Initialise(string args); 
}

public class ThingA : IMyType
{
    private ThingA() { }

    public void Initialise(string args)
    {
        Console.WriteLine(args);
    }
} 
1 голос
/ 22 июля 2011

Что вы можете сделать, это заключить (скрытое) соглашение о том, что все объекты, реализующие ваш интерфейс, также имеют определенный конструктор, который может вызывать фабрика. Вот как работает ISerializable. Недостатком является то, что наличие конструктора не проверяется компилятором. На вашей фабрике найдите конструктор через отражение и вызовите с правильными аргументами. Конструктор может быть защищен.

// Get constr with string arg
var constr = theType.GetConstructor(new[]{typeof(String)});

T result = (T)constr.Invoke(new[]{"argString"});
0 голосов
/ 25 июля 2011

Определение интерфейса IFactoryс помощью метода «Создать», а затем для каждого класса TT, который вы хотите создать, определите статический объект, который реализует IFactory .Затем вы можете либо передать объект IFactory везде, где вы хотите создать TT, либо создать статический класс FactoryHolder , с помощью которого вы затем зарегистрируете соответствующие фабрики.Обратите внимание, что универсальные классы имеют отдельный набор статических переменных для каждой комбинации универсальных типов, и это может обеспечить очень удобный и безопасный для типов способ хранения статического отображения между типами и экземплярами фабрики.

Одно небольшое предостережениеявляется то, что нужно зарегистрировать фабрику для каждого конкретного типа, который вы хотите найти.Можно зарегистрировать фабрику производного типа как производителя базового типа (так что, например, FactoryHolder .GetFactory () вернет IFactory), но если явно не зарегистрировать фабрику заданного точного типа T, FactoryHolder окажется пустым.Например, либо IFactoryили IFactoryможет использоваться как IFactory , но если один из них не зарегистрирован как IFactory , то FactoryHolder .GetFactory () не будет возвращен ни один из них.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...