+5

Functional inheritance - JavaScript

Inheritance - is the creation of new "classes" on the basis of existing ones.

In JavaScript can be realized in several ways, one of which - using an overlay designers.

Why inheritance?

Imagine several implementations of coffee machine.

Will we only coffee machine for easy life? Hardly ... probably still need at least a refrigerator, microwave, and possibly other machines.

In real life, these machines have the basic rules of use. For example, the large button - switch, power cord with plug socket should be in the food, etc.

It can be said that "all the machines have the common properties, and specific machines can complement them."

That is why, seeing the new technique, we can do something to do with it, even without reading the instructions.

Inheritance mechanism allows you to define a base class machine, in it to describe something that is common to all machines, and then on this basis to build other, more specific: Coffee Maker, Refrigerator, etc.

In web development, we may need classes Menu, Tabs, Dialog, and other interface components. They all usually have something in common.

You can identify such a common functionality in component classes and explore them from him, so as not to duplicate code.

Inheritance by Machine

The base class "machine» Machine will implement a general type methods "include» enable () and "deactivate» disable ():

function Machine() {
  var enabled = false;

  this.enable = function() {
    enabled = true;
  };

  this.disable = function() {
    enabled = false;
  };
}

Inherit from him a coffee maker. At the same time it receives these methods automatically:

function CoffeeMachine(power) {
  Machine.call(this); // inherit

  var waterAmount = 0;

  this.setWaterAmount = function(amount) {
    waterAmount = amount;
  };

}

var coffeeMachine = new CoffeeMachine(10000);

coffeeMachine.enable();
coffeeMachine.setWaterAmount(100);
coffeeMachine.disable();

Inheritance is implemented by calling Machine.call (this) at the beginning of the constructor Coffee Machine.

He calls the Machine, passing it as the context of this current object. Machine, in progress, writes this in a variety of useful properties and methods, in this case, and this.enable this.disable.

He calls the Machine, passing it as the context of this current object. Machine, in progress, writes this in a variety of useful properties and methods, in this case, and this.enable this.disable.

Next Designer Coffee Machine continues to execute, and can add their properties and methods.

The result is an object coffee Machine, which includes methods of Machine and CoffeeMachine.

Protected Properties

In the above, there is one problem.

The heir does not have access to private properties of the parent.

In other words, if the maker wants to appeal to the enabled, then it is waiting for a disappointment:

function Machine() {
  var enabled = false;

  this.enable = function() {
    enabled = true;
  };

  this.disable = function() {
    enabled = false;
  };
}

function CoffeeMachine(power) {
  Machine.call(this);

  this.enable();

  // error, variable is not defined!
  alert( enabled );
}

var coffeeMachine = new CoffeeMachine(10000);

This is natural, since enabled - Local Machine variable functions. It is another scope.

To the heir had access to the property, it must be written in this.

At the same time, to indicate that the property is internal, his name starts with an underscore _.

function Machine() {
  this._enabled = false; // instead var enabled

  this.enable = function() {
    this._enabled = true;
  };

  this.disable = function() {
    this._enabled = false;
  };
}

function CoffeeMachine(power) {
  Machine.call(this);

  this.enable();

  alert( this._enabled ); // true
}

var coffeeMachine = new CoffeeMachine(10000);

Underline in the beginning of the properties - a common sign that the property is internal, intended only for the access of the object itself and its successors. Such properties are called protected.

Technically, climb into it from external code, of course, possible, but a decent programmer will not do so.

Transfer of properties in protected

We have a private power CoffeeMachine property. Now we'll do it, too protected and fast forward in the Machine, because "power" is characteristic of all machines, not just a coffee machine.

function Machine(power) {
  this._power = power; // (1)

  this._enabled = false;

  this.enable = function() {
    this._enabled = true;
  };

  this.disable = function() {
    this._enabled = false;
  };
}

function CoffeeMachine(power) {
  Machine.apply(this, arguments); // (2)

  alert( this._enabled ); // false
  alert( this._power ); // 10000
}

var coffeeMachine = new CoffeeMachine(10000);

Now all the machines have the power Machine capacity. Note that we are out of the constructor parameter immediately copied it to the object in a row (1). Otherwise, she would have been available from the heirs.

The line (2) we now call not just Machine.call (this), and an expanded version: Machine.apply (this, arguments), which causes the Machine in the current context, together with the transfer of the current arguments.

You could use a simple call Machine.call (this, power), but the use of apply ensures the transfer of all the arguments, all of a sudden their number will increase - will not have to rewrite.

