Hinzufügen eines Felds zur Klasse zur Laufzeit - Entwurfsmuster

15

Stellen Sie sich vor, Ihr Kunde möchte die Möglichkeit haben, dem Produkt in seinem E-Shop in seinem CMS eine neue Eigenschaft (z. B. Farbe) hinzuzufügen.

Anstatt Eigenschaften als Felder zu haben:

class Car extends Product {
   protected String type;
   protected int seats;
}

Am Ende würden Sie wahrscheinlich Folgendes tun:

class Product {
   protected String productName;
   protected Map<String, Property> properties;
}

class Property {
   protected String name;
   protected String value;
}

Das heißt, es wird ein eigenes Typensystem über das vorhandene System erstellt. Es fühlt sich für mich so an, als könnte dies eine domänenspezifische Sprache erzeugen, oder nicht?

Ist dieser Ansatz ein bekanntes Entwurfsmuster? Würden Sie das Problem anders lösen? Ich weiß, dass es Sprachen gibt, in denen ich zur Laufzeit ein Feld hinzufügen kann, aber was ist mit der Datenbank? Möchten Sie lieber Spalten hinzufügen / ändern oder etwas wie oben gezeigt verwenden?

Vielen Dank für Ihre Zeit :).

Filip
quelle
Sie sind sich nicht sicher, welche Sprache Sie verwenden, aber wenn es sich um C # handelt, können Sie einen dynamischen Typ verwenden, der im Grunde genommen einen KVP in einem Wörterbuch speichert, wie Sie es bei Produkten tun, und es Ihnen ermöglicht, Eigenschaften einfach zu bearbeiten, ohne sie direkt hinzufügen zu müssen die sammlung als sammlung. Sie werden allerdings nicht stark tippen müssen. Ich weiß, dass Sie nach einem Entwurfsmuster gefragt haben, aber ich glaube nicht, dass Sie etwas Komplexes benötigen würden, um sie zu verwenden. msdn.microsoft.com/en-us/magazine/gg598922.aspx
Tony
Tony: Ich benutze Java hier, aber halte es für pseudo coude :). Kann ich das dynamische Objekt in C # in der Datenbank beibehalten? Ich bezweifle es, da die Datenbank die Struktur der Daten im Voraus kennen muss.
Filip
Es gibt das Zustandsentwurfsmuster a-la-Gang-of-Four, mit dem ein Objekt zur Laufzeit den Typ oder die Klasse zu ändern scheint . Andere Alternativen sind Observer Design Pattern oder ein Proxy Design Pattern
Nikos M.
1
Warum nicht einfach den Kartentyp verwenden? In der Datenbank kann es als {id} + {id, key, value} dargestellt werden, wenn Sie nicht nach Leistung fragen.
Schatten im Regen

Antworten:

4

Herzliche Glückwünsche! Sie haben gerade den Globus des Programmiersprachen- / Typensystems umrundet und sind auf der anderen Seite der Welt angekommen, von wo aus Sie abgereist sind. Sie sind gerade an der Grenze von Land mit dynamischer Sprache / Prototypen gelandet!

In vielen dynamischen Sprachen (z. B. JavaScript, PHP, Python) können Objekteigenschaften zur Laufzeit erweitert oder geändert werden.

Die extreme Form davon ist eine prototypbasierte Sprache wie Self oder JavaScript. Sie haben streng genommen keinen Unterricht. Sie können Dinge tun, die wie klassenbasierte, objektorientierte Programmierung mit Vererbung aussehen, aber die Regeln sind im Vergleich zu schärfer definierten, klassenbasierten Sprachen wie Java und C # erheblich gelockert.

Sprachen wie PHP und Python leben im Mittelfeld. Sie haben regelmäßige, idiomatische klassenbasierte Systeme. Objektattribute können jedoch zur Laufzeit hinzugefügt, geändert oder gelöscht werden - allerdings mit einigen Einschränkungen (z. B. "außer für integrierte Typen"), die Sie in JavaScript nicht finden.

Der große Kompromiss für diese Dynamik ist die Leistung. Vergessen Sie, wie stark oder schwach die Sprache getippt ist oder wie gut sie bis zum Maschinencode kompiliert werden kann. Dynamische Objekte müssen als flexible Maps / Wörterbücher und nicht als einfache Strukturen dargestellt werden. Dies erhöht den Aufwand für jeden Objektzugriff. Einige Programme sind sehr bemüht, diesen Overhead zu reduzieren (z. B. mit Phantom-Warg-Zuweisung und slotbasierten Klassen in Python), aber der zusätzliche Overhead ist in der Regel nur eine Frage des Kurses und des Eintrittspreises.

