Kyle Simpson's OLOO Pattern vs Prototype Design Pattern

前端 未结 8 1596
终归单人心
终归单人心 2020-12-02 03:41

Does Kyle Simpson\'s \"OLOO (Objects Linking to Other Objects) Pattern\" differ in any way from the the Prototype design pattern? Other than coining it by something that spe

相关标签:
8条回答
  • 2020-12-02 04:36

    The discussion in "You Don't Know JS: this & Object Prototypes" and the presentation of the OLOO is thought-provoking and I have learned a ton going through the book. The merits of the OLOO pattern are well-described in the other answers; however, I have the following pet complaints with it (or am missing something that prevents me from applying it effectively):

    1

    When a "class" "inherits" another "class" in the classical pattern, the two function can be declared similar syntax ("function declaration" or "function statement"):

    function Point(x,y) {
        this.x = x;
        this.y = y;
    };
    
    function Point3D(x,y,z) {
        Point.call(this, x,y);
        this.z = z;
    };
    
    Point3D.prototype = Object.create(Point.prototype);
    

    In contrast, in the OLOO pattern, different syntactical forms used to define the base and the derived objects:

    var Point = {
        init  : function(x,y) {
            this.x = x;
            this.y = y;
        }
    };
    
    
    var Point3D = Object.create(Point);
    Point3D.init = function(x,y,z) {
        Point.init.call(this, x, y);
        this.z = z;
    };
    

    As you can see in the example above the base object can be defined using object literal notation, whereas the same notation can't be used for the derived object. This asymmetry bugs me.

    2

    In the OLOO pattern, creating an object is two steps:

    1. call Object.create
    2. call some custom, non standard method to initialize the object (which you have to remember since it may vary from one object to the next):

       var p2a = Object.create(Point);
      
       p2a.init(1,1);
      

    In contrast, in the Prototype pattern you use the standard operator new:

    var p2a = new Point(1,1);
    

    3

    In the classical pattern I can create "static" utility functions that don't apply directly to an "instant" by assigning them directly to the "class" function (as opposed to its .prototype). E.g. like function square in the below code:

    Point.square = function(x) {return x*x;};
    
    Point.prototype.length = function() {
        return Math.sqrt(Point.square(this.x)+Point.square(this.y));
    };
    

    In contrast, in the OLOO pattern any "static" functions are available (via the [[prototype]] chain) on the object instances too:

    var Point = {
        init  : function(x,y) {
            this.x = x;
            this.y = y;
        },
        square: function(x) {return x*x;},
        length: function() {return Math.sqrt(Point.square(this.x)+Point.square(this.y));}
    };
    
    0 讨论(0)
  • 2020-12-02 04:37

    @Marcus @bholben

    Perhaps we can do something like this.

        const Point = {
    
            statics(m) { if (this !== Point) { throw Error(m); }},
    
            create (x, y) {
                this.statics();
                var P = Object.create(Point);
                P.init(x, y);
                return P;
            },
    
            init(x=0, y=0) {
                this.x = x;
                this.y = y;
            }
        };
    
    
        const Point3D = {
    
            __proto__: Point,
    
            statics(m) { if (this !== Point3D) { throw Error(m); }},
    
            create (x, y, z) {
                this.statics();
                var P = Object.create(Point3D);
                P.init(x, y, z);
                return P;
            },
    
            init (x=0, y=0, z=0) {
                super.init(x, y);
                this.z = z;
            }
        }; 
    

    Of course, creating a Point3D object that links to the prototype of a Point2D object is kind of silly, but that's beside the point (I wanted to be consistent with your example). Anyways, as far as the complaints go:

    1. The asymmetry can be fixed with ES6's Object.setPrototypeOf or the more frowned upon __proto__ = ... that I use. We can also use super on regular objects now too, as seen in Point3D.init(). Another way would be to do something like

      const Point3D = Object.assign(Object.create(Point), {  
          ...  
      }   
      

      though I don't particularly like the syntax.


    1. We can always just wrap p = Object.create(Point) and then p.init() into a constructor. e.g. Point.create(x,y). Using the code above we can create a Point3D "instance" in the following manner.

      var b = Point3D.create(1,2,3);
      console.log(b);                         // { x:1, y:2, z:3 }
      console.log(Point.isPrototypeOf(b));    // true
      console.log(Point3D.isPrototypeOf(b))   // true
      

    1. I just came up with this hack to emulate static methods in OLOO. I'm not sure if I like it or not. It requires calling a special property at the top of any "static" methods. For example, I've made the Point.create() method static.

          var p = Point.create(1,2);
          var q = p.create(4,1);          // Error!  
      

    Alternatively, with ES6 Symbols you can safely extend Javascript base classes. So you could save yourself some code and define the special property on Object.prototype. For example,

        const extendedJS = {};  
    
        ( function(extension) {
    
            const statics = Symbol('static');
    
            Object.defineProperty(Object.prototype, statics, {
                writable: true,
                enumerable: false,
                configurable: true,
                value(obj, message) {
                    if (this !== obj)
                        throw Error(message);
                }
            });
    
            Object.assign(extension, {statics});
    
        })(extendedJS);
    
    
        const Point = {
            create (x, y) {
                this[extendedJS.statics](Point);
                ...
    

    0 讨论(0)
提交回复
热议问题