在运行时将字段添加到类中-设计模式


15

想象一下,您的客户希望有可能在其CMS的eshop中为产品添加新属性(例如颜色)。

而不是将属性作为字段:

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

您可能最终会做类似的事情:

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

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

也就是说,在现有系统之上创建自己的类型系统。在我看来,这可以看作是创建域特定的语言,还是不能?

这种方法是已知的设计模式吗?您会以不同的方式解决问题吗?我知道可以在运行时中添加字段的语言,但是数据库呢?您是愿意添加/更改列还是使用上图所示的内容?

感谢您的时间 :)。



不确定使用的是哪种语言,但是如果使用的是C#,则可以使用一种动态类型,该类型基本上将KVP存储在字典中,就像您在产品上所做的那样,并允许您添加属性而不必直接将其添加到集合作为一个集合。但是,您不会进行强类型输入。我知道您要求一种设计模式,但是我认为使用它们不需要任何复杂的事情。msdn.microsoft.com/zh-CN/magazine/gg598922.aspx
Tony,

Tony:我在这里使用Java,但是考虑它是伪伪的:)。C#是否可以让我将该动态对象持久保存到数据库中?我对此表示怀疑,因为数据库需要预先了解数据的结构。
Filip

有一种状态设计模式 a-la-Four-of-Four,它使一个对象在运行时看起来改变其类型。其他替代方法是观察者设计模式或代理设计模式
Nikos M.

1
为什么不只使用map数据类型?在DB中,如果您不要求性能,则可以表示为{id} + {id,键,值}。
雨中的阴影

Answers:


4

恭喜你!您刚刚绕过编程语言/类型系统的范围,从您离开的地方到世界的另一端。您刚刚落在动态语言/基于原型的对象领域的边界上!

许多动态语言(例如JavaScript,PHP,Python)允许在运行时扩展或更改对象属性。

这种形式的极端形式是一种基于原型的语言,例如Self或JavaScript。严格来讲,他们没有课程。您可以执行看起来像具有继承的基于类,面向对象的编程的操作,但是与诸如Java和C#之类的更为清晰定义的基于类的语言相比,规则大大放松了。

像PHP和Python这样的语言存在于中间地带。他们有常规的,惯用的基于类的系统。但是可以在运行时添加,更改或删除对象属性,尽管有一些限制(例如“内置类型除外”),这些限制是您在JavaScript中找不到的。

这种动力的最大折衷是性能。忘了该语言的强弱输入类型,或将其编译成机器代码的能力。动态对象必须表示为灵活的地图/词典,而不是简单的结构。这增加了每个对象访问的开销。一些程序竭尽全力来减少这种开销(例如,使用幻像kwarg分配和Python中基于插槽的类),但是额外的开销通常仅与课程和入场费用相当。

回到您的设计,您正在将具有动态属性的功能嫁接到类的子集上。阿Product可具有可变的属性; 大概是一个Invoice或一个Order将不会。这不是坏路。它使您可以灵活地在需要的地方进行更改,同时保持严格,有纪律的语言和类型系统。不利的一面是,您负责管理那些灵活的属性,您可能必须通过看起来与更多本机属性稍有不同的机制来进行管理。p.prop('tensile_strength')而不是p.tensile_strength,例如,和p.set_prop('tensile_strength', 104.4)而不是p.tensile_strength = 104.4。但是我已经使用Pascal,Ada,C,Java甚至是动态语言来开发了许多程序,并且这些程序对非标准属性类型完全使用了此类getter-setter访问方式。该方法显然是可行的。

顺便说一句,静态类型和高度变化的世界之间的这种张力非常普遍。在设计数据库架构时,尤其是对于关系和关系前数据存储,经常会遇到类似的问题。有时,可以通过创建“超级行”来解决该问题,该行应具有足够的灵活性以包含或定义所有想象的变体的并集,然后将出现在这些字段中的所有数据填充。在WordPress的wp_posts表格,例如,有一个像场comment_countping_statuspost_parentpost_date_gmt那些只有在某些情况下有趣的,而且在实践中往往变成空白。另一种方法是非常备用的标准化表格,例如wp_options,就像Property类。尽管它需要更明确的管理,但其中的项目很少为空白。面向对象和文档数据库(例如MongoDB)通常可以更轻松地处理更改选项,因为它们可以随意创建和设置属性。


0

我喜欢这个问题,我的两分钱:

您的两种方法完全不同:

  • 第一个是强类型的OO ans-但不可扩展
  • 第二个是弱类型的(字符串封装了任何东西)

在C ++中,许多人会使用boost :: variant的std :: map 来实现两者的混合。

分离: 请注意,某些语言(例如C#)允许动态创建类型。对于动态添加成员的一般问题,这可能是一个不错的解决方案。但是,编译后“修改/添加”类型确实会破坏类型系统本身,并使您的“修改”类型几乎无用(例如,由于您甚至不知道它们的存在,您将如何访问这些添加的属性?唯一合理的方法是是对每个对象的系统反映...以纯动态语言_结尾,您可以引用'dynamic'.NET关键字)


在运行时创建类型似乎很有趣,但对我来说却太过奇特(我正在用Java编程)。如果我想将对象存储在databse中,那么这种解决方案将行不通,我一直坚信它始终是强类型的。我提出的弱类型解决方案可以轻松地存储在数据库中。
Filip

0

在运行时创建类型听起来要比仅创建抽象层复杂得多。创建抽象来解耦系统非常普遍。

让我以我的实践为例。莫斯科交易所的交易核心称为Plaza2,具有交易者的API。交易员编写程序以处理财务数据。问题在于,这些数据非常庞大,复杂,并且容易受到变化的影响。在引入新的金融产品或清算的习惯改变之后,它可能会改变。未来变化的性质无法预测。从字面上看,它每天都在变化,可怜的程序员应该编辑代码并发布新版本,而愤怒的交易者应该修改他们的系统。

显而易见的决定是将所有财务地狱隐藏在抽象的背后。他们使用了众所周知的SQL表抽象。他们核心的最大部分可以使用任何有效的架构,就像交易者的软件可以动态解析该架构并确定其是否与他们的系统兼容一样。

回到您的示例,在某种逻辑前面创建抽象语言是正常的。可能是“属性”,“表”,“消息”,甚至是人类语言,但请注意此方法的缺点:

  • 更多代码用于解析和验证(还有更多时间偏离路线)。编译器使用静态类型为您执行的所有操作都必须在运行时完成。
  • 有关消息,表或其他原语的更多文档。所有复杂性都从代码到某种模式或标准。这是上述财务地狱模式的示例:http : //ftp.moex.com/pub/FORTS/Plaza2/p2gate_en.pdf(数十页表格)

0

这种方法是已知的设计模式吗?

在XML和HTML中,这些将是节点/元素的属性。我也听说过它们称为扩展属性,名称/值对和参数。

您会以不同的方式解决问题吗?

是的,这就是我要解决的问题。

我知道可以在运行时中添加字段的语言,但是数据库呢?

从某种意义上说,数据库就像Java。在pesudo-sql中:

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

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

这将对应于Java

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

请注意,由于Map将名称存储为键,因此不需要Property类。

您是愿意添加/更改列还是使用上图所示的内容?

我已经尝试过添加/更改列的事情,这是一场噩梦。可以做到,但是事情总是不同步,我从来没有做好。我上面概述的表结构更加成功。如果你需要的做的搜索和顺序,考虑使用每种数据类型(一个属性表date_attributescurrency_attributes等),或添加一些属性,如产品表好旧的数据库列。报表通常更容易写在数据库列上,而不是子表上。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.