Кайл Симпсон Модель OLOO против шаблона проектирования прототипа

Отличается ли Кайл Симпсон "Шаблон OLOO (объекты, связывающиеся с другими объектами)" от шаблона проектирования "Прототип"? Кроме того, чтобы придумать что-то, что конкретно указывает на "связывание" (поведение прототипов) и разъяснение того, что здесь не происходит "копирование" (поведение классов), что именно представляет его паттерн?

Вот пример паттерна Кайла из его книги "Вы не знаете, JS: это и прототипы объектов":

var Foo = {
    init: function(who) {
        this.me = who;
    },
    identify: function() {
        return "I am " + this.me;
    }
};

var Bar = Object.create(Foo);

Bar.speak = function() {
    alert("Hello, " + this.identify() + ".");
};

var b1 = Object.create(Bar);
b1.init("b1");
var b2 = Object.create(Bar);
b2.init("b2");

b1.speak(); // alerts: "Hello, I am b1."
b2.speak(); // alerts: "Hello, I am b2."

Ответ 1

что именно вводит его шаблон?

OLOO охватывает цепочку прототипов как есть, без необходимости накладывать на другую (IMO запутанную) семантику, чтобы получить связь.

Итак, эти два фрагмента имеют ТОЧНО тот же результат, но поступают по-другому.

Форма конструктора:

function Foo() {}
Foo.prototype.y = 11;

function Bar() {}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.z = 31;

var x = new Bar();
x.y + x.z;  // 42

Форма OLOO:

var FooObj = { y: 11 };

var BarObj = Object.create(FooObj);
BarObj.z = 31;

var x = Object.create(BarObj);
x.y + x.z;  // 42

В обоих фрагментах объект x [[Prototype]] привязан к объекту (Bar.prototype или BarObj), который, в свою очередь, связан с третьим объектом (Foo.prototype или FooObj).

Связи и делегирование идентичны между фрагментами. Использование памяти одинаково между фрагментами. Возможность создания множества "детей" (например, много объектов, таких как x1 через x1000 и т.д.) Идентична между фрагментами. Производительность делегирования (x.y и x.z) идентична между фрагментами. Производительность создания объекта медленнее с OLOO, но проверка работоспособности, которая показывает, что более низкая производительность действительно не проблема.

То, что я утверждаю в предложениях OLOO, заключается в том, что гораздо проще просто выражать объекты и напрямую связывать их, чем косвенно связывать их с механизмами конструктора / new. Последний претендует на классы, но на самом деле это просто страшный синтаксис для выражения делегирования ( примечание стороны:, так что синтаксис ES6 class!).

OLOO просто вырезает среднего человека.

Здесь другое сравнение class против OLOO.

Ответ 2

Я прочитал книгу Кайла и нашел ее действительно информативной, особенно подробности о том, как this связан.

Плюсы:

Для меня есть пара больших профессионалов OLOO:

1. Простота

OLOO использует Object.create() для создания нового объекта, который [[prototype]] -linked для другого объекта. Вам не нужно понимать, что у функций есть свойство prototype, или беспокоиться о возможных подводных камнях, которые могут возникнуть в результате его модификации.

2. Более чистый синтаксис

Это спорно, но я чувствую, что синтаксис Oloo является (во многих случаях) аккуратней и более кратким, чем "стандартный" JavaScript подхода, особенно когда речь идет о полиморфизме (super -style вызовы).

Минусы:

Я думаю, что есть один сомнительный элемент дизайна (который на самом деле способствует пункту 2 выше), и это связано с затенением:

При делегировании поведения мы избегаем, если вообще возможно, называть вещи одинаково на разных уровнях цепочки [[Prototype]].

Идея заключается в том, что у объектов есть свои более конкретные функции, которые затем внутренне делегируются функциям вниз по цепочке. Например, у вас может быть объект resource с функцией save(), которая отправляет версию объекта в формате JSON на сервер, но у вас также может быть объект clientResource, который имеет функцию stripAndSave(), которая сначала удаляет свойства, которые не следует отправлять на сервер.

Потенциальная проблема: если кто-то другой придет и решит создать объект specialResource, не полностью осведомленный о всей цепочке прототипов, он может разумно * решить сохранить временную метку для последнего сохранения в свойстве под названием save, которая скрывает базовую функциональность save() на объекте resource двумя звеньями цепочки прототипов:

var resource = {
  save: function () { 
    console.log('Saving');
  }
};

var clientResource = Object.create(resource);

clientResource.stripAndSave = function () {
  // Do something else, then delegate
  console.log('Stripping unwanted properties');
  this.save();
};

var specialResource = Object.create( clientResource );

