1. Principle of single responsibility
There should be only one reason for a class to change. In JavaScript, the single responsibility principle is more often applied at the object or method level.
The responsibility of the Single Responsibility Principle (SRP) is defined as “cause of change”. If there are two incentives to rewrite a method, then the method has two responsibilities. Each responsibility is an axis of change, and if one method takes on too many responsibilities, the more likely it is that the method will need to be rewritten as requirements change
At this point, this approach is often an unstable one, and changing code is a dangerous thing, especially when two responsibilities are coupled. Changes in one responsibility can affect the implementation of other responsibilities, causing unexpected damage. The result of this coupling is a low cohesion and fragile design.
Thus, the SRP principle is: an object (method) does only one thing
1.1 SRP principles in design patterns
1.1.1 Proxy Mode
By adding a virtual proxy, the responsibility for preloading images is placed in the proxy object, while the ontology is only responsible for adding the IMG tag to the page, which is its original responsibility
// myImage is responsible for adding img tags to the page
var myImage = (function(){
var imgNode = document.createElement('img');
document.body.appendChild(imgNode);
return {
setSrc: function(src) { imgNode.src = src; }}}) ();// proxyImage is responsible for preloading images and passing the request to myImage after the preloading is complete
var proxyImage = (function(){
var img = new Image;
img.onload = function(){
myImage.setSrc(this.src);
}
return {
setSrc: function(src) {
myImage.setSrc('file//xxx/loading.gif')
img.src = src;
}
}
})()
proxyImage.setSrc('http:// xxx.png')
Copy the code
Split the function of adding img tags and the responsibility of preloading images into two objects, each of which has only one motive to be modified. When they change individually, they do not affect other objects.
Detailed proxy mode
1.1.2 Iterator pattern
The following code iterates through a collection and adds divs to the page with their innerHTML corresponding to each element in the collection
var appendDiv = function(data){
for(var i=0, l=data.length; i<l; i++) {
var div = document.createElement('div');
div.innerHTML = data[i];
document.body.appendChild(div)
}
};
appendDiv([1.2.3.4.5.6])
Copy the code
The appendDiv function is simply rendering data, but in this case it also iterates over the aggregated object data. If the data format changes from array to object, the code that traverses the data will have problems.
It is necessary to extract the responsibility for traversing data, which is the point of the iterator pattern, which provides a way to access an aggregate object without exposing its internal representation
After encapsulating the responsibilities of the iterated aggregator separately in each, even if new iteration methods are added later, you only need to modify the each function; the appendDiv function is not affected
var each = function(obj, callback) {
var value,
i = 0,
length = obj.length,
isArray = isArraylike(obj);
if (isArray) {
for(; i<length; i++) { callback.call(obj[i], i, obj[i]); }}else {
for(i inobj) { value = callback.call(obj[i], i, obj[i]); }}return obj;
}
var appendDiv = function(data){
each(data, function(i, n) {
var div = document.createElement('div');
div.innerHTML = n;
document.body.appendChild(div)
}
};
Copy the code
Verbose iterator pattern
1.1.3 Singleton Mode
Detailed singleton patterns
1.1.4 Decorator mode
Detailed decorator pattern
1.2 When should responsibilities be separated
The SRP principle is one of the simplest and most difficult principles to apply correctly
Note: Not all duties should be separated.
-
If two responsibilities always change at the same time as requirements change, there is no need to separate them. In ajax requests, for example, creating an XHR object and sending an XHR request are almost always together, so there is no need to separate the responsibilities of creating an XHR object from those of sending an XHR request.
-
The axis of change in responsibilities is meaningful only if they are certain to change, and even if two responsibilities have been coupled together and there are no signs of change, then it may not be necessary to actively separate them, and to do so when the code needs refactoring
1.3 SRP Violation
People habitually put a set of related activities together. How to correctly separate responsibilities is not easy
You may never think about separating responsibilities, but that doesn’t stop you from writing code to fulfill requirements.
On the one hand, they are guided by design principles; on the other hand, they are not necessarily adhered to at all times. In real development, SRP violations are not uncommon for a variety of reasons. Methods such as attR in Jquery clearly violate SRP principles. The attR of jQuery, which is responsible for both assignment and value, can cause some difficulties for maintainers of jQuery, but it can simplify things for users of jQuery.
There is a trade-off between convenience and stability. There is no standard answer for convenience or stability, but it depends on the context in which it is used.
1.4 Advantages and disadvantages of the SRP principle
The SRP principle has the advantage of reducing the complexity of individual classes or objects and breaking them down into smaller granularity by responsibility, which facilitates code reuse and unit testing. When one responsibility needs to be changed, it does not affect the other responsibilities
The single SRP principle also has some disadvantages, most notably the increased complexity of writing code. When we break objects down into smaller granularity by responsibility, we actually make it harder for them to relate to each other
2. The least knowledge rule
The Least Knowledge principle (LKP) : a software entity should interact with as few other entities as possible. The software entity here is a broad concept, including not only objects, but also systems, classes, modules, functions, variables, etc.
2.1 Reduce connections between objects
The single responsibility principle guides us to divide objects into smaller granularity, which improves object reusability. But an increasing number of objects can be intertwined, and if you change one of them, it is likely to affect other objects that reference it. Objects coupled to objects are likely to reduce their reusability.
The least knowledge principle requires that we design programs with minimal interaction between objects. If two objects do not have to communicate directly with each other, then they should not interact directly with each other. A common practice is to introduce a third party object to assume the role of passage between these objects. If some objects need to make requests to other objects, these requests can be forwarded through third-party objects.
2.2 Least knowledge principle in design patterns
The least knowledge principle is most reflected in the design pattern in the mediator pattern and appearance pattern
2.2.1 Intermediary mode
The mediator model embodies the least knowledge principle well. By adding a mediator object, all related objects communicate through the mediator object instead of referring to each other. So, when an object changes, the mediator only needs to be notified of the object.
2.2.2 Appearance Mode
The facade pattern primarily provides a consistent interface for a set of interfaces in a subsystem. The facade pattern defines a high-level interface that makes the subsystem easier to use
The cosmetic pattern serves to shield the customer from the complexity of a set of subsystems. The facade pattern provides an easy-to-use high-level interface to the customer, which forwards the customer’s requests to the subsystem to implement specific functions. Most customers can access subsystems by requesting a facade interface. But in a program that uses appearance patterns, requesting appearance is not mandatory. If the facade does not meet the customer’s personalized needs, the customer can also choose to bypass the facade and access the subsystem directly.
The facade pattern is easily confused with the plain encapsulation implementation. Both encapsulate something, but the key to the facade pattern is to define a high-level interface to encapsulate a set of “subsystems.”
The facade pattern maps to JavaScript, and this subsystem should at least refer to a collection of functions
var A = function(){
a1();
a2();
}
var B = function(){
b1();
b2();
}
var facade = function(){
A();
B();
}
facade();
Copy the code
Appearance patterns serve two main purposes
- Provides a simple and convenient access point for a set of subsystems.
- Isolate customers from complex subsystems without requiring them to understand the details of the subsystems.
From the second point, the appearance pattern conforms to the least knowledge principle.
2.3 Encapsulation embodied in the least knowledge principle
Encapsulation is largely about hiding data. A module or object can hide its internal data or implementation details, exposing only the necessary interface apis for external access. When one object must invoke another, we can make the object expose only the necessary interface, so that the contact between objects is limited to the minimum scope.
Encapsulation is also used to limit the scope of variables. The scope of a variable is specified in JavaScript:
- If a variable is declared globally, or declared implicitly (without var) anywhere in the code, the variable is visible globally;
- Variables that are explicitly declared inside a function (using var) are visible inside the function.
By limiting the visibility of a variable to the narrowest possible range, the less impact that variable has on other unrelated modules, and the less chance that the variable will be overwritten and conflicted. This is also an embodiment of the least knowledge principle in a broad sense.
3. Open-door principle
The open – closed principle (OCP) is the most important principle in object – oriented programming. Most of the time, a program with good design, often in accordance with the open – closed principle.
3.1 Extend the window.onload function
Function.prototype.after = function( afterfn ){
var __self = this;
return function(){
var ret = __self.apply( this.arguments );
afterfn.apply( this.arguments );
returnret; }};window.onload = ( window.onload || function(){} ).after(function(){
console.log( document.getElementsByTagName( The '*' ).length );
});
Copy the code
By decorating the function dynamically, we completely ignore the internal implementation of the previous window.onload function, whether it was elegant or ugly. It doesn’t matter if we, the maintainers, get a mangled, compressed copy of the code. As long as it was a stable function before, it won’t go wrong in the future because of our new requirements. The new code and the existing code can be completely consistent.
3.2 Open and closed
Open The idea of the closed principle: when it is necessary to change the function of a program or add new features to the program, you can use the method of adding code, but do not change the source code of the program.
3.3 Eliminate conditional branches by object polymorphism
Excessive conditional branching is a common cause of programs violating the open-closed principle. Every time a new if statement needs to be added, the original function is forced to change. Switching from “if” to “switch-case” doesn’t work; it’s a case of the same old thing. In fact, whenever we see a bunch of if or Swtich-case statements, the first thing we should think about is whether we can refactor them using the polymorphism of the object.
It is a common technique to use object polymorphism to make programs follow the open-closed principle.
var makeSound = function( animal ){
animal.sound();
};
var Duck = function(){};
Duck.prototype.sound = function(){
console.log( 'Quack quack' );
};
var Chicken = function(){};
Chicken.prototype.sound = function(){
console.log( 'Cluck cluck' );
};
makeSound( new Duck() ); // gaga makeSound(new Chicken()); / / luo luo luo
/ * * * * * * * * * increase animal dog, don't need to change the original makeSound function * * * * * * * * * * * * * * * * /
var Dog = function(){};
Dog.prototype.sound = function(){
console.log( Woof woof woof );
};
makeSound( new Dog() ); / / auf
Copy the code
3.4 Identify changes
The open-closed principle is a somewhat illusory one, with no practical template for how to implement it. But there are a few rules that we can use to make our programs as open and closed as possible. The most obvious is to identify what changes are going to happen in your program, and then encapsulate those changes.
By encapsulating change, you can isolate the parts of the system that are stable from those that are prone to change. In the evolution of the system, we only need to replace the parts that are easy to change, which is relatively easy to replace if they are already packaged. And beyond the changing part is the stable part. In the evolution of the system, the stable parts do not need to be changed.
In addition to taking advantage of object polymorphism, there are other ways to write code that adheres to the open-closed principle
3.4.1 Placing hooks
Placing hooks is also a way to separate changes. We place a hook where the program is likely to change, and the result of the hook determines the next step in the program. As a result, there is a fork in the path of the original code execution, and the future execution of the program is buried in many possibilities.
Since the number of subclasses is unlimited, there will always be “personalized” subclasses that force us to change the skeleton of the encapsulated algorithm. We can then place a hook somewhere in the parent class that is easy to change, and the result of the hook is determined by the specific child class. In this way, the program has the possibility to change.
3.4.2 Using callback functions
In JavaScript, functions can be passed as arguments to another function, which is one of the meanings of higher-order functions. In this case, we usually refer to this function as a callback function. In the JavaScript version of the design mode, policy mode, command mode, and so on can be easily implemented with callbacks.
The callback function is a special kind of hook. We can encapsulate a portion of mutable logic in callback functions and pass the callback functions as arguments to a stable and closed function. When the callback function is executed, the program can produce different results because of the internal logic of the callback function.
3.5 Open – close principle in design pattern
Almost all design patterns adhere to the open-closed principle, and the good designs we see often survive the open-closed principle. Both concrete design patterns and more abstract object-oriented design principles, such as the single responsibility principle, the least knowledge principle, dependency inversion principle and so on, have emerged in order to make programs comply with the open-closed principle. It can be said that the open-closed principle is the goal of writing a good program, and the other design principles are the process of achieving this goal.
3.5.1 Publish-subscribe mode
The publish subscription pattern is used to reduce dependencies between multiple objects. It replaces hard-coded notification mechanisms between objects so that one object no longer explicitly calls an interface of another object. When a new subscriber appears, the publisher’s code does not need to be modified at all; Also, if the publisher needs to change, it will not affect the previous subscribers.
3.5.2 Template method mode
The template method pattern is a typical design pattern that encapsulates changes to improve system extensibility. In a program that uses the template method pattern, the subclass method type and execution order are unchanged, so we extract this logic and put it in the parent class template method; How the methods of subclasses are implemented is variable, so that the logic of the changes is encapsulated in the subclasses. By adding new subclasses, you can add new functionality to the system without changing the abstract parent class or other subclasses, which is also in accordance with the open-closed principle.
3.5.3. Policy mode
The policy pattern and the template method pattern are competitors. In most cases, they can be used interchangeably. The template approach pattern is based on the idea of inheritance, while the policy pattern focuses on composition and delegation.
The policy pattern encapsulates various algorithms into separate policy classes that can be used interchangeably. The policy and the client code that uses the policy can be modified independently of each other. It is also very convenient to add a new policy class without modifying the previous code at all.
3.5.4 Proxy Mode
For example, we already have a function myImage that sets the image SRC. When we want to add the image preloading function to it, one way is to change the internal code of myImage function. It is better to provide a proxy function proxyMyImage. The proxy function is responsible for the image preloading, and after the image preloading is complete, it passes the request to the original myImage function, which does not need any changes in the process.
The ability to preload images and the ability to SRC images are separated into two functions that can be changed separately without affecting each other. MyImage is unaware of the agent’s existence and can continue to focus on its role of SRC for images.
3.5.5 Responsibility chain mode
Take a huge order function and split it into three functions: 500 yuan order, 200 yuan order, and ordinary order. These three functions are connected by a chain of responsibilities in which customer requests are passed:
var order500yuan = new Chain(function( orderType, pay, stock ){
// The code is omitted
});
var order200yuan = new Chain(function( orderType, pay, stock ){
// The code is omitted
});
var orderNormal = new Chain(function( orderType, pay, stock ){
// The code is omitted
});
order500yuan.setNextSuccessor( order200yuan ).setNextSuccessor( orderNormal );
order500yuan.passRequest( 1.true.10 ); // 500 yuan deposit, get 100 coupons
Copy the code
As you can see, when we add a new type of order function, we don’t need to change the original order function code, just add a new node in the chain.
3.6 Relativity of the open-closure principle
In chain-of-responsibility code, you might wonder: the open closed principle requires that we extend the functionality of the program only by adding source code, not by modifying the source code. So when we add a new $100 singleton to the chain of responsibilities, don’t we also have to change the code that sets the chain? The code is as follows:
order500yuan.setNextSuccessor( order200yuan ).setNextSuccessor( orderNormal ); A: order500yuan setNextSuccessor (order200yuan). SetNextSuccessor (order100yuan). SetNextSuccessor (orderNormal);Copy the code
In fact, it’s not easy to keep a program completely closed. Even if it could be done technically, it would take too much time and effort. And the price of conforming to the open-closed principle is the introduction of more levels of abstraction, which can increase code complexity.
What’s more, there will always be changes that can’t be sealed off from some code that can’t be sealed off anyway.
- Pick out the places where change is most likely to occur, and then construct abstractions to seal off those changes.
- When changes inevitably occur, try to make changes that are relatively easy to make. For an open source library, it is easier to modify the configuration files it provides than to modify its source code.