it-swarm.com.de

Refactoring-Konstruktor mit zu vielen Parametern

Ich bin in meinen ersten zwei Monaten als Softwareentwickler und wollte nur Ratschläge bekommen, ob dies verbessert werden kann.

Ich habe eine Klasse erstellt, die Daten von RFID in Form einer Nachricht darstellt:

 class RFIDMessage
 {

        string _a;
        string _b;
        string _c;
        string _d;
        string _e;
        string _f;
        string _g;
        string _h;

        public RFIDMessage(string a, string b, string c, string d, string e, string f, string g, string h)
        {
            _a = a;
            _b = b;
            _c = c;
            _d = d;
            _e = e;
            _f = f;
            _g = g;
            _h = h;
        }
}

Ich habe über das Prinzip der Einzelverantwortung gelesen und kann nicht herausfinden, ob dieser Konstruktor durch Bereitstellung eines neuen Datentyps verbessert werden kann. Diese Nachricht wird aus den Daten analysiert, die von einem RFID-Tag gelesen wurden, und acht Parameter scheinen etwas zu viele zu sein. Die Parameter ändern sich in dieser Instanz nicht, es gibt jedoch andere Nachrichtentypen - dies ist nur der Standard.

Meine Fragen sind:

1) Gibt es eine Möglichkeit, die Anzahl der Parameter zu reduzieren, die den Code wartbarer machen?

2) Wie kann ich das Design dieser Klasse ändern oder verbessern, um mehr Nachrichtentypen aufzunehmen?

15
M161

Sie haben Recht, 8 Parameter - alle Zeichenfolgen - machen den Konstruktor zu einem guten Kandidaten für eine Codeüberprüfung.

Betrachten Sie einige der nächsten Punkte.

Wesentliche Attribute zuerst

Sehen Sie sich das Nachrichtenmodell an und finden Sie heraus, welche Attribute erforderlich sind, um eine Instanz in einem konsistenten Zustand zu initialisieren. Reduzieren Sie die Anzahl der Argumente auf das Wesentliche. Fügen Sie für den Rest Setter oder Funktionen hinzu.

Wenn alle 8 Attribute erforderlich und schreibgeschützt sind, gibt es nicht zu viel zu tun.

Verkapselung

Ziehen Sie in Betracht, korrelierte Parameter zu kapseln. Zum Beispiel könnten A, B und C zusammen in einer neuen Klasse platziert werden. Finden Sie heraus, welche Parameter sich aus denselben Gründen gleichzeitig ändern.

Es reduziert die Anzahl der Argumente auf Kosten einer weiteren Klasse (Komplexität).

Verwenden Sie Kreationsmuster

Anstatt Nachrichten direkt von einer beliebigen Stelle in der Codequelle aus zu initialisieren, tun Sie dies von Fabriken oder Builder .

Arrays

Wenn keiner der oben genannten Punkte funktioniert, versuchen Sie es mit einem Array von Parametern. Angesichts des Fehlens aussagekräftiger Parameternamen ist dies wahrscheinlich die einfachste Lösung.

In Bezug auf Arrays habe ich eine Frage gepostet, in der ich nach ihrer Eignung frage. Ich zögerte, auf eine solche Lösung zu vertrauen. Die Antworten haben mir jedoch geholfen, weniger zurückhaltend zu sein. Probieren Sie es aus, wenn Sie diese Lösung auch nicht mögen. Es könnte Ihre Meinung darüber ändern.

Erbe

Schließlich werden Sie feststellen, dass Nachrichten gute Kandidaten für die Vererbung sind. Das Segmentieren von Nachrichten nach Attributen verursacht einen geringen Overhead, da Sie früher oder später im gesamten Code nach dem Typ if(message.getType() == ...) fragen.

19
Laiv

Hier ist eine ketzerische Antwort: Ich sehe nichts an sich Falsches daran, viele Parameter zu haben.

Eine Funktion zu haben, die viele Parameter akzeptiert, ist oft ein Ziel für das Refactoring, aber nur weil wir etwas in einen cooleren Codeabschnitt umgestaltet haben, heißt das nicht, dass wir dies hätten tun sollen .

Die Verwendung eines Arrays oder von Array-Argumenten anstelle von 8 separaten Parametern bedeutet, dass Sie zusätzliche Logik benötigen, um zu erzwingen, dass die richtige Anzahl von Parametern übergeben wird. Die Verwendung eines Builder-Musters ist alles in Ordnung und gut, außer wenn ich hinter Sie komme, um es zu pflegen Der Code und Sie haben ein Builder-Muster für jede einzelne dumme kleine Datenstruktur, dann werde ich wahrscheinlich in Ihren bajillion Codezeilen verloren gehen. Wenn Sie wirklich alle 8 Argumente benötigen, können Sie einige nicht zu Setter-Requisiten verschieben, da Sie eine Instanz der Klasse ohne alle erforderlichen Daten haben könnten.