Kehren Sie zu Ihrem Entwurf zurück und übertragen Sie die Fähigkeit, dynamische Eigenschaften auf eine Teilmenge Ihrer Klassen zu übertragen. A Productkann variable Attribute haben; vermutlich ein Invoiceoder ein Orderwürde und konnte nicht. Es ist kein schlechter Weg. Es gibt Ihnen die Flexibilität, Variationen dort zu haben, wo Sie sie benötigen, und bleibt dabei in einem strengen, disziplinierten Sprach- und Typensystem. Auf der anderen Seite sind Sie für die Verwaltung dieser flexiblen Eigenschaften verantwortlich. Möglicherweise müssen Sie dazu Mechanismen verwenden, die sich geringfügig von den nativen Attributen unterscheiden. p.prop('tensile_strength')eher als p.tensile_strengthzum Beispiel und p.set_prop('tensile_strength', 104.4)eher alsp.tensile_strength = 104.4. Aber ich habe mit vielen Programmen in Pascal, Ada, C, Java und sogar in dynamischen Sprachen gearbeitet und sie erstellt, die genau diesen Get-Set-Zugriff für nicht standardmäßige Attributtypen verwendeten. Der Ansatz ist klar umsetzbar.

Im Übrigen ist diese Spannung zwischen statischen Typen und einer sehr unterschiedlichen Welt äußerst verbreitet. Ein analoges Problem tritt häufig beim Entwerfen eines Datenbankschemas auf, insbesondere für relationale und vorrelationale Datenspeicher. Manchmal geht es darum, "Super-Zeilen" zu erstellen, die genügend Flexibilität enthalten, um die Vereinigung aller vorgestellten Variationen zu enthalten oder zu definieren, und dann alle Daten, die in diese Felder gelangen, zu stopfen. Die Wordpress - wp_postsTabelle , zum Beispiel, haben Felder wie comment_count, ping_status, post_parentund post_date_gmt, die nur unter bestimmten Umständen interessant, und dass in der Praxis oft leer gehen. Ein anderer Ansatz ist ein sehr sparsamer, normalisierter Tisch wp_options, ähnlich wie IhrPropertyKlasse. Es ist zwar eine explizitere Verwaltung erforderlich, die darin enthaltenen Elemente sind jedoch selten leer. Objektorientierte Datenbanken und Dokumentendatenbanken (z. B. MongoDB) haben es oft leichter, Optionen zu ändern, da sie Attribute nach Belieben erstellen und festlegen können.

Jonathan Eunice
quelle
0

Ich mag die Frage, meine zwei Cent:

Ihre beiden Ansätze unterscheiden sich grundlegend:

  • Der erste ist OO und stark typisiert - aber nicht erweiterbar
  • Der zweite ist schwach geschrieben (String kapselt alles)

In C ++ würden viele eine std :: map von boost :: variant verwenden , um eine Mischung aus beiden zu erzielen.

Disgression: Beachten Sie, dass einige Sprachen, wie z. B. C #, die dynamische Erstellung von Typen ermöglichen. Was eine gute Lösung für das allgemeine Problem sein könnte, Mitglieder dynamisch hinzuzufügen. Das "Ändern / Hinzufügen" von Typen nach dem Kompilieren beschädigt jedoch das Typensystem selbst und macht Ihre "geänderten" Typen fast unbrauchbar (z. B. wie würden Sie auf solche hinzugefügten Eigenschaften zugreifen, da Sie nicht einmal wissen, dass sie existieren? Der einzig sinnvolle Weg wäre dies eine systematische Reflexion über jedes Objekt sein ... mit einer reinen dynamischen Sprache enden _ Sie können auf das Schlüsselwort 'dynamic' verweisen.

quantdev
quelle
Das Erstellen von Typen zur Laufzeit ist für mich interessant, aber zu exotisch (ich programmiere in Java). Eine solche Lösung würde nicht funktionieren, wenn ich ein Objekt in einer Datenbank speichern möchte, die meines Erachtens immer stark typisiert ist. Die von mir vorgeschlagene schwach typisierte Lösung kann problemlos in einer Datenbank gespeichert werden.
Filip
0

Das Erstellen eines Typs zur Laufzeit klingt viel komplizierter als das Erstellen einer Abstraktionsschicht. Es ist sehr verbreitet, eine Abstraktion zu erstellen, um Systeme zu entkoppeln.

Lassen Sie mich ein Beispiel aus meiner Praxis zeigen. Die Moskauer Börse hat den Handelskern Plaza2 mit der Händler-API. Händler schreiben ihre Programme, um mit Finanzdaten zu arbeiten. Das Problem ist, dass diese Daten sehr umfangreich, komplex und stark Änderungen ausgesetzt sind. Dies kann sich ändern, nachdem ein neues Finanzprodukt eingeführt oder die Clearing-Regeln geändert wurden. Die Art der zukünftigen Änderungen kann nicht vorhergesagt werden. Buchstäblich kann es sich jeden Tag ändern und schlechte Programmierer sollten den Code bearbeiten und eine neue Version veröffentlichen, und verärgerte Händler sollten ihre Systeme überarbeiten.

Offensichtliche Entscheidung ist, alle finanzielle Hölle hinter einer Abstraktion zu verstecken. Sie haben bekannte SQL-Tabellen-Abstraktion verwendet. Der größte Teil ihres Kerns kann mit jedem gültigen Schema arbeiten, genauso wie die Software des Händlers das Schema dynamisch analysieren und herausfinden kann, ob es mit ihrem System kompatibel ist.

Zurück zu Ihrem Beispiel: Es ist normal, eine abstrakte Sprache vor einer Logik zu erstellen. Es kann sich um "Eigenschaft", "Tabelle", "Nachricht" oder sogar um menschliche Sprache handeln. Beachten Sie jedoch die Nachteile dieses Ansatzes:

  • Mehr Code zum Parsen und Validieren (und mehr Freizeit). Alles, was der Compiler mit der statischen Eingabe für Sie getan hat, müssen Sie zur Laufzeit tun.
  • Weitere Dokumentation von Nachrichten, Tabellen oder anderen Grundelementen. Die gesamte Komplexität reicht vom Code bis zu einer Art Schema oder Standard. Hier ist ein Beispiel für das oben erwähnte finanzielle Höllenschema: http://ftp.moex.com/pub/FORTS/Plaza2/p2gate_en.pdf (Dutzende von Tabellenseiten)
astef
quelle
0

Ist dieser Ansatz ein bekanntes Entwurfsmuster?

In XML und HTML wären dies die Attribute eines Knotens / Elements. Ich habe sie auch als erweiterte Eigenschaften, Name / Wert-Paare und Parameter bezeichnet gehört.

Würden Sie das Problem anders lösen?

So würde ich das Problem lösen, ja.

Ich weiß, dass es Sprachen gibt, in denen ich zur Laufzeit ein Feld hinzufügen kann, aber was ist mit der Datenbank?

Eine Datenbank wäre in gewisser Hinsicht wie Java. In Pesudo-SQL:

TABLE products
(
    product_name VARCHAR(50),
    product_id INTEGER AUTOINCREMENT
)

TABLE attributes
(
    product_id INTEGER,
    name VARCHAR(50),
    value VARCHAR(2000)
)

Dies würde dem Java entsprechen

class Product {
   protected String productName;
   protected Map<String, String> properties;
}

Beachten Sie, dass keine Property-Klasse erforderlich ist, da die Map den Namen als Schlüssel speichert.

Möchten Sie lieber Spalten hinzufügen / ändern oder etwas wie oben gezeigt verwenden?

Ich habe versucht, die Spalte hinzuzufügen / zu ändern, und es war ein Albtraum. Es kann getan werden, aber die Dinge gerieten immer wieder aus dem Takt und ich habe es nie geschafft, gut zu funktionieren. Die oben beschriebene Tabellenstruktur war viel erfolgreicher. Wenn Sie tun müssen , um Durchsuchung und Ordnung durch die, sollte mit einem Attributtabelle für jeden Datentyp ( date_attributes, currency_attributesusw.) oder einige der Eigenschaften , wie gute alte Datenbankspalten in der Tabelle Artikel hinzufügen. Berichte lassen sich oft einfacher in Datenbankspalten als in Untertabellen schreiben.

Guy Schalnat
quelle