Ostatnio myślałem o zabezpieczeniu części mojego kodu. Ciekaw jestem, jak można się upewnić, że obiekt nigdy nie zostanie utworzony bezpośrednio, ale tylko za pomocą jakiejś metody klasy fabrycznej. Powiedzmy, że mam klasę „obiektów biznesowych” i chcę się upewnić, że każda instancja tej klasy będzie miała prawidłowy stan wewnętrzny. Aby to osiągnąć, będę musiał sprawdzić przed utworzeniem obiektu, prawdopodobnie w jego konstruktorze. Wszystko jest w porządku, dopóki nie zdecyduję, że chcę, aby ta kontrola była częścią logiki biznesowej. Jak więc ustawić, aby obiekt biznesowy był tworzony tylko za pomocą jakiejś metody w mojej klasie logiki biznesowej, ale nigdy bezpośrednio? Pierwsza naturalna chęć użycia dobrego, starego słowa kluczowego „przyjaciel” języka C ++ nie powiedzie się w przypadku języka C #. Potrzebujemy więc innych opcji ...
Spróbujmy na przykład:
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;
}
}
Wszystko jest w porządku, dopóki nie przypomnisz sobie, że nadal możesz bezpośrednio utworzyć instancję MyBusinessObjectClass, bez sprawdzania danych wejściowych. Chciałbym całkowicie wykluczyć tę techniczną możliwość.
Więc co o tym myśli społeczność?
źródło
CreateBusinessObject
metody jest sprawdzenie poprawności argumentów ORAZ (zwrócenie nowego prawidłowego obiektu LUB zwrócenie błędu) w pojedynczym wywołaniu metody, gdy obiekt wywołujący nie wie od razu, czy argumenty są prawidłowe do przekazania do konstruktora.Distance
instancji z ujemnym argumentem powinnoArgumentOutOfRangeException
na przykład spowodować , że nic nie zyskasz na utworzeniu instancji,DistanceFactory
która wykonuje to samo sprawdzenie. Nadal nie widzę, co możesz tutaj zyskać.Odpowiedzi:
Wygląda na to, że przed utworzeniem obiektu chcesz po prostu uruchomić logikę biznesową - dlaczego więc nie utworzyć statycznej metody wewnątrz klasy „BusinessClass”, która wykonuje wszystkie brudne sprawdzanie właściwości „myProperty” i uczyni konstruktor prywatnym?
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; } }
Nazywanie tego byłoby całkiem proste:
źródło
Możesz ustawić konstruktor jako prywatny, a fabrykę jako typ zagnieżdżony:
public class BusinessObject { private BusinessObject(string property) { } public class Factory { public static BusinessObject CreateBusinessObject(string property) { return new BusinessObject(property); } } }
Działa to, ponieważ typy zagnieżdżone mają dostęp do prywatnych elementów członkowskich ich otaczających typów. Wiem, że to trochę restrykcyjne, ale mam nadzieję, że pomoże ...
źródło
CreateBusinessObject
metody wewnątrzFactory
klasy zagnieżdżonej zamiast używania tej metody statycznej jako metody bezpośrednio zBusinessObject
klasy ... czy możesz sobie przypomnieć motywacja do zrobienia tego?Build
.)Lub, jeśli chcesz naprawdę zaszaleć, odwróć kontrolę: niech klasa zwróci fabrykę i oprzyrząduje fabrykę delegatem, który może utworzyć klasę.
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; } }
:)
źródło
Można utworzyć konstruktora klasy MyBusinessObjectClass jako wewnętrzną i przenieść go wraz z fabryką do ich własnego zestawu. Teraz tylko fabryka powinna mieć możliwość skonstruowania instancji klasy.
źródło
Oprócz tego, co zasugerował Jon, możesz również mieć metodę fabryczną (w tym check) jako metodę statyczną BusinessObject w pierwszej kolejności. Następnie ustaw konstruktora jako prywatny, a wszyscy inni będą zmuszeni użyć metody statycznej.
public class BusinessObject { public static Create (string myProperty) { if (...) return new BusinessObject (myProperty); else return null; } }
Ale prawdziwe pytanie brzmi - dlaczego masz to wymaganie? Czy można przenieść fabrykę lub metodę fabryczną do klasy?
źródło
Po tylu latach zadano to pytanie, a wszystkie odpowiedzi, które widzę, niestety mówią ci, jak powinieneś zrobić swój kod, zamiast udzielać prostej odpowiedzi. Rzeczywista odpowiedź, której szukałeś, to posiadanie klas z prywatnym konstruktorem, ale publicznym instancją, co oznacza, że możesz tworzyć nowe instancje tylko z innych istniejących instancji ... które są dostępne tylko w fabryce:
Interfejs dla twoich zajęć:
public interface FactoryObject { FactoryObject Instantiate(); }
Twoja klasa:
public class YourClass : FactoryObject { static YourClass() { Factory.RegisterType(new YourClass()); } private YourClass() {} FactoryObject FactoryObject.Instantiate() { return new YourClass(); } }
I wreszcie fabryka:
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(); } }
Następnie możesz łatwo zmodyfikować ten kod, jeśli potrzebujesz dodatkowych parametrów do tworzenia instancji lub do wstępnego przetworzenia utworzonych instancji. Ten kod pozwoli ci wymusić tworzenie instancji przez fabrykę, ponieważ konstruktor klasy jest prywatny.
źródło
Jeszcze inną (uproszczoną) opcją jest utworzenie statycznej metody fabryki w klasie BusinessObject i zachowanie prywatności konstruktora.
public class BusinessObject { public static BusinessObject NewBusinessObject(string property) { return new BusinessObject(); } private BusinessObject() { } }
źródło
Więc wygląda na to, że to, czego chcę, nie może być zrobione w „czysty” sposób. Jest to zawsze coś w rodzaju „oddzwonienia” do klasy logiki.
Może mógłbym to zrobić w prosty sposób, po prostu zrobić metodę contructor w klasie obiektów, najpierw wywołać klasę logiki, aby sprawdzić dane wejściowe?
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; } }
W ten sposób obiekt biznesowy nie jest bezpośrednio tworzony, a metoda sprawdzania publicznego w logice biznesowej również nie zaszkodzi.
źródło
W przypadku dobrej separacji między interfejsami i implementacjami
wzorzec protected-constructor-public-initializer umożliwia bardzo zgrabne rozwiązanie.
Biorąc pod uwagę obiekt biznesowy:
public interface IBusinessObject { } class BusinessObject : IBusinessObject { public static IBusinessObject New() { return new BusinessObject(); } protected BusinessObject() { ... } }
i fabryka biznesowa:
public interface IBusinessFactory { } class BusinessFactory : IBusinessFactory { public static IBusinessFactory New() { return new BusinessFactory(); } protected BusinessFactory() { ... } }
następująca zmiana na
BusinessObject.New()
inicjatorze daje rozwiązanie:class BusinessObject : IBusinessObject { public static IBusinessObject New(BusinessFactory factory) { ... } ... }
Tutaj do wywołania
BusinessObject.New()
inicjatora potrzebne jest odniesienie do konkretnej fabryki biznesowej . Ale jedyną osobą, która ma wymagane referencje, jest sama fabryka.Mamy to, czego chcieliśmy: jedyny, który może tworzyć,
BusinessObject
toBusinessFactory
.źródło
public static IBusinessObject New(BusinessFactory factory)
uniosą wiele brwi, jeśli ktoś inny zajmie się kodem.factory
naasFriend
. W mojej bazie kodu wyglądałoby to następującopublic 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! }
źródło
Umieściłbym fabrykę w tym samym zestawie co klasa domeny i oznaczyłem konstruktora klasy domeny jako wewnętrzny. W ten sposób każda klasa w Twojej domenie może być w stanie utworzyć instancję, ale Ty sobie nie ufasz, prawda? Każdy, kto pisze kod poza warstwą domeny, będzie musiał korzystać z Twojej fabryki.
public class Person { internal Person() { } } public class PersonFactory { public Person Create() { return new Person(); } }
Muszę jednak zakwestionować Twoje podejście :-)
Myślę, że jeśli chcesz, aby Twoja klasa Person była ważna podczas tworzenia, musisz umieścić kod w konstruktorze.
public class Person { public Person(string firstName, string lastName) { FirstName = firstName; LastName = lastName; Validate(); } }
źródło
Rozwiązanie to opiera się na wspaniałej idei wykorzystania tokena w konstruktorze. Sporządzono w tej odpowiedzi, upewnij się, że obiekt został utworzony tylko przez fabrykę (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); } }
źródło
Wspomniano o wielu podejściach z różnymi kompromisami.
Create
metody i prywatnego lekarza.Chciałbym zaproponować fabrykę jako klasę częściową, która zawiera prywatne klasy zagnieżdżone z publicznymi konstruktorami. W 100% ukrywasz obiekt, który tworzy Twoja fabryka, i ujawniasz tylko to, co wybierzesz, za pośrednictwem jednego lub wielu interfejsów.
Przypadek użycia, o którym słyszałem, to przypadek, gdy chcesz śledzić 100% instancji w fabryce. Taka konstrukcja gwarantuje tylko fabryce dostęp do tworzenia instancji „chemikaliów” zdefiniowanych w „fabryce” i eliminuje potrzebę oddzielnego montażu, aby to osiągnąć.
== 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); } }
źródło
Nie rozumiem, dlaczego chcesz oddzielić „logikę biznesową” od „obiektu biznesowego”. Brzmi to jak zniekształcenie orientacji obiektu, a skończysz na zawiązywaniu się węzłami, stosując takie podejście.
źródło
Nie sądzę, aby było rozwiązanie, które nie jest gorsze niż problem, wszystko, co powyżej, wymaga publicznej fabryki statycznej, której IMHO jest gorszym problemem i nie powstrzyma ludzi po prostu dzwoniąc do fabryki, aby użyć twojego obiektu - to niczego nie ukrywa. Najlepiej ujawnić interfejs i / lub zachować konstruktora jako wewnętrznego, jeśli to możliwe, jest to najlepsza ochrona, ponieważ zestaw jest zaufanym kodem.
Jedną z opcji jest posiadanie statycznego konstruktora, który rejestruje fabrykę gdzieś w czymś w rodzaju kontenera IOC.
źródło
Oto inne rozwiązanie w duchu „tylko dlatego, że możesz, nie znaczy, że powinieneś” ...
Spełnia on wymagania zachowania prywatności konstruktora obiektów biznesowych i umieszczenia logiki fabryki w innej klasie. Potem robi się trochę szkicowo.
Klasa fabryczna zawiera statyczną metodę tworzenia obiektów biznesowych. Wywodzi się z klasy obiektu biznesowego w celu uzyskania dostępu do statycznej metody konstrukcji chronionej, która wywołuje konstruktor prywatny.
Fabryka jest abstrakcyjna, więc nie można w rzeczywistości utworzyć jej instancji (ponieważ byłby to również obiekt biznesowy, więc byłoby to dziwne) i ma prywatny konstruktor, więc kod klienta nie może z niej pochodzić.
Nie zapobiega się temu, że kod klienta również pochodzi z klasy obiektu biznesowego i wywołuje chronioną (ale nie zweryfikowaną) statyczną metodę konstrukcji. Lub, co gorsza, wywołanie chronionego domyślnego konstruktora, który musieliśmy dodać, aby skompilować klasę fabryczną w pierwszej kolejności. (Co, nawiasem mówiąc, może być problemem w przypadku dowolnego wzorca oddzielającego klasę fabryki od klasy obiektu biznesowego).
Nie próbuję sugerować nikomu przy zdrowych zmysłach, żeby zrobił coś takiego, ale było to interesujące ćwiczenie. FWIW, moim preferowanym rozwiązaniem byłoby użycie wewnętrznego konstruktora i granicy montażu jako osłony.
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() { } }
źródło
Byłbym wdzięczny za przemyślenia na temat tego rozwiązania. Jedynym, który może utworzyć „MyClassPrivilegeKey”, jest fabryka. a „MyClass” wymaga tego w konstruktorze. Uniknięto w ten sposób refleksji na temat prywatnych kontrahentów / „rejestracji” w fabryce.
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); } } } }
źródło