Klasse mit Mitgliedern, die während der Erstellung veränderbar, danach aber unveränderlich sind

22

Ich habe einen Algorithmus, der eine Sammlung von Objekten erstellt. Diese Objekte können während der Erstellung geändert werden, da sie mit sehr wenig anfangen, aber dann an verschiedenen Stellen im Algorithmus mit Daten gefüllt werden.

Nach Abschluss des Algorithmus sollten die Objekte niemals geändert werden - sie werden jedoch von anderen Teilen der Software verwendet.

Wird es in diesen Szenarien als gute Praxis angesehen, zwei Versionen der Klasse zu haben, wie unten beschrieben?

  • Das veränderbare wird dann durch den Algorithmus erzeugt
  • Nach Abschluss des Algorithmus werden die Daten in unveränderbare Objekte kopiert, die zurückgegeben werden.
Paul Richards
quelle
3
Könnten Sie Ihre Frage bearbeiten, um zu klären, was Ihr Problem / Ihre Frage ist?
Simon Bergot
Diese Frage könnte Sie auch interessieren: Wie man ein Eis am Stiel in .NET einfriert (eine Klasse unveränderlich macht)
R0MANARMY

Antworten:

46

Sie können möglicherweise das Builder-Muster verwenden . Es wird ein separates Builder-Objekt verwendet, um die erforderlichen Daten zu erfassen. Wenn alle Daten erfasst sind, wird das eigentliche Objekt erstellt. Das erstellte Objekt kann unveränderlich sein.

JacquesB
quelle
Ihre Lösung war die richtige für meine Anforderungen (von denen nicht alle angegeben wurden). Bei der Untersuchung haben die zurückgegebenen Objekte nicht alle die gleichen Eigenschaften. Die Builder-Klasse verfügt über eine Vielzahl von nullwertfähigen Feldern. Wenn die Daten erstellt wurden, wird ausgewählt, welche von drei verschiedenen Klassen generiert werden soll: Diese werden durch Vererbung miteinander verknüpft.
Paul Richards
2
@Paul Wenn diese Antwort Ihr Problem gelöst hat, sollten Sie sie als akzeptiert markieren.
Riking
24

Eine einfache Möglichkeit, dies zu erreichen, wäre eine Schnittstelle, über die man Eigenschaften lesen und nur Nur-Lese-Methoden aufrufen kann, und eine Klasse, die diese Schnittstelle implementiert, mit der Sie auch diese Klasse schreiben können.

Ihre Methode, die es erstellt, verarbeitet das erstere und gibt das letztere zurück, wobei nur eine schreibgeschützte Schnittstelle zur Interaktion bereitgestellt wird. Dies würde kein Kopieren erfordern, und Sie können das Verhalten, das Sie dem Anrufer zur Verfügung stellen möchten, im Gegensatz zum Ersteller auf einfache Weise optimieren.

Nehmen Sie dieses Beispiel:

public interface IPerson 
{
    public String FirstName 
    {
        get;
    }

    public String LastName 
    {
        get;
    }
} 

public class PersonImpl : IPerson 
{
    private String firstName, lastName;

    public String FirstName 
    {
        get { return firstName; }
        set { firstName = value; }
    }

    public String LastName 
    {
        get { return lastName; }
        set { lastName = value; }
    }
}

class Factory 
{
    public IPerson MakePerson() 
    {
        PersonImpl person = new PersonImpl();
        person.FirstName = 'Joe';
        person.LastName = 'Schmoe';
        return person;
    }
}

Der einzige Nachteil dieses Ansatzes besteht darin, dass er einfach in die implementierende Klasse umgewandelt werden kann. Wenn es um Sicherheit geht, reicht es nicht aus, diesen Ansatz zu verwenden. Eine Problemumgehung besteht darin, dass Sie eine Fassadenklasse erstellen können, um die veränderbare Klasse zu umschließen, die einfach eine Schnittstelle darstellt, mit der der Aufrufer arbeitet und auf das interne Objekt keinen Zugriff hat.