Ich weiß aus eigener Erfahrung, dass ich, wenn ich eine Codebasis erbe, den Code lieber in einem langweiligen, langwierigen, offensichtlichen Stil (wie Ihren 8 Konstruktorargumenten usw. usw.) anstatt mit einem coolen/einrichten möchte komplexes neues DI-Framework/Muster, das ich noch nie benutzt habe.

Wenn Ihre obige Klasse eine einfache (hoffentlich unveränderliche) Datenstruktur ist, die wirklich genau 8 Zeichenfolgenargumente benötigt, um gültig zu sein, dann sage ich, dass 8 Parameter eine vollkommen gute Möglichkeit sind, dies zu erreichen.

32
Graham

Ich gehe davon aus, dass diese Klasse unveränderlich sein soll.

Wenn Sie "echte" Unveränderlichkeit wünschen, müssen Sie sich an die Konstruktorargumente halten, da der Konstruktor der einzige Code ist, der ein readonly -Feld setzen kann.

Sie können jedoch auch nur öffentliche Getter und private Setter implementieren. Für alle Absichten und Zwecke, die das Objekt auch unveränderlich machen. Sie würden die privaten Felder in einer statischen Factory-Methode (ish) festlegen.

In diesem Beispiel gehe ich davon aus, dass die RFID-Daten in einem Stream bei Ihrer Anwendung ankommen, aber Sie können dies leicht ändern, um mit einem Byte-Array, einer Reihe von Strukturen, XML usw. zu arbeiten.

class RFIDMessage
{
    private string _a;
    private string _b;
    private string _c;
    //etc

    private RFIDMessage() 
    {
        //Declare default constructor private so nobody else can instantiate an RFIDMessage on their own
    }

    static public RFIDMessage FromStream(Stream stream)
    {
        var reader = new RFIDStreamReader(stream);
        var newObject = new RFIDMessage();
        newObject._a = reader.ReadA();
        newObject._b = reader.ReadB();
       //etc.

       return newObject;
    }
}

void SampleUsage()
{
    var rfidStream = RFIDAPI.GetStream(); //Or whatever
    RFIDMessage message = RFIDMessage.FromStream(rfidStream);
}

Ich kenne den Namen dieses Musters nicht, aber es ist in der .NET CLR üblich, z. FromBase64String .

3
John Wu

Nehmen wir an, Sie benötigen alle diese Datenbits in einer Instanz und vermeiden es, das Design der Klasse in Frage zu stellen. Möglicherweise könnte die Klasse weniger Mitglieder haben und die Gesamtmenge an Code könnte kleiner sein, wenn Sie eine andere Klasse der unteren Ebene erstellt haben, die eine Teilmenge dieser Mitglieder sammelt. Das ist nicht die Frage.

Wenn Sie das .net-Framework Entwurfsrichtlinien für Konstruktoren lesen, können Sie feststellen, dass eine geringere Anzahl von Parametern bevorzugt wird, sowohl weil Standardelementwerte den Code reduzieren können als auch weil es schwierig ist, viele Parameter ohne Mischen zu übergeben die Bestellung auf. Die Alternative besteht darin, Eigenschaften zu verwenden, um die Instanz vor der Verwendung einzurichten. Sie legen die Eigenschaften fest, für die nicht standardmäßige Werte erforderlich sind, und lassen die anderen unverändert. Dies kann die Verwendung vereinfachen.

Da schreibgeschützte Felder gegenüber veränderlichen Feldern bevorzugt werden sollten, gibt es einen Kompromiss, wenn veränderbare Felder nicht benötigt werden. Sie können schreibgeschützte Felder nur über Konstruktoren initialisieren. Sie haben keine schreibgeschützten Felder verwendet, möchten diese Entscheidung jedoch möglicherweise überdenken. Es ist eine Schwäche der Umgebung, die Sie zwingt, die Entscheidung zu treffen.

1
Frank Hileman

Obwohl ich denke, dass Ihr Code je nach tatsächlicher Situation in Ordnung sein könnte, möchte ich eine andere mögliche Lösung zeigen: "Fließende Syntax". Die Idee hinter der fließenden Syntax besteht darin, Methoden zum Ändern des Status des Objekts bereitzustellen, die das geänderte Objekt zurückgeben. Z.B.

public RFIDMessage SetA(string a)
{
    this._a = a;
    return this;
}

Es ermöglicht Code wie

RFIDMessage message = new RFIDMessage().SetA("Some A").SetC("Some C").SetH("Some H");

Beachten Sie, dass die Instanz jetzt nach der Erstellung geändert werden kann, was je nach Ihren Anforderungen erwünscht oder problematisch sein kann. Wenn es sich um ein Problem handelt, können Sie eine "schreibgeschützte Schnittstelle" erstellen, die von der Klasse implementiert wird (und die oben genannten SetA-Methoden sind nicht Teil der Schnittstelle), sodass Benutzer der Instanz wissen, dass die schreibgeschützte Schnittstelle dies nicht kann ändern Sie es.

1
Bernhard Hiller