Object-oriented JavaScript---polymorphic polymorphism
The word "polymorphic" originates from the Greek polymorphism, which is poly (plural) + morph (morphology) +ism, which literally we can understand as plural forms.
The actual meaning of polymorphism is that the same operation acts on different objects and can produce different interpretations and different execution results. In other words, when sending the same message to different objects, these objects give different feedback based on the message. It's not easy to literally understand polymorphism, let's take a look at the example below.
? The owner of the family raised two animals, respectively, is a duck and a chicken, when the host to them issued a "call" command, the Duck will "quack" to call, and the chicken will be "slightly cluck" to call. These two animals will make calls in their own way. They are also "animals, and can make sounds", but according to the master's instructions, they will each make different calls.
In fact, it contains a multi-state of thought.
A polymorphic JavaScript code
var makeSound = function( animal ){ if ( animal instanceof Duck ){ console.log( ‘嘎嘎嘎‘ ); }else if ( animal instanceof Chicken ){ console.log( ‘咯咯咯‘ ); }};var Duck = function(){};var Chicken = function(){};makeSound( new Duck() ); // 嘎嘎嘎makeSound( new Chicken() ); // 咯咯咯
This code does reflect "polymorphism", and when we send "call" messages to ducks and chickens separately, they react differently depending on the message.
But this "polymorphism" is not satisfactory, if later added an animal, such as a dog, obviously the barking of dogs is "Wang Bark", at this time we have to change the MakeSound function, in order to let the dog also make a cry. It is always dangerous to modify code, and the more places you modify, the more likely it is that the program will go wrong, and the makesound may become a huge function when the animal species grows.
The idea behind polymorphism is to separate "what to do" and "who to do and how to do", that is, to separate "unchanging things" from "things that may change." In this story, animals are called, which is constant, but how different types of animals are called variable. By isolating the immutable parts and encapsulating the mutable parts, which gives us the ability to extend the program, the program seems to be growing, it is also in line with the open---closure principle, and it is much more elegant and safe to simply add code to the same function than to modify the code.
The polymorphism of the object
The following is the rewritten code, first we isolate the immutable parts, that is, all animals will make a call:
var makeSound = function( animal ){ animal.sound();};
Then we wrap the mutable parts together, and the polymorphism we just talked about actually refers to the polymorphism of the object:
var Duck = function(){}Duck.prototype.sound = function(){ console.log( ‘嘎嘎嘎‘ );};var Chicken = function(){}Chicken.prototype.sound = function(){ console.log( ‘咯咯咯‘ );};makeSound( new Duck() ); // 嘎嘎嘎makeSound( new Chicken() ); // 咯咯咯
If one day you add a dog, simply append some code, instead of changing the previous MakeSound function, as follows:
var Dog = function(){}Dog.prototype.sound = function(){ console.log( ‘汪汪汪‘ );};makeSound( new Dog() ); // 汪汪汪
Type checking and polymorphism
Type checking is an open topic before the object polymorphism, but JavaScript is a dynamic type language that does not require type checking, and in order to really understand the purpose of polymorphism, we need to turn a corner from a static type language. Statically typed languages are checked for type matching at compile time. In Java, for example, you can't give a variable a different type of value because it's strictly type checked at compile time, and this type of check sometimes makes the code look stiff and the code looks like this:
String str;str = "abc"; // 没有问题str = 2; // 报错
Now let's try to replace the example of the duck and the chicken with the Java code:
public class Duck { // 鸭子类 public void makeSound(){ System.out.println( "嘎嘎嘎" ); }}public class Chicken { // 鸡类 public void makeSound(){ System.out.println( "咯咯咯" ); }}public class AnimalSound { public void makeSound( Duck duck ){ // (1) duck.makeSound(); }}public class Test { public static void main( String args[] ){ AnimalSound animalSound = new AnimalSound(); Duck duck = new Duck(); animalSound.makeSound( duck ); // 输出:嘎嘎嘎 }}
We've been able to make the Ducks cry, but if we want to get the chickens to bark now, we find it impossible. Because (1) the MakeSound method of the Animalsound class is defined by us to accept only the parameters of the Duck type.
public class Test { public static void main( String args[] ){ AnimalSound animalSound = new AnimalSound(); Chicken chicken = new Chicken(); animalSound.makeSound( chicken ); // 报错,只能接受 Duck 类型的参数 }}
While enjoying the security of static language type checking, we also feel trapped.
To solve this problem, the static type of object-oriented language is often designed to be transformed upward : When assigning a value to a class variable, the type of the variable can either use the class itself, or it can use the superclass of the class. It is as if we were describing a sparrow or a magpie in the sky, usually saying "a sparrow is flying" or "a magpie is flying". But if you want to ignore their specific type, then you can say "a bird is flying."
Similarly, when the type of the Duck object and the chicken object are hidden behind the super-type animal, the Duck object and the chicken object can be exchanged for use, which is the only way to make the object appear polymorphic, and the performance of polymorphism is the goal of implementing many design patterns.
Using inheritance to get polymorphic effects
Using inheritance to get polymorphic effects is the most common means of making objects appear polymorphic. Inheritance typically includes implementation inheritance and interface inheritance. We discuss implementing inheritance.
We first create a Animal abstract class, and then let both duck and chicken inherit from the Animal abstract class, the following code (1) and (2) at the assignment statement is clearly established, because ducks and chickens are also animals:
public abstract class Animal { abstract void makeSound(); // 抽象方法}public class Chicken extends Animal{ public void makeSound(){ System.out.println( "咯咯咯" ); }}public class Duck extends Animal{ public void makeSound(){ System.out.println( "嘎嘎嘎" ); }}Animal duck = new Duck(); // (1)Animal chicken = new Chicken(); // (2)// 现在剩下的就是让 AnimalSound 类的 makeSound 方法接受 Animal 类型的参数// 而不是具体的Duck 类型或者 Chicken 类型public class AnimalSound{ public void makeSound( Animal animal ){ // 接受 Animal 类型的参数 animal.makeSound(); }}public class Test { public static void main( String args[] ){ AnimalSound animalSound= new AnimalSound (); Animal duck = new Duck(); Animal chicken = new Chicken(); animalSound.makeSound( duck ); // 输出嘎嘎嘎 animalSound.makeSound( chicken ); // 输出咯咯咯 }}
The polymorphism of JavaScript
From the front we learned that the multi-state idea is actually the "what to Do" and "who do" separate, to achieve this, in the final analysis of the first to eliminate the coupling between the types of relations. If the coupling between types is not eliminated, then we specify in the MakeSound method that the calling object is a type, and it cannot be replaced with another type. In Java, you can achieve polymorphism by turning up.
The variable type of JavaScript is variable at run time. A JavaScript object that can represent both an object of type duck and an object of type Chicken, which means that the polymorphism of the JavaScript object is innate.
This innate polymorphism is not difficult to explain. JavaScript, as a dynamic type language, does not have a type-checking process at compile time, neither checking the created object type nor checking the passed parameter type. We can either pass the Duck object as an argument to the MakeSound function or pass the chicken object as a parameter.
It can be seen that the ability of an animal to make a call depends only on whether it has a makesound method, and does not depend on if it is a certain type of object, and there is no "type coupling" in any way. In JavaScript, there is no need for techniques such as upward transformation to achieve polymorphic effects.
The role of polymorphism in object-oriented programming
Many people believe that polymorphism is the most important technology in object-oriented programming languages. However, it is difficult to see this point, after all, most people do not care about how the chickens are called, do not want to know how the ducks are called. What does it matter to the programmer that the chickens and ducks make different calls under the same message?
Martin Fowler , in refactoring: improving the design of existing code, writes:
? In the film filming scene, when the director shouted "Action", the protagonist began to recite lines, lighting division is responsible for playing lights, the back of the mass actors pretended to be shot down, the props division to the lens with snowflakes. When you get the same message, each object knows what it is supposed to do. If you don't take advantage of the polymorphism of the object, but instead write it in a process-oriented way, it's equivalent to going to the front of everyone, confirming their division of labor (type), and then telling them what to do when the movie starts shooting. If you map to a program, the conditional branching statements will be flooded in the program.
With the polymorphism of the object, the director does not have to think about what the individual objects should do when they post the message. What an object should do is not an interim decision, but a pre-agreed and rehearsed completion. What each object should do, has become a method of the object, is installed inside the object, and each object is responsible for its own behavior. So these objects can do their work methodically and separately according to the same message.
The advantage of object-oriented design is that the behavior is distributed across objects and the objects are responsible for their own behavior.
Looking at an example of reality development, the idea of this example is very similar to the animal cry story. Suppose we are going to write a map app and now have two optional map API providers for us to access our apps. We currently choose Google Maps, Google Maps API provides the show method, responsible for displaying the entire map on the page. The sample code is as follows:
var googleMap = { show: function(){ console.log( ‘开始渲染谷歌地图‘ ); }};var renderMap = function(){ googleMap.show();};renderMap(); // 输出:开始渲染谷歌地图
Later for some reason, to the Google map to Baidu Map, in order to let rendermap function to maintain a certain degree of elasticity, we use some conditions branch to let Rendermap function support Google Maps and Baidu map:
var googleMap = { show: function(){ console.log( ‘开始渲染谷歌地图‘ ); }};var baiduMap = { show: function(){ console.log( ‘开始渲染百度地图‘ ); }};var renderMap = function( type ){ if ( type === ‘google‘ ){ googleMap.show(); }else if ( type === ‘baidu‘ ){ baiduMap.show(); }};renderMap( ‘google‘ ); // 输出:开始渲染谷歌地图renderMap( ‘baidu‘ ); // 输出:开始渲染百度地图
It can be seen that although the RENDERMAP function has maintained a certain degree of elasticity, but this flexibility is very fragile, once the need to replace the search for a map, it is undoubtedly necessary to change the Rendermap function, continue to the inside of the stack of conditional branching statements.
Let's start by abstracting the same part of the program, which is to show a map:
var renderMap = function( map ){ if ( map.show instanceof Function ){ map.show(); }};var googleMap = { show: function(){ console.log( ‘开始渲染谷歌地图‘ ); }};var baiduMap = { show: function(){ console.log( ‘开始渲染百度地图‘ ); }};renderMap( googleMap ); // 输出:开始渲染谷歌地图renderMap( baiduMap ); // 输出:开始渲染百度地图
Now look for polymorphism in this code. When we send a "show map" message to the Google Map object and the Baidu map object separately, the show method will be called separately, resulting in different execution results.
The polymorphism of the object prompts us, "What to Do" and "How to do" can be separated, even if later added to search the map, the Rendermap function still does not need to make any changes, as follows:
var sosoMap = { show: function(){ console.log( ‘开始渲染搜搜地图‘ ); }};renderMap( sosoMap ); // 输出:开始渲染搜搜地图
In this example, we assume that each map API provides a method name for the display map, which may not be so smooth in actual development, and the adapter pattern can be used to solve the problem.
Design Patterns and polymorphism
The title of the book "Design Mode" by GoF is "the basis of reusable object-oriented software". The book is entirely from the perspective of object-oriented design, through the encapsulation, inheritance, polymorphism, combination and other techniques of reuse, refining some reusable object-oriented design techniques. and the multi-state in which is the weight of the most, most of the implementation of design patterns are inseparable from the idea of polymorphism.
In the command mode, the request is encapsulated in some command objects, which allows the caller of the command and the receiver of the command to be fully decoupled, and when the command's Execute method is invoked, different commands do different things, resulting in different execution results. The process of doing these things is already encapsulated inside the command object, and as a client invoking the command, there is no need to care about the specific process of command execution.
In composite mode, polymorphism allows the customer to completely ignore the difference between the combined object and the leaf-node object, which is where the combined pattern is most important. When you send the same message to a combination object and a leaf node object, they do what they should do, and the combined object will continue to forward the message to the following leaf node object, and the leaf node object gives real feedback on the message.
In the strategy mode, the Context does not have the ability to execute the algorithm, but instead delegates this responsibility to a policy object. Each policy object is responsible for the algorithm that has been encapsulated inside the object. When we issue a "computed" message to these policy objects, they return their respective computed results.
In the language of JavaScript, which functions as a class-one object, the function itself is an object, and the function is used to encapsulate the behavior and be passed around. When we send a "call" message to some functions, these functions return different execution results, which is a manifestation of polymorphism and the reason many design patterns can be used in JavaScript to replace implementations with higher-order functions.
Object-oriented JavaScript---polymorphism