The word "polymorphic" originates from the Greek polymorphism, which is poly (plural) + morph (form) + ism, which is literally understood to be plural form.
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. Let's take a detailed description of the code below.
1.2.11-segment "polymorphic" JavaScript code
We implemented the above story in JavaScript code as follows:
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 cry. It's always dangerous to modify code, and the more places you change, the more likely it is to go wrong, and the more likely it is to makeSound
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, and is also in line with the open-closed principle, which is obviously more elegant and more secure than just adding code to the code to do the same thing.
Polymorphism of 1.2.2 Objects
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() ); //咯咯咯
Now we have a "call" message to both the ducks and the chickens, and they reacted differently after receiving the message. If one day the animal world adds 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() ); //汪汪汪
1.2.3 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.
We have already explained in section 1.1 that statically typed languages perform type-matching checks 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) AnimalSound
the method of the class makeSound
is defined as a parameter that accepts only Duck
the type:
public class Test { public static void main( String args[] ){ AnimalSound animalSound = new AnimalSound(); Chicken chicken = new Chicken(); animalSound.makeSound( chicken ); //报错,只能接受Duck类型的参数 }}
At some point, while enjoying the security of static language type checking, we also feel tied to our hands and feet.
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 Duck
objects and object Chicken
types are hidden behind super-types Animal
, Duck
objects and Chicken
objects can be exchanged for use, which is the only way to make objects appear polymorphic, and polymorphism is the goal of implementing many design patterns.
1.2.4 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. In this section we discuss implementation of inheritance, examples of interface inheritance, see Chapter 21st.
We first create an Animal
abstract class, and then separately let Duck
and Chicken
both 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)
Now all that is left is for the AnimalSound
class's makeSound
method to accept the Animal
type's arguments, not the specific Duck
type or Chicken
type:
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 ); //输出咯咯咯 }}
1.2.5 JavaScript polymorphism
From the previous explanation we learned that the multi-state idea is actually the "what to Do" and "who to do" separation, to achieve this, in the final analysis, first of all to eliminate the coupling between types. If the coupling between types is not eliminated, then we specify in the method that the calling makeSound
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 and an Duck
object of type, which means that the polymorphism of the Chicken
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. In the code example in section 1.2.2, we can either makeSound
pass an duck
object as an argument to a function or pass an chicken
object as a parameter.
It can be seen that the ability of an animal to make a call depends on whether it has makeSound
a method or not, and does not depend on if it is a certain type of object, and there is no "type coupling" in any way. This is what we learned from the duck types in the previous section. In JavaScript, there is no need for techniques such as upward transformation to achieve polymorphic effects.
The function of 1.2.6 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:
The most fundamental benefit of polymorphism is that you no longer have to ask the object "What type you are" and then invoke an object's behavior based on the resulting answer-you just call that behavior, and all the other polymorphic mechanisms will be arranged for you.
In other words, the most fundamental function of polymorphism is to eliminate these conditional branching statements by translating the procedural conditional branching statements into the polymorphism of the objects.
Martin Fowler's words can be well explained by the following example:
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, the Google Maps API provides a show
way to display the entire map on the page. The sample code is as follows:
var googleMap = { show: function(){ console.log( ‘开始渲染google地图‘ ); }};var renderMap = function(){ googleMap.show(); };renderMap();
Later for some reason, to the Google map to Baidu Map, in order to allow renderMap
the function to maintain a certain degree of flexibility, we use a number of conditions branch to let renderMap
the function at the same time support Google Maps and Baidu map:
var googleMap = { show: function(){ console.log( ‘开始渲染google地图‘ ); }};var baiduMap = { show: function(){ console.log( ‘开始渲染baidu地图‘ ); }};var renderMap = function( type ){ if ( type === ‘google‘ ){ googleMap.show(); }else if ( type === ‘baidu‘ ){ baiduMap.show(); }};renderMap( ‘google‘ ); // 输出: 开始渲染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 will undoubtedly have to change the renderMap
function, continue to stack the conditional branch statement inside.
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(); }};renderMap( googleMap ); // 输出: 开始渲染google地图 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, they will be called separately show
, 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( ‘开始渲染soso地图‘ ); }};renderMap( sosoMap );
In this example, we assume that each map API provides a way to display the map show
, which may not be so smooth in actual development, and the adapter pattern can be used to solve the problem.
1.2.7 design pattern 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.
With Command mode 1, 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 is invoked execute
, 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 combination mode 2, polymorphism allows the customer to completely ignore the difference between the composition 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 strategy mode 3, 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.
1. See chapter 9th, "Command mode" 2. See chapter 10th, "Combinatorial Mode" 3. See chapter 5th, "Strategy model"
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.
JavaScript Design patterns and development practices--javascript polymorphism