Overriding methods

So, we got class Coffee Machine, which inherits from the Machine.

Similarly, we can inherit from the Fridge Machine refrigerator, microwave MicroOven and other classes that share a common "mechanical" functionality, ie they have power and they can be switched on / off.

It's enough to cause a Machine in the current context, and then add their own methods.

// Fridge can add their arguments,
// Machine which will not be used
function Fridge(power, temperature) {
  Machine.apply(this, arguments);

  // ...
}

It so happens that the implementation of a specific method in the successor machine has its own characteristics.

You can, of course, to declare in your Coffee Machine enable:

function CoffeeMachine(power, capacity) {
  Machine.apply(this, arguments);

  // переопределить this.enable
  this.enable = function() {
    /* enable для Coffee Machine */
  };
}

... However, as a rule, we do not want to replace and expand the parent method, add to it something. For example, make sure that when the coffee was launched immediately.

For this pre-copy the parent's method to the variable, and then call in the new enable - where they see fit:

function CoffeeMachine(power) {
  Machine.apply(this, arguments);

  var parentEnable = this.enable; // (1)
  this.enable = function() { // (2)
      parentEnable.call(this); // (3)
      this.run(); // (4)
    }

  ...
}

The general scheme of the override method (by rows selected code snippet):

  1. Copy inherited from parent this.enable method in a variable, for example parentEnable.
  2. Replace this.enable its function ...
  3. ... Which still sells the old functionality by calling parentEnable.
  4. ... And in addition to making something of their own, such as coffee preparation starts.

Pay attention to the line (3).

In her parents' method is called so: parentEnable.call (this). If the call was so: parentEnable (), then it would not pass the current, and this would be an error occurred.

Technically, it can be able to call it and how parentEnable (), but then you have to ensure that the context would be, for example, bind it using bind or advertisement, in the parent, do not use the this, and to get context through a circuit like this:

function Machine(power) {
  this._enabled = false;

  var self = this;

  this.enable = function() {
    // We use an external variable instead of this
    self._enabled = true;
  };

  this.disable = function() {
    self._enabled = false;
  };

}

function CoffeeMachine(power) {
  Machine.apply(this, arguments);

  var waterAmount = 0;

  this.setWaterAmount = function(amount) {
    waterAmount = amount;
  };

  var parentEnable = this.enable;
  this.enable = function() {
      parentEnable(); // Now you can call whatever you like, this is not important
      this.run();
    }

  function onReady() {
    alert( 'Coffee ready!' );
  }

  this.run = function() {
    setTimeout(onReady, 1000);
  };

}

var coffeeMachine = new CoffeeMachine(10000);
coffeeMachine.setWaterAmount(50);
coffeeMachine.enable();

In the above method of the parent parentEnable = this.enable successfully continues to work even when you call without a context. And all because it uses self inside.

In Total

Organization of inheritance, which is described in this chapter is called "functional pattern of inheritance."

Her general scheme (briefly):

  1. Machine parent constructor is declared. In it can be private (private), public (public) and protected (protected) properties:
function Machine(params) {
  // local variables and functions are available only within the Machine
  var privateProperty;

  // public accessible from the outside
  this.publicProperty = ...;

  // protected and available within the Machine for posterity
  // we agree to not touch them outside
  this._protectedProperty = ...
}

var machine = new Machine(...)
machine.public();
  1. To inherit the descendant constructor calls the parent in its context through apply. After that can add variables and their methods:
function CoffeeMachine(params) {
  // the universal call to the transfer of any arguments
  Machine.apply(this, arguments);

  this.coffeePublicProperty = ...
}

var coffeeMachine = new CoffeeMachine(...);
coffeeMachine.publicProperty();
coffeeMachine.coffeePublicProperty();
  1. In CoffeeMachine properties obtained from a parent, you can overwrite your own. But usually not required to replace and expand the parent method. For this purpose it is previously copied into a variable:
function CoffeeMachine(params) {
  Machine.apply(this, arguments);

  var parentProtected = this._protectedProperty;
  this._protectedProperty = function(args) {
    parentProtected.apply(this, args); // (*)
    // ...
  };
}

Line (*) can be simplified to parentProtected (args), if the parent does not use the this method, but, for example, is bound to var self = this:

function Machine(params) {
  var self = this;

  this._protected = function() {
    self.property = "value";
  };
}

I must say that the mode of inheritance, as described in this article are used infrequently.

But to know and understand it is necessary because in many existing class libraries written in a functional style, and expand / inherit from them can only be so.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí