Ajout d'un champ à la classe lors de l'exécution - modèle de conception

15

Imaginez que votre client souhaite avoir la possibilité d'ajouter une nouvelle propriété (par exemple, la couleur) au produit dans son eshop dans son CMS.

Au lieu d'avoir des propriétés comme champs:

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

Vous finiriez probablement par faire quelque chose comme:

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

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

C'est-à-dire, créer son propre système de types par-dessus celui existant. Il me semble que cela pourrait être considéré comme créant une langue spécifique à un domaine, ou non?

Cette approche est-elle un modèle de conception connu? Résoudriez-vous le problème différemment? Je sais qu'il existe des langues dans lesquelles je peux ajouter un champ lors de l'exécution, mais qu'en est-il de la base de données? Souhaitez-vous plutôt ajouter / modifier des colonnes ou utiliser quelque chose comme indiqué ci-dessus?

Merci pour votre temps :).

Filip
la source
Je ne sais pas quelle langue vous utilisez, mais s'il s'agit de C #, vous pouvez utiliser un type dynamique qui stocke essentiellement un KVP dans un dictionnaire un peu comme ce que vous faites sur les produits et vous permet de simplement clouer sur les propriétés sans avoir à les ajouter directement à la collection en tant que collection. Cependant, vous n'aurez pas de frappe forte. Je sais que vous avez demandé un modèle de conception, mais je ne pense pas que vous auriez besoin de quelque chose de complexe pour les utiliser. msdn.microsoft.com/en-us/magazine/gg598922.aspx
Tony
Tony: J'utilise Java ici, mais considérez-le comme un pseudo coude :). Est-ce que C # me permettrait de conserver cet objet dynamique dans la base de données? J'en doute, car la base de données doit connaître la structure des données à l'avance.
Filip
Il y a le modèle de conception d'état à la gang des quatre, qui fait qu'un objet semble changer de type ou de classe au moment de l'exécution. D'autres alternatives sont Observer Design Pattern ou Proxy Design Pattern
Nikos M.
1
Pourquoi ne pas simplement utiliser le type de données de carte? Dans DB, il peut être représenté par {id} + {id, clé, valeur}, si vous ne demandez pas de performances.
Shadows In Rain

Réponses:

4

Toutes nos félicitations! Vous venez de faire le tour du globe du système de langage / type de programmation, arrivant de l'autre côté du monde d'où vous êtes parti. Vous venez d'atterrir à la frontière du langage dynamique / terre objet basé sur des prototypes!

De nombreux langages dynamiques (par exemple JavaScript, PHP, Python) permettent d'étendre ou de modifier les propriétés des objets lors de l'exécution.

La forme extrême de ceci est un langage basé sur un prototype comme Self ou JavaScript. Ils n'ont pas de cours à proprement parler. Vous pouvez faire des choses qui ressemblent à une programmation basée sur des classes, orientée objet avec héritage, mais les règles sont considérablement assouplies par rapport à des langages basés sur des classes mieux définis comme Java et C #.

Des langages comme PHP et Python vivent au milieu. Ils ont des systèmes basés sur des classes régulières et idiomatiques. Mais les attributs d'objet peuvent être ajoutés, modifiés ou supprimés au moment de l'exécution - bien qu'avec certaines restrictions (comme «sauf pour les types intégrés») que vous ne trouvez pas en JavaScript.

Le gros compromis de ce dynamisme est la performance. Oubliez à quel point la langue est typée fortement ou faiblement, ou à quel point elle peut être compilée en code machine. Les objets dynamiques doivent être représentés comme des cartes / dictionnaires flexibles, plutôt que de simples structures. Cela ajoute une surcharge à chaque accès aux objets. Certains programmes se donnent beaucoup de mal pour réduire ce surcoût (par exemple avec une affectation de kwarg fantôme et des classes basées sur des slots en Python), mais le surcoût supplémentaire est généralement à la hauteur du cours et du prix d'admission.

Pour revenir à votre conception, vous greffez la possibilité d'avoir des propriétés dynamiques sur un sous-ensemble de vos classes. A Productpeut avoir des attributs variables; vraisemblablement un Invoiceou un Orderserait et ne pourrait pas. Ce n'est pas une mauvaise façon de procéder. Il vous donne la possibilité de varier là où vous en avez besoin, tout en restant dans un système de langage et de type strict et discipliné. En revanche, vous êtes responsable de la gestion de ces propriétés flexibles, et vous devrez probablement le faire via des mécanismes qui semblent légèrement différents des attributs plus natifs. p.prop('tensile_strength')plutôt que p.tensile_strength, par exemple, et p.set_prop('tensile_strength', 104.4)plutôt quep.tensile_strength = 104.4. Mais j'ai travaillé avec et construit de nombreux programmes en Pascal, Ada, C, Java et même des langages dynamiques qui utilisaient exactement un tel accès getter-setter pour les types d'attributs non standard; l'approche est clairement réalisable.

