This article focuses on the pre-knowledge of design patterns, but does not cover specific design patterns.

Dynamically typed language

Programming languages can be broadly divided into two types in terms of data types, statically typed and dynamically typed.

Statically typed languages determine the type of the data before compilation. If the data type does not match, it will not be compiled. Dynamically typed languages, on the other hand, determine the data type of a variable when it is assigned to it at runtime.

Statically typed languages are more stable and secure than dynamically typed languages. However, in order to achieve such security, you need to spend more effort on variable assignment. It also requires more code to declare variables. So statically typed languages are relatively cumbersome and safe. Dynamically typed languages are relatively error-prone but flexible and fast.

Because we don’t need to consider data types when assigning values to variables in Javascript, Javascript is a dynamically typed language where we can call any method on any object regardless of whether it originally had a method or not.

Interface oriented programming

Duck types and polymorphism

A common term for duck types is when a bird is seen to walk like a duck, swim like a duck, and quack like a duck. That is, as long as they have all of the specified behaviors they can be classified. There IS no need to focus on the type in the duck type, which only focuses on the behavior of the object (HAS-a) rather than the object itself (IS-A).

Since dynamic languages have no type judgment, implementing duck types requires only specific behavior.

var dog = {
    quackSing :function(){console.log( Woof woof woof)};
}
var cat = {
    quackSing :function(){console.log( Meow, meow, meow.)};
}

function quackSing(animal){
	if(animal && typeof animal.quackSing == 'function') {console.log('Because I have the quckSing method, I can join the animal family.')
	}
}
quackSing(cat) // Since I have the quckSing method, I can join the animal family
quackSing(dog) // Since I have the quckSing method, I can join the animal family
Copy the code

In statically typed languages, the equivalent of the duck type is polymorphism, which means that the same operation behaves differently on different objects of the same type. It is generally achieved through inheritance (upward transition). It is common to put specific desired behavior into a superclass or interface, and then subclasses inherit the superclass and get the superclass methods. These subclasses are called superclass interfaces. For example, Cat and Dog belong to Animal interfaces.

public abstract class Animal {
    abstract void quackSing(a){}}public class Cat extends Animal {
    void quackSing(a){
        System.out.printf(Meow, meow, meow.)}}public class Dat extends Animal {
    void quackSing(a){
        System.out.printf(Woof woof woof)}}public class AnimalbeHavior{
    Animal animal;
    // If you only use a subclass of Animal, the other subclasses cannot be implemented.
    // For example, when the type is changed from Animal to Cat, the type verification parameter cannot be put into Dog.
    public AnimalbeHavior (Animal a){
        animal = a;
    }
    public void quackSing(a){
        animal.quackSing
    }
}
AnimalbeHavior aa = new AnimalbeHavior(new Cat());
aa.quackSing() //' Meow meow meow '
AnimalbeHavior bb = new AnimalbeHavior(new Dog());
bb.quackSing() //' woof woof woof woof '
Copy the code

The fundamental benefit of polymorphism is that you no longer have to ask an object “what type are you?” and then call an object’s behavior based on the answer — you just call that behavior, and all the other polymorphic mechanisms will take care of that for you ———— refactoring: Improving the design of existing code

Borrowing from duck types and polymorphisms, we can implement a principle: “programming for interfaces,” not “programming for implementations.” In the example above, we consider anything that has a quackSing method to be an interface. When we call the quackSing method on the Animal interface. Since Cat and Dog both have the same behavior, we can delegate to the quackSing method of the concrete implementation (Cat and Dog). The advantage of polymorphisms and duck types is that you can distribute behavior across objects and let them take care of themselves, eliminating branching statements.

Without abstracting such an interface, we can only determine which implementation (Cat, Dog) triggers the behavior when performing the same behavior, violating the open close principle. Interface oriented programming, and unified management of such interfaces, makes code more resilient.

function quackSing(animal){
	if(isCat){
            console.log(Meow, meow, meow.)}else if (isDog){
            console.log(Woof woof woof)}}Copy the code

Use composition more than inheritance

Now Xiao Ming has several cats. They all run, eat and meow. They all have the same method. So we create these kittens with inheritance, with run, eat, and makeSound methods.

One day, xiao Ming bought a ToyCat. The cat could not eat, that is, it had no way to eat. Since inheritance is used, ToyCat needs to override the eat method of the superclass. The toy cat could even be a beckon cat, so add a beckon method to the superclass. Thus, any kitten with no beckon ability needs to override the superclass’s beckon method. So inheritance causes subclasses to implement the method if they don’t need it, which is wasteful.

In fact, instead of just adding all the methods to the superclass at once, you can treat the behavior as an Interface, and then subclasses can implement that behavior by inheriting the Interface. For example, a cat with the ability to eat inherits the Eatable interface, a Beckonable interface with the ability to wave, and a corresponding behavior is implemented in the current subclass.

