6 Stimmen

Der objektorientierte Weg zur Trennung des Modells von seiner Darstellung

Nehmen wir an, wir haben ein Objekt, das die Konfiguration eines Geräts darstellt. Zur Veranschaulichung: ein Temperaturregler (TempController). Er enthält eine Eigenschaft, die Sollwerttemperatur.

Ich muss diese Konfiguration in einer Datei zur Verwendung in einem anderen Gerät speichern. Das Dateiformat (FormatA) ist in Stein gemeißelt. Ich möchte nicht, dass das TempController-Objekt etwas über das Dateiformat erfährt... es ist für dieses Objekt einfach nicht relevant. Also erstelle ich ein weiteres Objekt, "FormatAExporter", das den TempController in die gewünschte Ausgabe umwandelt.

Ein Jahr später machen wir einen neuen Temperaturregler, nennen wir ihn "AdvancedTempController", der nicht nur einen Sollwert, sondern auch eine Ratenregelung hat, also ein oder zwei Eigenschaften mehr. Es wird auch ein neues Dateiformat erfunden, um diese Eigenschaften zu speichern... nennen wir es FormatB.

Beide Dateiformate sind in der Lage, beide Geräte zu repräsentieren (gehen Sie davon aus, dass AdvancedTempController vernünftige Standardeinstellungen hat, wenn es keine Einstellungen hat).

Hier ist also das Problem: Wie kann FormatBExporter beide Fälle behandeln, ohne 'isa' oder eine andere "schummelnde" Methode zu verwenden, um herauszufinden, welche Art von Objekt ich habe?

Mein erster Gedanke ist, in jedem Temperaturregler eine Methode zu haben, die einen Kundenexporter für diese Klasse bereitstellen kann, z. B. TempController.getExporter() und AdvancedTempController.getExporter(). Dadurch werden mehrere Dateiformate nicht gut unterstützt.

Der einzige andere Ansatz, der mir in den Sinn kommt, ist eine Methode in jedem Temperaturregler, die eine Liste von Eigenschaften und deren Werte zurückgibt, und dann kann der Formatierer entscheiden, wie diese ausgegeben werden sollen. Es würde funktionieren, aber das scheint verworren.

UPDATE: Bei näherer Betrachtung stellt sich heraus, dass der letztgenannte Ansatz nicht wirklich gut funktioniert. Wenn alle Ihre Typen sind einfach es könnte, aber wenn Ihre Eigenschaften sind Objekte, dann Sie am Ende nur schieben das Problem eine Ebene nach unten ... Sie sind gezwungen, ein Paar von String, Objekt-Werte zurückgeben, und der Exporteur müssen wissen, was die Objekte tatsächlich sind, um sie zu nutzen. Damit verschiebt sich das Problem nur auf eine weitere Ebene.

Gibt es irgendwelche Vorschläge, wie ich das flexibel halten kann?

4voto

jop Punkte 80065

Was Sie tun können, ist, lassen Sie die TempControllers verantwortlich für die Persistenz selbst mit einem generischen Archivierungsprogramm sein.

class TempController 
{
    private Temperature _setPoint;
    public Temperature SetPoint { get; set;}

    public ImportFrom(Archive archive)
    {
        SetPoint = archive.Read("SetPoint");
    }
    public ExportTo(Archive archive)

    {
        archive.Write("SetPoint", SetPoint);
    }
}

class AdvancedTempController
{
    private Temperature _setPoint;
    private Rate _rateControl;
    public Temperature SetPoint { get; set;}
    public Rate RateControl { get; set;}

    public ImportFrom(Archive archive)
    {
        SetPoint = archive.Read("SetPoint");
        RateControl = archive.ReadWithDefault("RateControl", Rate.Zero);
    }

    public ExportTo(Archive archive)
    {
        archive.Write("SetPoint", SetPoint);
        archive.Write("RateControl", RateControl);
    }
}

Auf diese Weise ist es den Controllern egal, wie die tatsächlichen Werte gespeichert werden, aber Sie halten die Interna des Objekts gut gekapselt.

Jetzt können Sie eine abstrakte Archivklasse definieren, die alle Archivklassen implementieren können.

abstract class Archive
{
    public abstract object Read(string key);
    public abstract object ReadWithDefault(string key, object defaultValue);
    public abstract void Write(string key);
}

Ein FormatA-Archiv kann dies auf eine Weise tun, ein FormatB-Archiv kann es auf eine andere Weise tun.