D'ailleurs, cette tension entre les types statiques et un monde très varié est extrêmement courante. Un problème analogue est souvent observé lors de la conception d'un schéma de base de données, en particulier pour les magasins de données relationnelles et pré-relationnelles. Parfois, il est traité en créant des "super-lignes" qui contiennent suffisamment de flexibilité pour contenir ou définir l'union de toutes les variations imaginées, puis en remplissant toutes les données qui entrent dans ces champs. Le WordPress wp_poststableau , par exemple, a des champs comme comment_count, ping_status, post_parentet post_date_gmtqui ne sont intéressants que dans certaines circonstances, et que , dans la pratique vont souvent en blanc. Une autre approche est une table normalisée très disponible comme wp_options, tout comme votrePropertyclasse. Bien qu'il nécessite une gestion plus explicite, les éléments qu'il contient sont rarement vides. Les bases de données orientées objet et documentaires (par exemple MongoDB) ont souvent plus de facilité à gérer les options changeantes, car elles peuvent créer et définir des attributs à peu près à volonté.

Jonathan Eunice
la source
0

J'aime la question, mes deux cents:

Vos deux approches sont radicalement différentes:

  • Le premier est OO et fortement typé - mais pas extensible
  • Le second est faiblement typé (la chaîne encapsule n'importe quoi)

En C ++, beaucoup utiliseraient une carte std :: map de boost :: variant pour obtenir un mélange des deux.

Disgression: Notez que certains langages, tels que C #, permettent la création dynamique de types. Ce qui pourrait être une bonne solution pour le problème général de l'ajout dynamique de membres. Cependant, "modifier / ajouter" des types après la compilation endommage le système de types lui-même et rend vos types "modifiés" presque inutiles (par exemple, comment accéderiez-vous à ces propriétés ajoutées, puisque vous ne savez même pas qu'elles existent? La seule manière raisonnable serait être une réflexion systématique sur chaque objet ... se terminant par un langage dynamique pur _ vous pouvez vous référer au mot-clé .NET 'dynamique')

quantdev
la source
La création de types à l'exécution semble intéressante mais trop exotique pour moi (je programme en Java). Une telle solution ne fonctionnerait pas, si je voulais stocker un objet dans une base de données, qui est toujours fortement typée, je crois. La solution faiblement typée que j'ai proposée peut être stockée facilement dans la base de données.
Filip
0

La création d'un type à l'exécution semble beaucoup plus compliquée que la simple création d'une couche d'abstraction. Il est très courant de créer une abstraction pour découpler des systèmes.

Permettez-moi de montrer un exemple de ma pratique. La bourse de Moscou a le noyau commercial appelé Plaza2 avec l'API du commerçant. Les traders écrivent leurs programmes pour travailler avec des données financières. Le problème est que ces données sont très énormes, complexes et très exposées aux changements. Il peut changer après l'introduction d'un nouveau produit financier ou la modification de grilles de compensation. La nature des changements futurs n'est pas prévisible. Littéralement, cela peut changer tous les jours et les mauvais programmeurs devraient modifier le code et publier une nouvelle version, et les traders en colère devraient réviser leurs systèmes.

La décision évidente est de cacher tout l'enfer financier derrière une abstraction. Ils ont utilisé l'abstraction de tables SQL bien connue. La plus grande partie de leur cœur peut fonctionner avec n'importe quel schéma valide, tout comme le logiciel du commerçant peut analyser dynamiquement le schéma et savoir s'il est compatible avec leur système.

Pour revenir à votre exemple, il est normal de créer un langage abstrait devant une logique. Il peut s'agir de «propriété», de «table», de «message» ou même de langage humain, mais faites attention aux inconvénients de cette approche:

  • Plus de code pour l'analyse et la validation (et plus de temps libre). Tout ce que le compilateur a fait pour vous avec la frappe statique, vous devez le faire pendant l'exécution.
  • Plus de documentation sur les messages, les tables ou autres primitives. Toute la complexité va du code à une sorte de schéma ou de standart. Voici un exemple de schéma d'enfer financier mentionné ci-dessus: http://ftp.moex.com/pub/FORTS/Plaza2/p2gate_en.pdf (des dizaines de pages de tableaux)
astef
la source
0

Cette approche est-elle un modèle de conception connu?

En XML et HTML, ce seraient les attributs d'un nœud / élément. Je les ai également entendus appelés propriétés étendues, paires nom / valeur et paramètres.

Résoudriez-vous le problème différemment?

C'est ainsi que je résoudrais le problème, oui.

Je sais qu'il existe des langues dans lesquelles je peux ajouter un champ lors de l'exécution, mais qu'en est-il de la base de données?

Une base de données serait comme Java, dans certains sens. En pesudo-sql:

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

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

Cela correspondrait à Java

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

Notez qu'il n'y a pas besoin d'une classe Property, car la carte stocke le nom comme clé.

Souhaitez-vous plutôt ajouter / modifier des colonnes ou utiliser quelque chose comme indiqué ci-dessus?

J'ai essayé la colonne add / alter et c'était un cauchemar. Cela peut être fait, mais les choses se désynchronisaient et je n'ai jamais réussi à bien fonctionner. La structure de tableau que j'ai décrite ci-dessus a été beaucoup plus efficace. Si vous avez besoin de faire des recherches et par ordre de, envisagez d' utiliser une table d' attributs pour chaque type de données ( date_attributes, currency_attributes, etc.) ou en ajoutant quelques - unes des propriétés que les bonnes vieilles colonnes de base de données dans la table des produits. Les rapports sont souvent beaucoup plus faciles à écrire sur les colonnes de la base de données que sur les sous-tables.

Guy Schalnat
la source