But the interface does not have implemented code, so the inherited interface cannot achieve code reuse. This means that when you need to modify a class of behavior, you need to modify it in every class that defines the behavior, which can be very error-prone.

We can solve these problems by following two design principles.

Rule of design: Identify the parts of your application that might need to change, isolate them, and don’t mix them with code that doesn’t need to change.

For these kittens, we first identify possible changes in behavior (EAT, beckon), which will vary from kitten to kitten. While other methods do not need to be changed often, we will not deal with it for the time being.

You can start thinking about what’s going to change at the beginning of the design, or you can “encapsulate” what’s going to change as requirements change. This makes the code more resilient.

Design principle: Program for the interface, not the implementation.

For Interface programming, the Interface is not the one that comes with the syntax above. The interface here represents each behavior, and can be viewed as a collection of concrete behavior implementations that are actions. For example, the behavior of eating is an interface (Eatbehavior), and the concrete eating method (Eatmuch, Eatlittile, Eatnothing) is the concrete realization of the behavior. You can also beckon as an interface (Beckonbechvior).

At this point, the cat subclass does not need to implement the specific behavior, but instead uses the behavior represented by the interface (Beckonbechvior, Eatbehavior). Specific actions are delegated to interfaces (Beckonbechvior, Eatbehavior).

With inheritance, the behavior of the cat is inherited by the superclass or implemented by itself, and subclasses are bound to the concrete implementation (concrete behavior is implemented by subclasses). To put it simply, whereas cat subclasses bind specific actions, now cat subclasses bind interfaces, and then interfaces act.

The advantage of this design is that the cat subclass is decoupled from the specific action. These actions can be reused by other objects because the actions and objects are decoupled. You can also add new actions without affecting the cat subclasses.

// Abstract class of cat
public abstract class Cat {
    Eatbehavior eatbehavior;
    Beckonbehavior beckonbehavior;
    public Cat {}
    
    public run (a){
        System.out.printf('It took a cat walk.')}public makeSound (a){
        System.out.printf(Meow, meow, meow.)}public void eat(a){
        // Delegate the eat method to the Eatbehavior
        eatbehavior.eat()
    }
    public void beckon(a) {
        // The beckon method is delegated to the Eatbehavior
        beckonbehavior.beckon()
    }
    public abstract void otherThings(a){
        // ...}}Copy the code
// Eat the interface and inherit the interface methods
public interface Eatbehavior{
    public void eat(a) {}}public class Eatmuch implements Eatbehavior {
    public void eat(a){
        System.out.printf('I eat more thieves! ')}}public class Eatlittle implements Eatbehavior {
    public void eat(a){
        System.out.printf('I'll eat it! ')}}public class Eatnothing implements Eatbehavior {
    public void eat(a){
        System.out.printf('I didn't eat anything! ')}}Copy the code
// Methods for waving interfaces and inheriting interfaces
public interface Beckonbehavior {
    public void beckon(a){}}public nobeckon implements Beckonbehavior {
    public void beckon(a){
        System.out.printf('OH, I don't wave')}}public isbeckon implements Beckonbehavior {
    public void beckon(a){
        System.out.printf('I beckon the thief 6')}}Copy the code
// Toy cat ~
public class ToyCat extends Cat {
    public void otherThings(a){
        // ...
    }
    public ToyCat(a) {
        // When multiple classes are used together, the difference between composition and inheritance is that behavior is generated by composition of behavior objects rather than inheritance.
        eatbehavior = new Eatnothing();
        beckonbehavior = new isbeckon();
    }
    // The behavior can be dynamically modified to make the system more flexible
    public void setEatbehavior (Eatbehavior behavior){
        eatbehavior = new behavior();
    }
    public void setBebeckonbehavior (Beckonbehavior behavior){
        beckonbehavior = new behavior();
    }
}
Cat toyCat = new ToyCat();
toyCat.eat(); // I didn't eat anything!
toyCat.setEatbehavior(new Eatmuch());
toyCat.eat(); // I eat a lot of thieves!
Copy the code

Looking back, using “interface oriented” design, we solved the problem of useless methods from inheritance and the problem of unreusable interfaces. And switching gears, the interface doesn’t have to be a “set of behaviors,” but a “family of algorithms,” which now represent what a duck can do. However, if the ball moves differently, this family of algorithms represents multiple algorithms for moving the ball differently.

Finally, I have a general understanding of the pre-knowledge of design patterns and can start to enter the gate of design patterns. You can start with simple policy patterns, command patterns, and see how “interface oriented programming” and “more composition, less inheritance” are used in practice. It is recommended to take a look at the parameter book at the bottom

This is the end of the article, through reading and consulting materials summarized. If there are mistakes, please point out, immediately correct! Finally beg a praise 👍👍👍👍👍👍👍👍👍👍 bai!! Big handsome than and big beautiful people!

Reference books

JavaScript Design Patterns and Development Practices

HeadFirst Design Mode