JavaScript OOP Course 5: Composition (Mixins): Difference between revisions

From WikiMLT
m (Стадий: 6 [Фаза:Утвърждаване, Статус:Утвърден]; Категория:JavaScript)
 
m (Text replacement - "mlw-continue" to "code-continue")
 
Line 8: Line 8:
'''With the Composition technique we can compose few objects together to create a new object.''' The Composition technique has a great flexibility. Here is how it works.  
'''With the Composition technique we can compose few objects together to create a new object.''' The Composition technique has a great flexibility. Here is how it works.  


1. Let's define few objects that contains the methods (features) we need to mix-in at our application objects. <syntaxhighlight lang="javascript" class="mlw-continue">
1. Let's define few objects that contains the methods (features) we need to mix-in at our application objects. <syntaxhighlight lang="javascript" class="code-continue">
const canEat = {
const canEat = {
   eat: function() {
   eat: function() {
Line 24: Line 24:
</syntaxhighlight>
</syntaxhighlight>
2. Now we can use these objects together to create a person that can <code>.eat()</code> and <code>.walk()</code>. In ES6 we can use the <code>Object.assign()</code> method. As the first argument to that method we must pass an object, where the members (properties and methods) of the rest objects (passed as the rest arguments) will be copied.  
2. Now we can use these objects together to create a person that can <code>.eat()</code> and <code>.walk()</code>. In ES6 we can use the <code>Object.assign()</code> method. As the first argument to that method we must pass an object, where the members (properties and methods) of the rest objects (passed as the rest arguments) will be copied.  
<syntaxhighlight lang="javascript" class="mlw-continue">
<syntaxhighlight lang="javascript" class="code-continue">
const person = Object.assign({hunger: null}, canEat, canWalk);
const person = Object.assign({hunger: null}, canEat, canWalk);
</syntaxhighlight>
</syntaxhighlight>
<syntaxhighlight lang="javascript" class="mlw-continue">
<syntaxhighlight lang="javascript" class="code-continue">
person;
person;
</syntaxhighlight>
</syntaxhighlight>
<syntaxhighlight lang="shell-session" class="mlw-continue">
<syntaxhighlight lang="shell-session" class="code-continue">
{hunger: null, eat: ƒ, walk: ƒ}
{hunger: null, eat: ƒ, walk: ƒ}
     hunger: null
     hunger: null
Line 37: Line 37:
     [[Prototype]]: Object
     [[Prototype]]: Object
</syntaxhighlight>
</syntaxhighlight>
3. Create a constructor function by using this technique. Here we need to pass  <code>this</code> of the newly created objects to the <code>Object.assign()</code> method.<syntaxhighlight lang="javascript" class="mlw-continue">
3. Create a constructor function by using this technique. Here we need to pass  <code>this</code> of the newly created objects to the <code>Object.assign()</code> method.<syntaxhighlight lang="javascript" class="code-continue">
function Person() {
function Person() {
     this.hunger = null;
     this.hunger = null;
Line 43: Line 43:
}
}
</syntaxhighlight>
</syntaxhighlight>
<syntaxhighlight lang="javascript" class="mlw-continue">
<syntaxhighlight lang="javascript" class="code-continue">
const person1 = new Person();
const person1 = new Person();
person1;
person1;
</syntaxhighlight>
</syntaxhighlight>
<syntaxhighlight lang="shell-session" class="mlw-continue">
<syntaxhighlight lang="shell-session" class="code-continue">
Person {hunger: null, eat: ƒ, walk: ƒ}
Person {hunger: null, eat: ƒ, walk: ƒ}
     hunger: null
     hunger: null
Line 56: Line 56:


Or we can assign the methods <code>.eat()</code> and <code>.walk()</code> at the Prototype level of our constructor function, which is better because will reduce the resources used by our program - the methods will be available at the prototype level and not be replicated at each single instance of the object.
Or we can assign the methods <code>.eat()</code> and <code>.walk()</code> at the Prototype level of our constructor function, which is better because will reduce the resources used by our program - the methods will be available at the prototype level and not be replicated at each single instance of the object.
<syntaxhighlight lang="javascript" class="mlw-continue">
<syntaxhighlight lang="javascript" class="code-continue">
function Person() {
function Person() {
     this.hunger = null;
     this.hunger = null;
Line 62: Line 62:
Object.assign(Person.prototype, canEat, canWalk);
Object.assign(Person.prototype, canEat, canWalk);
</syntaxhighlight>
</syntaxhighlight>
<syntaxhighlight lang="javascript" class="mlw-continue">
<syntaxhighlight lang="javascript" class="code-continue">
const person2 = new Person();
const person2 = new Person();
person2;
person2;
</syntaxhighlight>
</syntaxhighlight>
<syntaxhighlight lang="shell-session" class="mlw-continue">
<syntaxhighlight lang="shell-session" class="code-continue">
Person {hunger: null}
Person {hunger: null}
     hunger: null
     hunger: null
Line 75: Line 75:
         [[Prototype]]: Object
         [[Prototype]]: Object
</syntaxhighlight>
</syntaxhighlight>
4. Let's imagine tomorrow we need to add two new objects to our application - Goldfish and Dog, and both of them should have capability to swim.<syntaxhighlight lang="javascript" class="mlw-continue">
4. Let's imagine tomorrow we need to add two new objects to our application - Goldfish and Dog, and both of them should have capability to swim.<syntaxhighlight lang="javascript" class="code-continue">
const canSwim = {
const canSwim = {
   swim: function() {
   swim: function() {
Line 85: Line 85:
</syntaxhighlight>
</syntaxhighlight>
Then we can define new constructors.
Then we can define new constructors.
<syntaxhighlight lang="javascript" class="mlw-continue">
<syntaxhighlight lang="javascript" class="code-continue">
function Dog() {
function Dog() {
     this.hunger = null;
     this.hunger = null;
Line 91: Line 91:
Object.assign(Dog.prototype, canEat, canWalk, canSwim);
Object.assign(Dog.prototype, canEat, canWalk, canSwim);
</syntaxhighlight>
</syntaxhighlight>
<syntaxhighlight lang="javascript" class="mlw-continue">
<syntaxhighlight lang="javascript" class="code-continue">
const dog1 = new Dog();
const dog1 = new Dog();
dog1;
dog1;
Line 106: Line 106:
</syntaxhighlight>
</syntaxhighlight>


<syntaxhighlight lang="javascript" class="mlw-continue">
<syntaxhighlight lang="javascript" class="code-continue">
function Goldfish() {
function Goldfish() {
     this.hunger = null;
     this.hunger = null;
Line 112: Line 112:
Object.assign(Goldfish.prototype, canEat, canSwim);
Object.assign(Goldfish.prototype, canEat, canSwim);
</syntaxhighlight>
</syntaxhighlight>
<syntaxhighlight lang="javascript" class="mlw-continue">
<syntaxhighlight lang="javascript" class="code-continue">
const goldfish1 = new Goldfish();
const goldfish1 = new Goldfish();
goldfish1;
goldfish1;
Line 126: Line 126:
</syntaxhighlight>
</syntaxhighlight>


5. In order to make our code more readable we can extract the logic related to <code>Object.assign()</code> in a function that use the ''rest'' and ''spread'' operator and is called <code>mixin(target, ...sources)</code>.<syntaxhighlight lang="javascript" class="mlw-continue">
5. In order to make our code more readable we can extract the logic related to <code>Object.assign()</code> in a function that use the ''rest'' and ''spread'' operator and is called <code>mixin(target, ...sources)</code>.<syntaxhighlight lang="javascript" class="code-continue">
function mixin(targetConstructor, ...sources) {                // here '...' is the rest operator
function mixin(targetConstructor, ...sources) {                // here '...' is the rest operator
     Object.assign(targetConstructor.prototype, ...sources);    // here '...' is the spread operator
     Object.assign(targetConstructor.prototype, ...sources);    // here '...' is the spread operator

Latest revision as of 08:29, 26 September 2022

Ref­er­ences

Com­po­si­tion Tech­nique

With the Com­po­si­tion tech­nique we can com­pose few ob­jects to­geth­er to cre­ate a new ob­ject. The Com­po­si­tion tech­nique has a great flex­i­bil­i­ty. Here is how it works.

1. Let's de­fine few ob­jects that con­tains the meth­ods (fea­tures) we need to mix-in at our ap­pli­ca­tion ob­jects.

const canEat = {
  eat: function() {
      this.hunger--;
      console.log('eating');
  }  
};

const canWalk = {
  walk: function() {
      this.hunger++;
      console.log('walking');
  }  
};

2. Now we can use these ob­jects to­geth­er to cre­ate a per­son that can .eat() and .walk(). In ES6 we can use the Object.assign() method. As the first ar­gu­ment to that method we must pass an ob­ject, where the mem­bers (prop­er­ties and meth­ods) of the rest ob­jects (passed as the rest ar­gu­ments) will be copied.

const person = Object.assign({hunger: null}, canEat, canWalk);
person;
{hunger: null, eat: ƒ, walk: ƒ}
    hunger: null
    eat: ƒ ()
    walk: ƒ ()
    [[Prototype]]: Object

3. Cre­ate a con­struc­tor func­tion by us­ing this tech­nique. Here we need to pass this of the new­ly cre­at­ed ob­jects to the Object.assign() method.

function Person() {
    this.hunger = null;
    Object.assign(this, canEat, canWalk);
}
const person1 = new Person();
person1;
Person {hunger: null, eat: ƒ, walk: ƒ}
    hunger: null
    eat: ƒ ()
    walk: ƒ ()
    [[Prototype]]: Object

Or we can as­sign the meth­ods .eat() and .walk() at the Pro­to­type lev­el of our con­struc­tor func­tion, which is bet­ter be­cause will re­duce the re­sources used by our pro­gram – the meth­ods will be avail­able at the pro­to­type lev­el and not be repli­cat­ed at each sin­gle in­stance of the ob­ject.

function Person() {
    this.hunger = null;
}
Object.assign(Person.prototype, canEat, canWalk);
const person2 = new Person();
person2;
Person {hunger: null}
    hunger: null
    [[Prototype]]: Object
        eat: ƒ ()
        walk: ƒ ()
        constructor: ƒ Person()
        [[Prototype]]: Object

4. Let's imag­ine to­mor­row we need to add two new ob­jects to our ap­pli­ca­tion – Gold­fish and Dog, and both of them should have ca­pa­bil­i­ty to swim.

const canSwim = {
  swim: function() {
      this.hunger++;
      console.log('swi');
  }  
};

Then we can de­fine new con­struc­tors.

function Dog() {
    this.hunger = null;
}
Object.assign(Dog.prototype, canEat, canWalk, canSwim);
const dog1 = new Dog();
dog1;
Dog {hunger: null}
    hunger: null
    [[Prototype]]: Object
        eat: ƒ ()
        swim: ƒ ()
        walk: ƒ ()
        constructor: ƒ Dog()
        [[Prototype]]: Object
function Goldfish() {
    this.hunger = null;
}
Object.assign(Goldfish.prototype, canEat, canSwim);
const goldfish1 = new Goldfish();
goldfish1;
Goldfish {hunger: null}
    hunger: null
    [[Prototype]]: Object
        eat: ƒ ()
        swim: ƒ ()
        constructor: ƒ Goldfish()
        [[Prototype]]: Object

5. In or­der to make our code more read­able we can ex­tract the log­ic re­lat­ed to Object.assign() in a func­tion that use the rest and spread op­er­a­tor and is called mixin(target, …sources).

function mixin(targetConstructor, ...sources) {                 // here '...' is the rest operator
    Object.assign(targetConstructor.prototype, ...sources);     // here '...' is the spread operator
}

Now we can use the mixin(target, …sources) func­tion to sim­pli­fy our code in the fol­low­ing way.

function Dog() { this.hunger = null; }
mixin(Dog, canEat, canWalk, canSwim);
function Goldfish() { this.hunger = null; }
mixin(Goldfish, canEat, canSwim);