class FormatAArchive : Archive
{
    public object Read(string key)
    {
        // read stuff 
    }

    public object ReadWithDefault(string key, object defaultValue)
    {
        // if store contains key, read stuff
        // else return default value
    }

    public void Write(string key)
    {
        // write stuff
    }
}

class FormatBArchive : Archive
{
    public object Read(string key)
    {
        // read stuff
    }

    public object ReadWithDefault(string key, object defaultValue)
    {
        // if store contains key, read stuff
        // else return default value
    }

    public void Write(string key)
    {
        // write stuff
    }
}

Sie können einen anderen Controller-Typ hinzufügen und ihm einen beliebigen Formatierer übergeben. Sie können auch ein anderes Formatierungsprogramm erstellen und es an einen beliebigen Controller übergeben.

1voto

Ruud Punkte 2976

In C# oder anderen Sprachen, die dies unterstützen, können Sie dies tun:

class TempController {
    int SetPoint;
}
class AdvancedTempController : TempController {
    int Rate;
}

class FormatAExporter {
    void Export(TempController tc) {
      Write(tc.SetPoint);
    }
}

class FormatBExporter {
    void Export(TempController tc) {
      if (tc is AdvancedTempController) {
          Write((tc as AdvancedTempController).Rate);
      }
      Write(tc.SetPoint);
    }
}

0voto

Alex Martelli Punkte 805329

Ich würde den "temp controller" über eine getState-Methode eine Map (z.B. in Python ein dict, in Javascript ein Objekt, in C++ eine std::map oder std::hashmap, etc, etc) seiner Eigenschaften und aktuellen Werte zurückgeben lassen - was ist daran kompliziert?! Es könnte kaum einfacher sein, es ist völlig erweiterbar und völlig entkoppelt von der Verwendung, für die es eingesetzt wird (Anzeige, Serialisierung, &c).

0voto

Tom Dalling Punkte 22462

Wenn FormatBExporter einen AdvancedTempController annimmt, können Sie eine Brückenklasse erstellen, die den TempController an den AdvancedTempController anpasst. Möglicherweise müssen Sie jedoch eine Art getFormat()-Funktion zu AdvancedTempController hinzufügen.

Zum Beispiel:

FormatBExporter exporterB;
TempController tempController;
AdvancedTempController bridged = TempToAdvancedTempBridge(tempController);

exporterB.export(bridged);

Es besteht auch die Möglichkeit, ein Schlüssel-Wert-Zuordnungsschema zu verwenden. FormatAExporter exportiert/importiert einen Wert für den Schlüssel "setpoint". FormatBExporter exportiert/importiert einen Wert für die Schlüssel "setpoint" und "ratecontrol". Auf diese Weise kann der alte FormatAExporter weiterhin das neue Dateiformat lesen (er ignoriert lediglich "ratecontrol"), und der FormatBExporter kann das alte Dateiformat lesen (wenn "ratecontrol" fehlt, verwendet er einen Standardwert).

0voto

kyoryu Punkte 12525

Nun, das hängt zum großen Teil von den Dateiformaten ab, über die Sie sprechen.

Wenn sie auf Schlüssel/Wert-Kombinationen basieren (einschließlich verschachtelter Kombinationen wie xml), dann ist eine Art Zwischenspeicherobjekt, das lose typisiert ist und an den entsprechenden Dateiformatschreiber übergeben werden kann, ein guter Weg, dies zu tun.

Wenn nicht, dann haben Sie ein Szenario, in dem Sie vier Kombinationen von Objekten und Dateiformaten haben, mit benutzerdefinierter Logik für jedes Szenario. In diesem Fall ist es möglicherweise nicht möglich, eine einzige Darstellung für jedes Dateiformat zu haben, die mit jedem Controller umgehen kann. Mit anderen Worten: Wenn Sie den Dateiformat-Writer nicht verallgemeinern können, können Sie ihn nicht verallgemeinern.

Mir gefällt die Idee nicht wirklich, dass die Controller Exporteure haben - ich bin einfach kein Fan von Objekten, die über Speichermechanismen und so weiter Bescheid wissen (sie können über das Konzept der Speicherung Bescheid wissen und eine bestimmte Instanz haben, die ihnen über einen DI-Mechanismus gegeben wird). Aber ich denke, Sie sind damit einverstanden, und aus ziemlich genau den gleichen Gründen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X