Убедитесь, что объект создан только фабрикой (C#)

Как сделать так, чтобы конкретный класс создавался только фабрикой, а не вызывать new напрямую?

РЕДАКТИРОВАТЬ: мне нужно, чтобы фабрика была отдельным классом (для целей внедрения зависимостей), поэтому я не могу сделать его статическим методом экземпляра класса, и поэтому я не могу сделать новый private.

8 ответов

Решение

Если по какой-то причине вам нужно, чтобы фабрика и созданный класс находились в отдельных сборках (что означает просто internal не будет работать), и вы можете убедиться, что ваша фабрика получит шанс запустить в первую очередь, вы можете сделать это:

// In factory assembly:

public class Factory
{
    public Factory()
    {
        token = new object();
        MyClass.StoreCreateToken(token);
    }

    public MyClass Create()
    {
        return new MyClass(token);
    }

    private object token;
}

// In other assembly:

public class MyClass
{
    public static void StoreCreateToken(object token)
    {
        if (token != null) throw new InvalidOperationException(
            "Only one factory can create MyClass.");

        this.token = token;
    }

    public MyClass(object token)
    {
        if (this.token != token) throw new InvalidOperationException(
            "Need an appropriate token to create MyClass.");
    }

    private static object token;
}

Да, это громоздко и неловко. Но могут быть странные ситуации, когда это действительно хорошее решение.

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

Сделайте его конструкторы частными и предоставьте фабричный метод как статический метод для самого класса.

В большинстве случаев вы можете просто сделать конструкторы внутренними, что позволит вам разделить фабрику на ее собственный класс - я обнаружил, что часто не стоит пытаться помешать моей собственной команде использовать new создавать экземпляры внутри сборки класса.

Сделайте конструктор внутренним и разместите фабрику в той же сборке.

public MyClass
{
    internal MyClass()
    {
    }
}

в той же сборке

public MyClassGenerator
{
    public static CreateMyClass()
    {
        return new MyClass();
    }
}

Если фабрика не может быть в одной сборке или этот метод не работает для вас, посмотрите ответ Дэна

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

Вы можете обратиться к ответу Эрика Липперта здесь (для аналогичной проблемы): зачем мне когда-либо нужно использовать вложенные классы C#

Он всегда будет создаваться путем вызова new где-нибудь, но если вы хотите, чтобы это происходило только в вашем классе фабрики, вы можете установить для всех конструкторов значение Internal (или Private) и использовать метод фабрики Public Static для того же класса.

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

Мне не нравится иметь фабрику на самом типе, особенно если это объект домена. Имейте это внутреннее, если у вас есть отдельный класс как фабрика (который я думаю, что вы должны). Используйте атрибут InternalVisible, если фабрика находится в другой сборке.

Другие вопросы по тегам