这种简化模式的(漂亮,可读,可扩展,高效)替代方案是什么?
<br>
更新
似乎形状会引发人们“对OOP”这个问题。在我看来,使用OOP机制进行简单的功能选择是过分的。这是另一个例子
type Shape =
{ kind: "square", a: number } |
{ kind: "rect", a: number, b: number } |
{ kind: "circle", r: number }
let area = {
"square": function (s) { return s.a * s.a; },
"rect": function (s) { return s.a * s.b; },
"circle": function (s) { return s.r * s.r * Math.PI; },
calc(s: Shape) {
return area[s.kind](s);
}
}
area.calc({ kind: "square", a: 2 });
答案 0 :(得分:1)
简单的switch
语句或if-else
级联就可以解决问题。
优势:TypeScript exhaustive checks工作。
令人惊讶:这并不比地图模式慢。在Chrome 62中使用http://jsben.ch/3SRWx进行了测试。
type Shape =
{ kind: "square", a: number } |
{ kind: "rect", a: number, b: number } |
{ kind: "circle", r: number }
function area(s: Shape): number {
switch (s.kind) {
case "square": return s.a * s.a;
case "rect": return s.a * s.b;
case "circle": return s.r * s.r * Math.PI;
}
// gives error if not all possible cases were handled above
const _exhaustiveCheck: never = s;
return 0;
}
area({ kind: "square", a: 2 });
答案 1 :(得分:0)
一种方法是使用原型链接的面向对象样式。为了清楚起见,我会坚持使用类语法。
这在技术术语上实际上是一种完全不同的方法,但提供了相同类型的界面。
我们需要的第一件事是一个基础对象,它存储所需的数据并定义所有形状的接口。在这种情况下,我们希望所有形状都可以输出它们的区域,根据它们的内部数据计算:
class Shape {
/**
* @param {object} data
*/
constructor(data) {
this.data = data;
}
/**
* Calculates the area of the shape. Should be implemented by
* all extending classes.
*
* @abstract
* @return {number|NaN}
*/
calculateArea() { return NaN; }
}
现在我们可以创建一些子类,注意所有'实现'(覆盖是技术上正确的术语)calculateArea
方法:
class Square extends Shape {
calculateArea() {
return Math.pow(this.data.a, 2);
}
}
class Rect extends Shape {
calculateArea() {
return this.data.a * this.data.b;
}
}
class Circle extends Shape {
calculateArea() {
return Math.pow(this.data.r, 2) * Math.PI;
}
}
我们现在能够像这样创建新的Shape扩展对象:
const square = new Square({ a: 1 });
const rect = new Rect({ a: 2, b: 3 });
const circle = new Circle({ r: 4 });
但是,我们仍然必须知道我们想要创建什么样的形状。为了将我们可以在给定数据中给出附加属性type
的特征与面向对象的样式相结合,我们需要一个构建器工厂。为了保持井井有条,我们将该工厂定义为Shape
的静态方法:
class Shape {
/**
* Creates a new Shape extending class if a valid type is given,
* otherwise creates and returns a new Shape base object.
*
* @param {object} data
* @param {string} data.type
* @return {Shape}
*/
static create(data) {
// It's not at all necessary to make this part anything
// more complicated than a switch-statement. If you wish,
// you could move this to a standalone factory function
// as well.
switch (data.type) {
case 'square':
return new Square(data);
case 'rect':
return new Rect(data);
case 'circle':
return new Circle(data);
default:
return new this(data);
}
}
// ...
}
我们现在有一个统一的界面来创建形状:
const square = Shape.create({ type: 'square', a: 1 });
const rect = Shape.create({ type: 'rect', a: 2, b: 3 });
const circle = Shape.create({ type: 'circle', r: 4 });
最后要做的是有一种直接计算区域的简单方法。这应该很简单:
// You might want this as global function, or as another static
// method of Shape. Whatever works for your codebase:
function calculateArea(data) {
const shape = Shape.create(data);
return shape.calculateArea();
}
// Here you have it:
calculateArea({ type: 'square', a: 4 }); // => 16
calculateArea({ type: 'rect', a: 2, b: 3 }); // => 6
calculateArea({ type: 'circle', r: 1 }); // 3.14159265359...
答案 2 :(得分:0)
你的例子很好,除了两个相对较小的问题:
正如Bergi在评论中注意到的那样,calc
与area
对象中的所有区域类型在同一范围内,因此它会阻止您拥有一个名为{{1的区域类型}}
编译器不会检查您是否拥有具有正确签名的所有可能形状种类的面积计算方法
所以我认为这可能会略有改善:
calc
答案 3 :(得分:0)
ECMAScript 2015引入了Map。
map = new Map([
[9, "nine"],
[0, "zero"],
[2, "two"],
['a', "a"],
[{}, "empty object"],
[x => x*x, "function"],
[1, "one"],
]);
for (const [key, value] of map) {
console.log(`${key} -> ${value}`);
}
所以这个例子看起来像这样。
function area(s) {
return new Map([
["square", s => s.a * s.a],
["rect", s => s.a * s.b],
["circle", s => s.r * s.r * Math.PI]
]).get(s.kind)(s);
}
area({ kind: "square", a: 2 });
问题(截至2017年):所有浏览器均为Not supported。 TypeScript不支持。