specialResource.timeStampedSave = function () {
  // Set the timestamp of the last save
  this.save = Date.now();
  this.stripAndSave();
};

a = Object.create(clientResource);
b = Object.create(specialResource);

a.stripAndSave();    // "Stripping unwanted properties" & "Saving".
b.timeStampedSave(); // Error!

Это особенно надуманный пример, но суть в том, что отсутствие скрытия других свойств может привести к неловким ситуациям и интенсивному использованию тезауруса!

Возможно, лучшей иллюстрацией этого был бы метод init - особенно острый, поскольку функции типа конструктора OOLO обходят стороной. Поскольку каждому связанному объекту, скорее всего, понадобится такая функция, может быть утомительным занятие назвать их соответствующим образом, а уникальность может затруднить запоминание того, что использовать.

* На самом деле это не особенно разумно (lastSaved было бы намного лучше, но это всего лишь пример.)

Ответ 3

Обсуждение в "You Do not Know JS: этот и прототипы объектов" и презентация OLOO вызывает размышления, и я узнал тонну, проходящую через книгу. Достоинства шаблона OLOO хорошо описаны в других ответах; однако у меня есть следующие жалобы на пищу с ним (или я пропустил что-то, что мешает мне применить его эффективно):

1

Когда "класс" "наследует" другой "класс" в классическом шаблоне, две функции могут быть объявлены аналогичным синтаксисом ( "объявление функции" или "инструкция функции" ):

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);

Напротив, в шаблоне OLOO разные синтаксические формы, используемые для определения базы и производных объектов:

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;
};

Как вы можете видеть в приведенном выше примере, базовый объект может быть определен с использованием объектной нотации, тогда как для производного объекта нельзя использовать одну и ту же нотацию. Эта асимметрия меня беспокоит.

2

В шаблоне OLOO создание объекта выполняется в два этапа:

  • вызов Object.create
  • вызвать некоторый настраиваемый, нестандартный метод для инициализации объекта (который вы должны запомнить, поскольку он может варьироваться от одного объекта к другому):

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

Напротив, в шаблоне Prototype вы используете стандартный оператор new:

var p2a = new Point(1,1);

3

В классическом шаблоне я могу создать "статические" функции полезности, которые не применяются непосредственно к "мгновенному", назначая их непосредственно функции "класса" (в отличие от ее .prototype). Например. как функция square в приведенном ниже коде:

Point.square = function(x) {return x*x;};

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

Напротив, в шаблоне OLOO доступны любые "статические" функции (через цепочку [[prototype]]):

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));}
};

Ответ 4

"Я решил сделать это, чтобы каждый объект зависел от другого"

Как объясняет Кайл, когда два объекта связаны [[Prototype]], они на самом деле не являются зависимые друг от друга; вместо этого они являются индивидуальным объектом. Вы связываете один объект к другому с помощью ссылки [[Prototype]], которую вы можете изменить в любое время. Если вы берете два [[Prototype]] связанных объектов, созданных в стиле OLOO, как зависящие друг от друга, вы также должны думать о тех, которые созданы через вызовы constructor.

var foo= {},
    bar= Object.create(foo),
    baz= Object.create(bar);


console.log(Object.getPrototypeOf(foo)) //Object.prototype

console.log(Object.getPrototypeOf(bar)) //foo

console.log(Object.getPrototypeOf(baz)) //bar

Теперь подумайте, считаете ли вы, что foo bar и baz зависят друг от друга?

Теперь сделаем тот же код стиля constructor -

function Foo() {}

function Bar() {}

function Baz() {}

Bar.prototype= Object.create(Foo);
Baz.prototype= Object.create(Bar);

var foo= new Foo(),
    bar= new Bar().
    baz= new Baz();

console.log(Object.getPrototypeOf(foo)) //Foo.prototype
console.log(Object.getPrototypeOf(Foo.prototype)) //Object.prototype

console.log(Object.getPrototypeOf(bar)) //Bar.prototype
console.log(Object.getPrototypeOf(Bar.prototype)) //Foo.prototype

console.log(Object.getPrototypeOf(baz)) //Baz.prototype
console.log(Object.getPrototypeOf(Baz.prototype)) //Bar.prototype

Единственное различие b/w последнего и прежнего кода заключается в том, что в последнем foo, bar, baz bbjects связаны друг с другом через произвольные объекты их функции constructor (Foo.prototype, Bar.prototype, Baz.prototype), но в первом (стиль OLOO) они напрямую связаны. Оба способа вы просто связываете foo, bar, baz друг с другом, прямо в первом и косвенно в последнем. Но в обоих случаях объекты не зависят друг от друга, потому что это не похоже на экземпляр любого класса, который после создания экземпляра не может быть наследован от какого-либо другого класса. Вы всегда можете изменить, какой объект должен делегировать объект.

