Недавно я подумал о защите части моего кода. Мне любопытно, как можно убедиться, что объект никогда не может быть создан напрямую, а только с помощью некоторого метода фабричного класса. Допустим, у меня есть класс «бизнес-объект», и я хочу убедиться, что любой экземпляр этого класса будет иметь допустимое внутреннее состояние. Для этого мне нужно будет выполнить некоторую проверку перед созданием объекта, возможно, в его конструкторе. Все в порядке, пока я не решу, что хочу, чтобы эта проверка была частью бизнес-логики. Итак, как я могу организовать создание бизнес-объекта только с помощью какого-либо метода в моем классе бизнес-логики, но никогда напрямую? Первое естественное желание использовать старое доброе ключевое слово «друг» 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 напрямую, не проверяя ввод. Я бы хотел вообще исключить такую техническую возможность.
Итак, что думает об этом сообщество?
источник
CreateBusinessObject
метода - проверить аргументы И для (вернуть новый действительный объект ИЛИ вернуть ошибку) в одном вызове метода, когда вызывающий не сразу узнает, допустимы ли аргументы для передачи в конструктор.Distance
экземпляра с отрицательным аргументом должно вызвать,ArgumentOutOfRangeException
например, вы ничего не получите от создания,DistanceFactory
который выполняет ту же проверку. Я все еще не понимаю, что вы здесь выиграете.Ответы:
Похоже, вы просто хотите запустить некоторую бизнес-логику перед созданием объекта - так почему бы вам просто не создать статический метод внутри «BusinessClass», который выполняет всю грязную работу проверки «myProperty», и сделать конструктор закрытым?
public BusinessClass { public string MyProperty { get; private set; } private BusinessClass() { } private BusinessClass(string myProperty) { MyProperty = myProperty; } public static BusinessClass CreateObject(string myProperty) { // Perform some check on myProperty if (/* all ok */) return new BusinessClass(myProperty); return null; } }
Вызвать это было бы довольно просто:
источник
Вы можете сделать конструктор частным, а фабрику - вложенным типом:
public class BusinessObject { private BusinessObject(string property) { } public class Factory { public static BusinessObject CreateBusinessObject(string property) { return new BusinessObject(property); } } }
Это работает, потому что вложенные типы имеют доступ к закрытым членам их включающих типов. Я знаю, что это немного ограничительно, но, надеюсь, это поможет ...
источник
CreateBusinessObject
метода внутри вложенногоFactory
класса вместо того, чтобы этот статический метод был методом непосредственноBusinessObject
класса ... можете ли вы вспомнить свой мотивация для этого?Build
.)Или, если вы хотите пойти по-настоящему необычно, инвертируйте управление: пусть класс вернет фабрику, и оснастите фабрику делегатом, который может создать класс.
public class BusinessObject { public static BusinessObjectFactory GetFactory() { return new BusinessObjectFactory (p => new BusinessObject (p)); } private BusinessObject(string property) { } } public class BusinessObjectFactory { private Func<string, BusinessObject> _ctorCaller; public BusinessObjectFactory (Func<string, BusinessObject> ctorCaller) { _ctorCaller = ctorCaller; } public BusinessObject CreateBusinessObject(string myProperty) { if (...) return _ctorCaller (myProperty); else return null; } }
:)
источник
Вы можете сделать конструктор своего класса MyBusinessObjectClass внутренним и переместить его и фабрику в их собственную сборку. Теперь только фабрика должна иметь возможность создавать экземпляр класса.
источник
Помимо того, что предложил Джон, вы также можете либо сделать фабричный метод (включая проверку) статическим методом BusinessObject в первую очередь. Затем сделайте конструктор закрытым, и все остальные будут вынуждены использовать статический метод.
public class BusinessObject { public static Create (string myProperty) { if (...) return new BusinessObject (myProperty); else return null; } }
Но реальный вопрос - почему у вас есть это требование? Допустимо ли переместить в класс фабрику или фабричный метод?
источник
Спустя столько лет это спросили, и все ответы, которые я вижу, к сожалению, говорят вам, как вы должны делать свой код, вместо того, чтобы давать прямой ответ. Фактический ответ, который вы искали, - иметь ваши классы с частным конструктором, но с общедоступным экземпляром, что означает, что вы можете создавать новые экземпляры только из других существующих экземпляров ... которые доступны только на фабрике:
Интерфейс для ваших классов:
public interface FactoryObject { FactoryObject Instantiate(); }
Твой класс:
public class YourClass : FactoryObject { static YourClass() { Factory.RegisterType(new YourClass()); } private YourClass() {} FactoryObject FactoryObject.Instantiate() { return new YourClass(); } }
И, наконец, завод:
public static class Factory { private static List<FactoryObject> knownObjects = new List<FactoryObject>(); public static void RegisterType(FactoryObject obj) { knownObjects.Add(obj); } public static T Instantiate<T>() where T : FactoryObject { var knownObject = knownObjects.Where(x => x.GetType() == typeof(T)); return (T)knownObject.Instantiate(); } }
Затем вы можете легко изменить этот код, если вам нужны дополнительные параметры для создания экземпляров или для предварительной обработки экземпляров, которые вы создаете. И этот код позволит вам принудительно создать экземпляр через фабрику, поскольку конструктор класса является частным.
источник
Еще один (легкий) вариант - создать статический фабричный метод в классе BusinessObject и оставить конструктор закрытым.
public class BusinessObject { public static BusinessObject NewBusinessObject(string property) { return new BusinessObject(); } private BusinessObject() { } }
источник
Так что, похоже, то, что я хочу, нельзя сделать «чистым» способом. Это всегда своего рода «обратный вызов» логическому классу.
Может быть, я мог бы сделать это простым способом, просто заставив метод конструктора в классе объекта сначала вызвать класс логики для проверки ввода?
public MyBusinessObjectClass { public string MyProperty { get; private set; } private MyBusinessObjectClass (string myProperty) { MyProperty = myProperty; } pubilc static MyBusinessObjectClass CreateInstance (string myProperty) { if (MyBusinessLogicClass.ValidateBusinessObject (myProperty)) return new MyBusinessObjectClass (myProperty); return null; } } public MyBusinessLogicClass { public static bool ValidateBusinessObject (string myProperty) { // Perform some check on myProperty return CheckResult; } }
Таким образом, бизнес-объект не может быть создан напрямую, и открытый метод проверки в бизнес-логике также не причинит вреда.
источник
В случае хорошего разделения интерфейсов и реализаций
шаблон защищенного конструктора-общедоступного-инициализатора позволяет получить очень изящное решение.
Учитывая бизнес-объект:
public interface IBusinessObject { } class BusinessObject : IBusinessObject { public static IBusinessObject New() { return new BusinessObject(); } protected BusinessObject() { ... } }
и фабрика бизнеса:
public interface IBusinessFactory { } class BusinessFactory : IBusinessFactory { public static IBusinessFactory New() { return new BusinessFactory(); } protected BusinessFactory() { ... } }
следующее изменение
BusinessObject.New()
инициализатора дает решение:class BusinessObject : IBusinessObject { public static IBusinessObject New(BusinessFactory factory) { ... } ... }
Здесь для вызова
BusinessObject.New()
инициализатора требуется ссылка на конкретную фабрику бизнеса . Но единственный, у кого есть необходимая справка, - это сама фабрика бизнеса.Мы получили то , что хотели: только тот , кто может создать
BusinessObject
этоBusinessFactory
.источник
public static IBusinessObject New(BusinessFactory factory)
вызовут у многих брови, если кто-то еще будет поддерживать код.factory
наasFriend
. В моей кодовой базе это будет отображаться какpublic static IBusinessObject New(BusinessFactory asFriend)
public class HandlerFactory: Handler { public IHandler GetHandler() { return base.CreateMe(); } } public interface IHandler { void DoWork(); } public class Handler : IHandler { public void DoWork() { Console.WriteLine("hander doing work"); } protected IHandler CreateMe() { return new Handler(); } protected Handler(){} } public static void Main(string[] args) { // Handler handler = new Handler(); - this will error out! var factory = new HandlerFactory(); var handler = factory.GetHandler(); handler.DoWork(); // this works! }
источник
Я бы поместил фабрику в ту же сборку, что и доменный класс, и пометил бы конструктор доменного класса как внутренний. Таким образом, любой класс в вашем домене может создать экземпляр, но вы не доверяете себе, верно? Любой, кто пишет код за пределами уровня домена, должен будет использовать вашу фабрику.
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(); } }
источник
Это решение основано на универсальной идее использования токена в конструкторе. Сделано в этом ответе, убедитесь, что объект создается только фабрикой (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); } }
источник
Было упомянуто несколько подходов с разными компромиссами.
Create
метод и частный 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); } }
источник
Я не понимаю, почему вы хотите отделить «бизнес-логику» от «бизнес-объекта». Это звучит как искажение ориентации объекта, и вы в конечном итоге свяжете себя узлами, применив такой подход.
источник
Я не думаю, что есть решение, которое не хуже, чем проблема, все, что он сказал выше, требует общедоступной статической фабрики, которая, IMHO, является более серьезной проблемой и не помешает людям просто вызывать фабрику для использования вашего объекта - она ничего не скрывает. Лучше всего предоставить интерфейс и / или сохранить конструктор как внутренний, если вы можете, это лучшая защита, поскольку сборка является надежным кодом.
Один из вариантов - иметь статический конструктор, который где-нибудь регистрирует фабрику с чем-то вроде контейнера IOC.
источник
Вот еще одно решение в духе «только потому, что ты можешь, не значит, что ты должен» ...
Он отвечает требованиям сохранения приватности конструктора бизнес-объекта и помещения фабричной логики в другой класс. После этого становится немного схематично.
Класс фабрики имеет статический метод для создания бизнес-объектов. Он является производным от класса бизнес-объекта, чтобы получить доступ к статическому защищенному методу построения, который вызывает частный конструктор.
Фабрика абстрактна, поэтому вы не можете создать ее экземпляр (потому что это также был бы бизнес-объект, что было бы странно), и у нее есть частный конструктор, поэтому клиентский код не может быть производным от него.
Что не помешало это код клиента также производным от класса бизнес-объекта и вызывает защищенный (но не проверенный) статический метод построения. Или, что еще хуже, вызов защищенного конструктора по умолчанию, который мы должны были добавить, чтобы в первую очередь скомпилировать фабричный класс. (Что, кстати, может быть проблемой с любым шаблоном, который отделяет класс фабрики от класса бизнес-объекта.)
Я не пытаюсь предложить кому-то в здравом уме сделать что-то подобное, но это было интересное упражнение. FWIW, моим предпочтительным решением было бы использовать внутренний конструктор и границу сборки в качестве защиты.
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() { } }
источник
Был бы признателен за некоторые мысли об этом решении. Единственный, кто может создать 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); } } } }
источник