Auf diese Weise hilft Ihnen nicht einmal das Casting. Beide können von derselben schreibgeschützten Schnittstelle abgeleitet werden. Durch das Konvertieren des zurückgegebenen Objekts erhalten Sie jedoch nur die Facade-Klasse, die unveränderlich ist, da sie den zugrunde liegenden Status der umgebrochenen veränderlichen Klasse nicht ändert.

Erwähnenswert ist, dass dies nicht dem typischen Trend folgt, in dem ein unveränderliches Objekt durch seinen Konstruktor ein für allemal konstruiert wird. Verständlicherweise müssen Sie sich möglicherweise mit vielen Parametern befassen, aber Sie sollten sich fragen, ob alle diese Parameter im Voraus definiert werden müssen oder ob einige später eingeführt werden können. In diesem Fall sollte ein einfacher Konstruktor mit nur erforderlichen Parametern verwendet werden. Mit anderen Worten, verwenden Sie dieses Muster nicht, wenn es ein anderes Problem in Ihrem Programm verdeckt.

Neil
quelle
1
Die Sicherheit ist nicht besser, wenn ein "schreibgeschütztes" Objekt zurückgegeben wird, da der Code, der das Objekt abruft, das Objekt unter Verwendung von Reflektion weiterhin ändern kann. Sogar eine Zeichenfolge kann durch Reflektion geändert (nicht kopiert, direkt geändert) werden.
MTilsted
Das Sicherheitsproblem kann ordentlich mit einer Privatklasse gelöst werden
Esben Skov Pedersen
@Esben: Sie müssen sich immer noch mit MS07-052 herumschlagen: Die Codeausführung führt zur Codeausführung . Ihr Code wird im gleichen Sicherheitskontext ausgeführt wie sein Code, sodass er nur einen Debugger anfügen und alles tun kann, was er möchte.
Kevin
Kevin1 das kann man über alle Kapseln sagen. Ich versuche nicht, mich vor Reflexion zu schützen.
Esben Skov Pedersen
1
Das Problem bei der Verwendung des Wortes "Sicherheit" ist, dass sofort jemand davon ausgeht, dass das, was ich als sicherere Option bezeichne, der maximalen Sicherheit und den besten Praktiken entspricht. Ich habe es auch nie gesagt. Wenn Sie die Bibliothek zur Nutzung geben, es sei denn, sie ist verschleiert (und manchmal auch verschleiert), können Sie die Gewährleistung der Sicherheit vergessen. Ich denke jedoch, dass wir uns alle einig sein können, dass Sie die Bibliothek nicht genau so verwenden, wie sie verwendet werden sollte, wenn Sie ein zurückgegebenes Fassadenobjekt mithilfe von Reflektion manipulieren, um das darin enthaltene interne Objekt abzurufen.
Neil
8

Sie können die Builder - Muster verwenden , wie @JacquesB sagt, oder denken , alternativ , warum es eigentlich ist , dass diese Objekte von Ihnen haben bei der Erstellung sein wandelbar?

Mit anderen Worten, warum muss der Erstellungsprozess zeitlich verteilt werden, anstatt alle erforderlichen Werte in den Konstruktor zu übertragen und Instanzen auf einmal zu erstellen?

Denn der Builder könnte eine gute Lösung für das falsche Problem sein.

Wenn das Problem darin besteht, dass Sie einen Konstruktor haben, der etwa 10 Parameter lang ist, und Sie möchten ihn abmildern, indem Sie das Objekt nach und nach erstellen, könnte dies bedeuten, dass das Design durcheinander ist und diese 10 Werte " bagged "/ gruppiert in ein paar Objekte ... oder das Hauptobjekt in ein paar kleinere aufgeteilt ...

In diesem Fall - bleiben Sie bei der Unveränderlichkeit, verbessern Sie einfach das Design.

Konrad Morawski
quelle
Es ist schwierig, alles auf einmal zu erstellen, da der Code mehrere Datenbankabfragen ausführt, um die Daten abzurufen. Es vergleicht Daten in verschiedenen Tabellen und in verschiedenen Datenbanken.
Paul Richards