假设我们有一个类,它有一个表示形状的字段,形状可以是圆形、矩形(或者更多其他的类型)。我们希望在属性检查器配置这个字段的时候,可以选择一种。比如选择圆,就展示它的半径;选择矩形就能配置它的宽和高。
我们可以怎么做呢?
class Shape {}
class Circle extends Shape { radius = 0.0; } // 圆形
class Rectangle extends Shape { width = 0.0; height = 0.0; } // 矩形
class Demo { shape: Shape = new Circle(); /* 表示形状的字段 */ }
一种方法是创建一个枚举字段表示形状类型,然后在这个类型字段的访问器里做一些事:
enum ShapeType { Circle, Rectangle }
class Demo {
@property // 在属性检查器中显示这个字段
get shapeType() { return this._shapeType; }
set shapeType(value) {
// 切换类型时,重新创建 `this.shape`
if (this._shapeType !== value) {
this._shapeType = value;
if (value === ShapeType.Circle) {
this.shape = new Circle();
} else /* ... */ {
/* ... */
}
}
}
private _shapeType = ShapeType.Circle;
}
这是目前解决此类问题广泛使用的方法。
这个方法有一个缺陷,就是它引入了一个额外的 this._shapeType 字段。我们可以改良一下这个实现,以免去 _shapeType 的存储——用 this.shape 来推导当前的形状类型:
class Demo {
@property
get shapeType() {
// 根据 `this.shape` 推断 `ShapeType`
if (this.shape.constructor === Circle) { return ShapeType.Circle; }
else /* ... */ { /* ... */ }
}
set shapeType(value) {
if (this.shapeType !== value) {
if (value === ShapeType.Circle) {
this.shape = new Circle();
} else /* ... */ {
/* ... */
}
}
}
}
改良后的方法已经能解决我们的问题了。不过,当可选择的类型变多时,这里就要写很多的 if - else 分支,很麻烦。有鉴于此,我们可以将这个“生成类型”的功能做成一个装饰器,然后开箱即用。
我先给出使用方法和效果,然后在文章的最后给出实现代码。
使用方法:
@property // 这个 @property 和平常的装饰器一样,而且不是必须;它控制 shape 本身的展示。
@polymorphism({ // 为当前装饰的字段生成一个类型字段。
types: [ // 允许的类型项
[Circle, '圆形'], // 圆形类以及它的展示名称
[Rectangle, '矩形'], // 矩形类以及它的展示名称
]
})
public shape: Shape = new Circle();
效果:
(录屏的时候下拉框没录下来)

实现:
PolymorphismDemo.zip (1.2 KB)
