Фабричный шаблон в C #: Как гарантировать, что экземпляр объекта может быть создан только фабричным классом? - PullRequest
82 голосов
/ 05 февраля 2009

Недавно я думал о защите своего кода. Мне любопытно, как можно сделать так, чтобы объект никогда не создавался напрямую, а только с помощью некоторого метода фабричного класса. Допустим, у меня есть некоторый класс «бизнес-объекта», и я хочу убедиться, что у любого экземпляра этого класса будет допустимое внутреннее состояние. Чтобы добиться этого, мне нужно выполнить некоторую проверку перед созданием объекта, возможно, в его конструкторе. Это все нормально, пока я не решу, что хочу сделать эту проверку частью бизнес-логики. Итак, как я могу организовать создание бизнес-объекта только с помощью некоторого метода в моем классе бизнес-логики, но никогда напрямую? Первое естественное желание использовать старое доброе ключевое слово «друг» в C ++ не дотягивает до C #. Поэтому нам нужны другие варианты ...

Давайте попробуем пример:

public MyBusinessObjectClass
{
    public string MyProperty { get; private set; }

    public MyBusinessObjectClass (string myProperty)
    {
        MyProperty = myProperty;
    }
}

public MyBusinessLogicClass
{
    public MyBusinessObjectClass CreateBusinessObject (string myProperty)
    {
        // Perform some check on myProperty

        if (true /* check is okay */)
            return new MyBusinessObjectClass (myProperty);

        return null;
    }
}

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

Итак, что об этом думает сообщество?

Ответы [ 17 ]

1 голос
/ 22 апреля 2016

Было упомянуто несколько подходов с различными компромиссами.

  • Вложение класса фабрики в класс, построенный частным образом, позволяет фабрике создать 1 класс. В этот момент вам лучше использовать метод Create и личный ctor.
  • Использование наследования и защищенного ctor имеет ту же проблему.

Я бы хотел предложить фабрику как частичный класс, который содержит частные вложенные классы с открытыми конструкторами. Вы на 100% скрываете объект, который строит ваша фабрика, и выставляете только то, что выбираете, через один или несколько интерфейсов.

Случай использования, о котором я слышал, может быть, когда вы хотите отслеживать 100% экземпляров на заводе. Этот дизайн гарантирует, что никто, кроме фабрики, не имеет доступа к созданию экземпляров «химикатов», определенных в «фабрике», и устраняет необходимость отдельной сборки для достижения этого.

== ChemicalFactory.cs ==
partial class ChemicalFactory {
    private  ChemicalFactory() {}

    public interface IChemical {
        int AtomicNumber { get; }
    }

    public static IChemical CreateOxygen() {
        return new Oxygen();
    }
}


== Oxygen.cs ==
partial class ChemicalFactory {
    private class Oxygen : IChemical {
        public Oxygen() {
            AtomicNumber = 8;
        }
        public int AtomicNumber { get; }
    }
}



== Program.cs ==
class Program {
    static void Main(string[] args) {
        var ox = ChemicalFactory.CreateOxygen();
        Console.WriteLine(ox.AtomicNumber);
    }
}
1 голос
/ 04 мая 2014

Это решение основано на munificents идее использования токена в конструкторе. Готово в этом ответе Убедитесь, что объект создан только фабрикой (C #)

  public class BusinessObject
    {
        public BusinessObject(object instantiator)
        {
            if (instantiator.GetType() != typeof(Factory))
                throw new ArgumentException("Instantiator class must be Factory");
        }

    }

    public class Factory
    {
        public BusinessObject CreateBusinessObject()
        {
            return new BusinessObject(this);
        }
    }
1 голос
/ 05 февраля 2009

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

public class Person
{
  internal Person()
  {
  }
}

public class PersonFactory
{
  public Person Create()
  {
    return new Person();
  }  
}

Однако я должен поставить под сомнение ваш подход: -)

Я думаю, что если вы хотите, чтобы ваш класс Person был действительным при создании, вы должны поместить код в конструктор.

public class Person
{
  public Person(string firstName, string lastName)
  {
    FirstName = firstName;
    LastName = lastName;
    Validate();
  }
}
0 голосов
/ 23 октября 2018

Хотелось бы услышать некоторые мысли об этом решении. Единственный, кто может создать 'MyClassPrivilegeKey' - это фабрика. и «MyClass» требует это в конструкторе. Таким образом избегая размышлений о частных подрядчиках / «регистрация» на заводе.

public static class Runnable
{
    public static void Run()
    {
        MyClass myClass = MyClassPrivilegeKey.MyClassFactory.GetInstance();
    }
}

public abstract class MyClass
{
    public MyClass(MyClassPrivilegeKey key) { }
}

public class MyClassA : MyClass
{
    public MyClassA(MyClassPrivilegeKey key) : base(key) { }
}

public class MyClassB : MyClass
{
    public MyClassB(MyClassPrivilegeKey key) : base(key) { }
}


public class MyClassPrivilegeKey
{
    private MyClassPrivilegeKey()
    {
    }

    public static class MyClassFactory
    {
        private static MyClassPrivilegeKey key = new MyClassPrivilegeKey();

        public static MyClass GetInstance()
        {
            if (/* some things == */true)
            {
                return new MyClassA(key);
            }
            else
            {
                return new MyClassB(key);
            }
        }
    }
}
0 голосов
/ 09 июля 2014

Вот еще одно решение в духе «просто потому, что ты не можешь означать, что должен» ...

Он отвечает требованиям сохранения приватности конструктора бизнес-объекта и помещения фабричной логики в другой класс. После этого он становится немного отрывочным.

В фабричном классе есть статический метод для создания бизнес-объектов. Он наследуется от класса бизнес-объектов для доступа к статическому защищенному методу конструирования, который вызывает закрытый конструктор.

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

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

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

using System;

public class MyBusinessObjectClass
{
    public string MyProperty { get; private set; }

    private MyBusinessObjectClass(string myProperty)
    {
        MyProperty = myProperty;
    }

    // Need accesible default constructor, or else MyBusinessObjectFactory declaration will generate:
    // error CS0122: 'MyBusinessObjectClass.MyBusinessObjectClass(string)' is inaccessible due to its protection level
    protected MyBusinessObjectClass()
    {
    }

    protected static MyBusinessObjectClass Construct(string myProperty)
    {
        return new MyBusinessObjectClass(myProperty);
    }
}

public abstract class MyBusinessObjectFactory : MyBusinessObjectClass
{
    public static MyBusinessObjectClass CreateBusinessObject(string myProperty)
    {
        // Perform some check on myProperty

        if (true /* check is okay */)
            return Construct(myProperty);

        return null;
    }

    private MyBusinessObjectFactory()
    {
    }
}
0 голосов
/ 11 декабря 2012

Я не думаю, что есть решение, которое не хуже, чем проблема, все, что он выше, требует общедоступной статической фабрики, которая ИМХО является более серьезной проблемой и не остановит людей, просто вызывающих фабрику для использования вашего объекта - она ​​ничего не скрывает , Лучше всего предоставить интерфейс и / или сохранить конструктор как внутренний, если это возможно, это лучшая защита, поскольку сборка является доверенным кодом.

Один из вариантов - иметь статический конструктор, который регистрирует фабрику где-то с контейнером IOC.

0 голосов
/ 05 февраля 2009

Я не понимаю, почему вы хотите отделить «бизнес-логику» от «бизнес-объекта». Это звучит как искажение ориентации объекта, и вы, в конечном итоге, завяжетесь в узлы, приняв такой подход.

...