所以我看到了一些改进代码的方法。
在我看来,Union Types 应该只在两种类型是不同类型时使用。例如,CSS 允许某些属性的值是字符串或数字。那么你如何向你的函数的使用者传达你只希望他们通过这两个中的一个呢?这是联合类型的一个很好的例子:
var element: HtmlElement;Z
function bad(width: any) {
element.style.width = width;
}
// no typescript error about the wrong type being passed in
bad(new Date());
type widthType = string | number | null;
function good(width: widthType) {
element.style.width = widthType
}
// typescript error about the wrong type being passed in
good(new Date());
Typescript Playground Example.
虽然许多人决定使用kind 属性,但我尽可能避免使用它,因为它是Magic String。如果两种类型兼容,则必须有人知道您的广场的相互作用才能在那里建立自己的广场(yikes)。从技术上讲,您可以通过迁移到抽象类来避免这种情况:
abstract class Square {
static kind = 'square'
}
但是你可以只使用instanceOf,所以没有真正的意义。
但是,在面向对象编程中,我们需要注意继承(is-a)和组合(has-a)。由于 Rectangle is-a 形状和 Square is-a 形状,那么我们应该这样建模我们的对象:
interface Shape { }
interface Rectangle : Shape { }
interface Square : Shape { }
现在我们对模型有了一个很好的关注点,我们需要看看这个方法。什么是区域?一个区域is the quantity that expresses the extend of a two dimensional figure or shape。所以我们现在应该修改我们的继承链/树/任何需要这个功能的东西:
interface Shape {
areas(shape: Shape): number;
}
interface Rectangle : Shape { }
interface Square : Shape { }
我们encapsulate 在形状级别使用该方法,因为所有形状(假设为 2D 或更大)都有一个区域(0 仍然是一个大小)。
很容易回顾这个和事情,为什么形状应该做这个计算,我只是建议许多框架(如果不是大多数 OOP 框架)做这个确切的事情。当您通过Equals 比较.Net 中的两个对象时,您应该始终测试类型是否相同。但请注意,该方法位于对象的根目录,而不是断开/全局方法。
所以这可能是改进的一个好结果:
interface Shape {
// null would indicate we can't join the two
// I prefer null to indicate a known invalid value
// and only use undefined to indicate an unknown (always invalid) value
areas(shape: Shape): number | null;
}
interface Rectangle : Shape { }
interface Square : Shape { }
class MyRectangle : Rectangle {
width: number;
height: number;
area(shape: Shape){
if (!(shape instanceOf Rectangle)) {
return null;
}
return this.height * this.width + shape.height * shape.width;
}
}
class MySquare : Square {
size: number;
area(shape: Shape){
if (!(shape instanceOf Square)) {
return null;
}
return this.size * this.size + shape.size * shape.size;
}
}
// Example call:
const mySquare = new MySquare();
const mySquare2 = new MySquare();
const areas = mySquare2.area(mySquare); // fully type checked.
如果接口是一个单独的库而不是类,那么前面的例子很好,有人可能实际上想要以不同的方式表达这些值。如果不是这种情况,并且应该只有 1 种正方形和 1 种矩形,那么接口不是最佳选择,我建议改用类。因为在前面的示例中实现 Circle 将非常困难(接口和类都需要更改)。使用类看起来像:
abstract class Shape {
area(shape: Shape);
}
class Rectangle : Shape {
width: number;
height: number;
area(shape: Shape){
if (!(shape instanceOf Rectangle)) {
return null;
}
return this.height * this.width + shape.height * shape.width;
}
}
class Square: Shape {
size: number;
area(shape: Shape){
if (!(shape instanceOf Square)) {
return null;
}
return this.size * this.size + shape.size * shape.size;
}
}
现在实现 Circle 变得微不足道了。
class Circle: Shape {
radius: number;
area(shape: Shape){
if (!(shape instanceOf Circle)) {
return null;
}
return this.size * this.size + shape.size * shape.size;
}
}