Vor kurzem habe ich darüber nachgedacht, einen Teil meines Codes zu sichern. Ich bin gespannt, wie man sicherstellen kann, dass ein Objekt niemals direkt erstellt werden kann, sondern nur über eine Methode einer Factory-Klasse. Angenommen, ich habe eine "Geschäftsobjekt" -Klasse und möchte sicherstellen, dass jede Instanz dieser Klasse einen gültigen internen Status hat. Um dies zu erreichen, muss ich einige Überprüfungen durchführen, bevor ich ein Objekt erstelle, wahrscheinlich in seinem Konstruktor. Dies ist alles in Ordnung, bis ich beschließe, dass diese Überprüfung Teil der Geschäftslogik sein soll. Wie kann ich also veranlassen, dass ein Geschäftsobjekt nur mit einer Methode in meiner Geschäftslogikklasse erstellt werden kann, jedoch niemals direkt? Der erste natürliche Wunsch, ein gutes altes "Freund" -Schlüsselwort von C ++ zu verwenden, wird mit C # nicht erfüllt. Wir brauchen also andere Optionen ...
Versuchen wir ein Beispiel:
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;
}
}
Es ist alles in Ordnung, bis Sie sich daran erinnern, dass Sie die MyBusinessObjectClass-Instanz weiterhin direkt erstellen können, ohne die Eingabe zu überprüfen. Ich möchte diese technische Möglichkeit insgesamt ausschließen.
Was denkt die Community darüber?
quelle
CreateBusinessObject
Methode besteht darin, Argumente in einem einzelnen Methodenaufruf zu validieren UND (ein neues gültiges Objekt zurückzugeben ODER einen Fehler zurückzugeben), wenn ein Aufrufer nicht sofort weiß, ob Argumente für die Übergabe an den Konstruktor gültig sind.Distance
Instanz mit einem negativen Argument sollteArgumentOutOfRangeException
beispielsweise eine auslösen. Sie würden nichts davon profitieren, wenn Sie eine Instanz erstellenDistanceFactory
, die dieselbe Prüfung durchführt. Ich sehe immer noch nicht, was Sie hier gewinnen können.Antworten:
Sieht so aus, als ob Sie vor dem Erstellen des Objekts nur eine Geschäftslogik ausführen möchten. Warum erstellen Sie also nicht einfach eine statische Methode in der "BusinessClass", die alle schmutzigen "myProperty" -Prüfungen ausführt, und machen den Konstruktor privat?
Es zu nennen wäre ziemlich einfach:
quelle
Sie können den Konstruktor privat und die Factory als verschachtelten Typ festlegen:
Dies funktioniert, weil verschachtelte Typen Zugriff auf die privaten Mitglieder ihrer umschließenden Typen haben. Ich weiß, dass es ein bisschen restriktiv ist, aber hoffentlich hilft es ...
quelle
CreateBusinessObject
Methode in eine verschachtelteFactory
Klasse zu integrieren, anstatt dass diese statische Methode eine Methode direkt derBusinessObject
Klasse ist. Können Sie sich an Ihre erinnern? Motivation dafür?Build
.)Wenn Sie wirklich ausgefallen sein möchten, kehren Sie die Kontrolle um: Lassen Sie die Klasse die Factory zurückgeben und instrumentieren Sie die Factory mit einem Delegaten, der die Klasse erstellen kann.
:) :)
quelle
Sie können den Konstruktor in Ihrer MyBusinessObjectClass-Klasse intern erstellen und ihn und die Factory in eine eigene Assembly verschieben. Jetzt sollte nur die Factory in der Lage sein, eine Instanz der Klasse zu erstellen.
quelle
Abgesehen von dem, was Jon vorgeschlagen hat, könnte die Factory-Methode (einschließlich der Prüfung) in erster Linie eine statische Methode von BusinessObject sein. Lassen Sie dann den Konstruktor privat, und alle anderen werden gezwungen, die statische Methode zu verwenden.
Die eigentliche Frage ist jedoch: Warum haben Sie diese Anforderung? Ist es akzeptabel, die Fabrik oder die Fabrikmethode in die Klasse zu verschieben?
quelle
Nach so vielen Jahren wurde dies gefragt, und alle Antworten, die ich sehe, sagen Ihnen leider, wie Sie Ihren Code machen sollten, anstatt eine klare Antwort zu geben. Die eigentliche Antwort, nach der Sie gesucht haben, besteht darin, dass Ihre Klassen einen privaten Konstruktor, aber einen öffentlichen Instanziator haben. Dies bedeutet, dass Sie nur neue Instanzen aus anderen vorhandenen Instanzen erstellen können ... die nur im Werk verfügbar sind:
Die Schnittstelle für Ihre Klassen:
Deine Klasse:
Und schließlich die Fabrik:
Anschließend können Sie diesen Code problemlos ändern, wenn Sie zusätzliche Parameter für die Instanziierung oder die Vorverarbeitung der von Ihnen erstellten Instanzen benötigen. Mit diesem Code können Sie die Instanziierung durch die Factory erzwingen, da der Klassenkonstruktor privat ist.
quelle
Eine weitere (leichte) Option besteht darin, eine statische Factory-Methode in der BusinessObject-Klasse zu erstellen und den Konstruktor privat zu halten.
quelle
Es sieht also so aus, als ob das, was ich will, nicht "rein" gemacht werden kann. Es ist immer eine Art "Rückruf" an die Logikklasse.
Vielleicht könnte ich es auf einfache Weise tun, einfach eine Konstruktormethode in der Objektklasse erstellen und zuerst die Logikklasse aufrufen, um die Eingabe zu überprüfen?
Auf diese Weise kann das Geschäftsobjekt nicht direkt erstellt werden, und die öffentliche Überprüfungsmethode in der Geschäftslogik schadet ebenfalls nicht.
quelle
Bei einer guten Trennung zwischen Schnittstellen und Implementierungen ermöglicht das
Muster des geschützten Konstruktor-Public-Initialisierers eine sehr saubere Lösung.
Bei einem Geschäftsobjekt:
und eine Geschäftsfabrik:
Die folgende Änderung am
BusinessObject.New()
Initialisierer ergibt die Lösung:Hier wird ein Verweis auf eine konkrete Geschäftsfabrik benötigt, um den
BusinessObject.New()
Initialisierer aufzurufen . Aber der einzige, der die erforderliche Referenz hat, ist die Business Factory selbst.Wir haben bekommen, was wir wollten: Der einzige, der schaffen kann,
BusinessObject
istBusinessFactory
.quelle
public static IBusinessObject New(BusinessFactory factory)
viele Augenbrauen hochziehen, wenn jemand anderes den Code beibehält.factory
in ändernasFriend
. In meiner Codebasis würde es dann alspublic static IBusinessObject New(BusinessFactory asFriend)
quelle
Ich würde die Factory in dieselbe Assembly wie die Domänenklasse stellen und den internen Konstruktor der Domänenklasse markieren. Auf diese Weise kann jede Klasse in Ihrer Domäne möglicherweise eine Instanz erstellen, aber Sie vertrauen darauf, dass Sie dies nicht tun, oder? Jeder, der Code außerhalb der Domänenschicht schreibt, muss Ihre Factory verwenden.
Allerdings muss ich deinen Ansatz in Frage stellen :-)
Ich denke, wenn Sie möchten, dass Ihre Person-Klasse bei der Erstellung gültig ist, müssen Sie den Code in den Konstruktor einfügen.
quelle
Diese Lösung basiert auf der Idee von Munificents , ein Token im Konstruktor zu verwenden. Stellen Sie in dieser Antwort sicher, dass das Objekt nur vom Werk erstellt wurde (C #).
quelle
Es wurden mehrere Ansätze mit unterschiedlichen Kompromissen erwähnt.
Create
Methode und einem privaten Ctor besser dran .Ich möchte die Factory als Teilklasse vorschlagen, die private verschachtelte Klassen mit öffentlichen Konstruktoren enthält. Sie verstecken das Objekt, das Ihre Fabrik erstellt, zu 100% und legen nur das, was Sie auswählen, über eine oder mehrere Schnittstellen offen.
Der Anwendungsfall, den ich dafür gehört habe, wäre, wenn Sie 100% der Instanzen in der Fabrik verfolgen möchten. Dieses Design garantiert niemandem, aber die Fabrik hat Zugriff auf die Erstellung von Instanzen von "Chemikalien", die in der "Fabrik" definiert sind, und macht eine separate Baugruppe überflüssig, um dies zu erreichen.
quelle
Ich verstehe nicht, warum Sie die "Geschäftslogik" vom "Geschäftsobjekt" trennen wollen. Dies klingt nach einer Verzerrung der Objektorientierung, und Sie werden sich auf diese Weise in Knoten binden.
quelle
Ich glaube nicht, dass es eine Lösung gibt, die nicht schlimmer ist als das Problem. Alles, was er oben erwähnt, erfordert eine öffentliche statische Fabrik, die meiner Meinung nach ein schlimmeres Problem ist und die Leute nicht davon abhält, nur die Fabrik anzurufen, um Ihr Objekt zu verwenden - sie verbirgt nichts. Stellen Sie am besten eine Schnittstelle bereit und / oder behalten Sie den Konstruktor als intern bei, wenn Sie können. Dies ist der beste Schutz, da die Assembly vertrauenswürdiger Code ist.
Eine Möglichkeit besteht darin, einen statischen Konstruktor zu haben, der eine Fabrik irgendwo mit so etwas wie einem IOC-Container registriert.
quelle
Hier ist eine andere Lösung im Sinne von "Nur weil du kannst, heißt das nicht, dass du es solltest" ...
Es erfüllt die Anforderungen, den Geschäftsobjektkonstruktor privat zu halten und die Factory-Logik in eine andere Klasse zu stellen. Danach wird es etwas lückenhaft.
Die Factory-Klasse verfügt über eine statische Methode zum Erstellen von Geschäftsobjekten. Es wird von der Geschäftsobjektklasse abgeleitet, um auf eine statisch geschützte Konstruktionsmethode zuzugreifen, die den privaten Konstruktor aufruft.
Die Factory ist abstrakt, sodass Sie keine Instanz davon erstellen können (da es sich auch um ein Geschäftsobjekt handelt, was seltsam wäre). Sie verfügt über einen privaten Konstruktor, sodass Client-Code nicht daraus abgeleitet werden kann.
Was nicht verhindert wird, ist, dass Clientcode auch von der Geschäftsobjektklasse abgeleitet wird und die geschützte (aber nicht validierte) statische Konstruktionsmethode aufruft. Oder schlimmer noch, wir mussten den geschützten Standardkonstruktor aufrufen, den wir hinzufügen mussten, damit die Factory-Klasse überhaupt kompiliert werden konnte. (Was im Übrigen wahrscheinlich ein Problem mit einem Muster darstellt, das die Factory-Klasse von der Business-Objektklasse trennt.)
Ich versuche nicht vorzuschlagen, dass jemand, der bei klarem Verstand ist, so etwas tun sollte, aber es war eine interessante Übung. FWIW, meine bevorzugte Lösung wäre, einen internen Konstruktor und die Baugruppengrenze als Schutz zu verwenden.
quelle
Würde mich freuen, einige Gedanken zu dieser Lösung zu hören. Der einzige, der 'MyClassPrivilegeKey' erstellen kann, ist die Factory. und 'MyClass' erfordert es im Konstruktor. So vermeiden Sie Überlegungen zu privaten Auftragnehmern / "Registrierung" in der Fabrik.
quelle