var anotherObj= {};
Object.setPrototypeOf(foo, anotherObj);

Поэтому они все независимы друг от друга.

"Я надеялся, что OLOO решит проблему, в которой каждый объект знает ничего о другом."

Да, что действительно возможно -

Позвольте использовать Tech в качестве объекта утилиты -

 var Tech= {
     tag: "technology",
     setName= function(name) {
              this.name= name;
}
}

создать столько объектов, которые вы хотите связать с Tech -

var html= Object.create(Tech),
     css= Object.create(Tech),
     js= Object.create(Tech);

Some checking (avoiding console.log)- 

    html.isPrototypeOf(css); //false
    html.isPrototypeOf(js); //false

    css.isPrototypeOf(html); //false
    css.isPrototypeOf(js); //false

    js.isPrototypeOf(html); //false
    js.isPrototypwOf(css); //false

    Tech.isPrototypeOf(html); //true
    Tech.isPrototypeOf(css); //true
    Tech.isPrototypeOf(js); //true

Считаете ли вы, что объекты html, css, js связаны друг с другом? Нет, это не так. Теперь посмотрим, как мы могли это сделать с помощью функции constructor -

function Tech() { }

Tech.prototype.tag= "technology";

Tech.prototype.setName=  function(name) {
              this.name= name;
}

создайте столько объектов, которые вы хотите связать с Tech.proptotype -

var html= new Tech(),
     css= new Tech(),
      js= new Tech();

Некоторая проверка (исключая console.log) -

html.isPrototypeOf(css); //false
html.isPrototypeOf(js); //false

css.isPrototypeOf(html); //false
css.isPrototypeOf(js); //false

js.isPrototypeOf(html); //false
js.isPrototypeOf(css); //false

Tech.prototype.isPrototypeOf(html); //true
Tech.prototype.isPrototypeOf(css); //true
Tech.prototype.isPrototypeOf(js); //true

Как вы думаете, эти constructor -стильные объекты (html, css, js) Объекты отличаются от OLOO -стильного кода? На самом деле они служат той же цели. В OLOO -стройте один объект делегировать на Tech (делегирование было задано явно), а в constructor - создать один объект делегировать на Tech.prototype (делегирование было задано неявно). В конечном итоге вы оказываете связь между тремя объектами, не связанными друг с другом, с одним объектом, напрямую используя стиль OLOO, косвенно используя стиль constructor.

"Как есть, ObjB должен быть создан из ObjA.. Object.create(ObjB) и т.д.

Нет, ObjB здесь не похоже на экземпляр (в классических языках) любого класса ObjA. Можно сказать, что как ObjB объект передается объекту ObjA при его создании time ". Если вы использовали конструктор, вы сделали бы ту же" связь ", хотя косвенно, используя .prototype s.

Ответ 5

@Marcus @bholben

Возможно, мы сможем сделать что-то вроде этого.

    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;
        }
    }; 

Конечно, создание объекта Point3D, связанного с прототипом объекта Point2D, выглядит глупым, но это не так (я хотел быть в соответствии с вашим примером). В любом случае, если жалобы идут:

  • Асимметрию можно исправить с помощью ES6 Object.setPrototypeOf или тем больше я нахожусь на __proto__ = .... Мы также можем использовать super для обычных объектов, как показано в Point3D.init(). Другой способ - сделать что-то вроде

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

    хотя мне не особенно нравится синтаксис.


  1. Мы всегда можем просто заключить p = Object.create(Point), а затем p.init() в конструктор. например Point.create(x,y). Используя вышеприведенный код, мы можем создать экземпляр Point3D следующим образом.

    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. Я только что придумал этот хак для эмуляции статических методов в OLOO. Я не уверен, нравится мне это или нет. Это требует вызова специального свойства в верхней части любых "статических" методов. Например, я сделал статический метод Point.create().

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

В качестве альтернативы, с ES6 Символы вы можете безопасно расширять базовые классы Javascript. Таким образом, вы можете сэкономить код и определить специальное свойство Object.prototype. Например,

    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);
            ...

Ответ 6

@james emanon - Итак, вы имеете в виду множественное наследование (см. стр. 75 в книге "You Do not Know JS: этот и прототипы объектов" ). И этот механизм мы можем найти, например, в функции подчеркивания "растянуть". Имена объекта, который вы указали в своем примере, - это немного смешающие яблоки, апельсины и конфеты, но я понимаю суть этого. По моему опыту это была бы версия OOLO:

