JavaScript 构造函数模式

JavaScript constructor patterns

本文关键字:模式 构造函数 JavaScript      更新时间:2023-09-26

我正在寻找一个理智的解决方案来解决JavaScript只有一个构造函数的问题。因此,假设我们有一个类Point并且我们希望允许从坐标创建对象。

我将忽略所有这些示例中的类型检查。

function Point(x, y) {
  this.x = x;
  this.y = y;
}

容易。从其他点创建点怎么样?

function Point(x, y) {
   if (!y /* && x instanceof Point */) {
     y = x.y;
     x = x.x;
   }
   this.x = x;
   this.y = y;
}

这很快就会变成一场噩梦。所以我想要的是一种将这两个构造函数解耦(或者将一个构造函数拆分为两个(的设计模式。Objective-C对此有一个很好的模式。ObjC人一些东西创造对象。

function Point(x, y) {
  this.x = x;
  this.y = y;
}
Point.withPoint = function(point) {
  return new Point(point.x, point.y);
};

到目前为止,我非常喜欢这个。但是现在我们有两种不同的语法。

var a = new Point(4, 2);
var b = Point.withPoint(a);

好吧,这很容易,不是吗?只需添加Point.withCoordinates.但是构造函数呢?隐藏它?我不知道。我想这就是你进来的地方。


这是我决定采用的:

var Point = {
  withCoordinates: function(x, y) {
    if (typeof x == 'number' && typeof y == 'number') {
      this.x = x;
      this.y = y;
      return this;
    }
    throw TypeError('expected two numbers');
  },
  withPoint: function(point) {
    if (typeof point.x == 'number' && typeof point.y == 'number') {
      this.withCoordinates(point.x, point.y);
      return this;
    }
    throw TypeError('expected a point');
  }
};
var a = Object.create(Point).withCoordinates(0, 0);
var b = Object.create(Point).withPoint(a);

优点:

  • 无样板
  • 描述性语法/API
  • 扩展性好
  • 功能的
  • 易于测试

缺点:

  • 实例不知道它们是否已初始化
  • 不能只向类添加属性(比较Number.MAX_SAFE_INTEGER(

请注意 Point.withPoint 中的类型检查。它允许鸭子类型的点,如点击事件。

function onClick(event) {
  var position = Object.create(Point).withPoint(event);
}

另请注意,在某种默认 ctor 中缺少零初始化。点实际上是一个很好的例子,说明为什么这并不总是一个好主意。

就像在 ObjC 上一样,你可以有单独的 "alloc" 和 "init" 条目,例如:

function Point() {}
Point.prototype.withCoordinates = function(x, y) {
    this.x = x;
    this.y = y;
    return this;
}
Point.prototype.withOffsetFromPoint = function(p, delta) {
    this.x = p.x + delta;
    this.y = p.y + delta;
    return this;
}
p = new Point().withOffsetFromPoint(
    new Point().withCoordinates(5, 6),
    10);
console.log(p) // 15, 16

其中虚拟构造函数基本上是"分配"的东西。

以更现代的方式相同,没有new

Point = {
    withCoordinates: function(x, y) {
        this.x = x;
        this.y = y;
        return this;
    },
    withOffsetFromPoint: function(p, delta) {
        this.x = p.x + delta;
        this.y = p.y + delta;
        return this;
    }
}
p = Object.create(Point).withOffsetFromPoint(
    Object.create(Point).withCoordinates(5, 6),
    10);
console.log(p)

另一个(也许是最惯用的(选项是让构造函数接受命名参数(通过"选项"对象(:

p = new Point({ x:1, y:2 })
p = new Point({ point: someOtherPoint })

好吧,也许这是一种愚蠢的方法,但您可以添加一个属性来指定对象是"POINT"。然后,在构造函数上检查此属性。

这并不理想,但如果它符合您的需求......

http://jsfiddle.net/s4w2pn5a/

function Point(x, y) {
   this.type = "POINT";
   if (!y && x.type == "POINT") {
     y = x.y;
     x = x.x;
   }
   this.x = x;
   this.y = y;
}
var p1 = new Point(10, 20);
var p2 = new Point(p1);
alert(p2.x);
您可以使用

instanceof模式,但将实例变量的初始化移动到另一个函数。

function Point (point) {
    if (point instanceof Point) {
        this.init(point.x, point.y);
    } else {
        this.init.apply(this, arguments);
    }
}
Point.prototype.init = function (x, y) {
    this.x = x;
    this.y = y;
};

鸭子打字

更好的选择是使用鸭子类型模式,其中Point构造函数将始终接受鸭子类型Point

function Point (point) {
    this.init(point.x, point.y);
}
Point.prototype.init = function (x, y) {
    this.x = x;
    this.y = y;
};
var point = new Point({
    x: 1,
    y: 1
});
var point2 = new Point(point);

这提供了更易于阅读的构造函数调用,并允许Point使用者使用 x 键和y键传入任何内容。

有关鸭子打字的更多信息,请看"A Drip of JavaScript"。

实例化和配置不是类的责任。您必须添加工厂,构建器,DI容器等...来完成这项工作。我建议您阅读更多有关创建设计模式的信息。

例如:

var PointProvider = function (){};
PointProvider.prototype = {
    fromCoords: function (x,y){
        return new Point(x,y);
    },
    clonePoint: function (p){
        return new Point(p.x, p.y);
    }
};
var pointProvider = new PointProvider();
var p1 = pointProvider.fromCoords(x,y);
var p2 = pointProvider.fromPoint(p1);

您也可以使用多个资源库:

var Point = function (){
    if (arguments.length)
        this.setCoords.apply(this, arguments);
};
Point.prototype = {
    setCoords: function (x,y){
        this.x = x;
        this.y = y;
        return this;
    },
    setCoordsFromPoint: function (p){
        this.x = p.x;
        this.y = p.y;
        return this;
    }
};
var p1 = new Point(x,y);
var p2 = new Point().setCoordsFromPoint(p1);

或带有立面

var p = function (){
    var point = new Point();
    if (arguments.length == 2)
        point.setCoords.apply(point, arguments);
    else if (arguments.length == 1)
        point.setCoordsFromPoint.apply(point, arguments);
    return point;
}
var p1 = p(x,y);
var p2 = p(p1);

所以总结论点计数等属于更高的抽象层次。

顺便说一句,方法重载是其他语言的一部分,例如 java,因此您可以在那里简单地定义 2 个具有不同类型参数的构造函数,例如:

class Point {
    private int x;
    private int y;
    Point(int x, int y){
        this.x = x;
        this.y = y;
    }
    Point(Point p){
        this.x = p.x;
        this.y = p.y;
    }
}

可悲的是,这个功能不是javascript的一部分...