JavaScript OOP Course 5: Composition (Mixins)

From WikiMLT
Revision as of 09:40, 18 June 2022 by Spas (talk | contribs) (Стадий: 6 [Фаза:Утвърждаване, Статус:Утвърден]; Категория:JavaScript)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

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