var ObjA = {
  setA: function(a) {
    this.a = a;
  },
  outputA: function() {
    console.log("Invoking outputA - A: ", this.a);
  }
};

// 'ObjB' links/delegates to 'ObjA'
var ObjB = Object.create( ObjA );

ObjB.setB = function(b) {
   this.b = b;
}

ObjB.setA_B = function(a, b) {
    this.setA( a ); // This is obvious. 'setA' is not found in 'ObjB' so by prototype chain it found in 'ObjA'
    this.setB( b );
    console.log("Invoking setA_B - A: ", this.a, " B: ", this.b);
};

// 'ObjC' links/delegates to 'ObjB'
var ObjC = Object.create( ObjB );

ObjC.setC = function(c) {
    this.c = c;  
};

ObjC.setA_C = function(a, c) {
    this.setA( a ); // Invoking 'setA' that is clearly not in ObjC shows that prototype chaining goes through ObjB all the way to the ObjA
    this.setC( c );
    console.log("Invoking setA_C - A: ", this.a, " C: ", this.c);
};

ObjC.setA_B_C = function(a, b, c){
    this.setA( a ); // Invoking 'setA' that is clearly not in ObjC nor ObjB shows that prototype chaining got all the way to the ObjA
    this.setB( b );
    this.setC( c );
    console.log("Invoking setA_B_C - A: ", this.a, " B: ", this.b, " C: ", this.c);
};

ObjA.setA("A1");
ObjA.outputA(); // Invoking outputA - A:  A1

ObjB.setA_B("A2", "B1"); // Invoking setA_B - A:  A2  B:  B1

ObjC.setA_C("A3", "C1"); // Invoking setA_C - A:  A3  C:  C1
ObjC.setA_B_C("A4", "B2", "C1"); // Invoking setA_B_C - A:  A4  B:  B2  C:  C1

Это простой пример, но указанная точка состоит в том, что мы просто объединяем объект вместе в довольно плоскую структуру/формирование и все еще имеем возможность использовать методы и свойства из нескольких объектов. Мы достигаем того же, что и с классом/ "копированием свойств". Суммируется Кайлом (стр. 114, "Этот и прототипы объектов" ):

Иными словами, реальный механизм, суть важного к функциональности, которую мы можем использовать в JavaScript, все об объектах связанные с другими объектами.

Я понимаю, что более естественным способом для вас было бы указать все "родительские" (осторожные:)) объекты в одном месте/функции, а скорее моделировать целую цепочку.

Для этого требуется сдвиг в мышлении и проблемах моделирования в наших приложениях в соответствии с этим. Я тоже привык к этому. Надеюсь, что это поможет, и окончательный вердикт от самого Кайла будет большим.:)

Ответ 7

@Marcus, как и вы, я увлекался OLOO, а также не любил асимметрию, как описано в вашем первом пункте. Я играл с абстракцией, чтобы вернуть симметрию. Вы можете создать функцию link(), которая используется вместо Object.create(). При использовании ваш код может выглядеть примерно так:

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


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

Помните, что Object.create() имеет второй параметр, который может быть передан. Вот функция ссылок, которая использует второй параметр. Он также позволяет немного настраивать конфигурацию...

function link(delegate, props, propsConfig) {
  props = props || {};
  propsConfig = propsConfig || {};

  var obj = {};
  Object.keys(props).forEach(function (key) {
    obj[key] = {
      value: props[key],
      enumerable: propsConfig.isEnumerable || true,
      writable: propsConfig.isWritable || true,
      configurable: propsConfig.isConfigurable || true
    };
  });

  return Object.create(delegate, obj);
}

Конечно, я думаю, что @Kyle не одобряет затенение функции init() в объекте Point3D.; -)

Ответ 8

Есть ли способ OLO больше, чем "два" объекта. все примеры я состоял из основанного примера (см. пример OP). Допустим, у нас были следующие объекты, как мы можем создать "четвертый" объект с атрибутами "других" трех? ала...

var Button = {
     init: function(name, cost) {
       this.buttonName = name;
       this.buttonCost = cost;
     }
}

var Shoe = {
     speed: 100
}

var Bike = {
     range: '4 miles'
}

эти объекты являются произвольными и могут охватывать все виды поведения. Но суть в том, что у нас есть "n" количество объектов, а наш новый объект нуждается в чем-то из всех трех.

вместо приведенных примеров ala:

var newObj = Object.create(oneSingularObject);
    newObj.whatever..

НО, наш новыйОбъект = (кнопка, велосипед, башмак)......

Каков шаблон, чтобы получить это в OLOO?