Design Principles (SOLID Principles)

In the field of programming, SOLID (Single Function, Open-Closed Principle, Liskov Substitution, Interface Segregation, and Dependency Inversion) was introduced by Robert C. Martin in the early 2000s to refer to five aspects of object-oriented programming and object-oriented design. a basic principle. When these principles are applied together, they make it more possible for a programmer to develop a system that is easy to maintain and extend software.

1. Single Responsibility Principle (SRP)

For a class, there should be only one reason for it to change. In JavaScript, there are not many scenarios that need to use classes, and the single responsibility principle is more applied at the object or method level.

If we have two motivations to override a method, then the method has two responsibilities. Each responsibility is an axis of change, and if a method takes on too many responsibilities, the greater the likelihood that the method will need to be rewritten as requirements change.
Simply put: an object (method) does only one thing.

1.1 Advantages and disadvantages

  • Advantages: It reduces the complexity of a single class or object, and divides objects into smaller granularities according to their responsibilities, which is conducive to code reuse and unit testing.
  • Disadvantages: The complexity of writing code is increased, objects are divided into smaller granularities, and the direct connection of objects becomes more complicated.

2. Open-Closed Principle (OCP)

The open-closed principle was first proposed by Bertrand Meyer, the designer of the Eiffel language, in his book Object-Oriented Software Construction. It is defined as follows:

Software entities (classes, modules, functions) etc. should be open for extension but closed for modification.

Extend window.onload function

 Function.prototype.after = function(afterfn) {
    var _self = this;
    return function() {
        var ret = _slef.apply(this, arguments);
        afterfn.apply(this, arguments);
        return ret;
    }
}

window.onload = (window.onload || function() {}).after(function(){
    consolo.log("拓展的代码")
})

By dynamically decorating the function, the new function is directly expanded instead of directly modifying the previous onload related code.

3. The Liskov Substitution Principle (LSP)

Defined as follows:

The design of the subclass should ensure that when replacing the parent class, the logic of the original program will not be changed and the correctness of the original program will not be destroyed.

Rectangle example:

 // 考虑我们有一个程序用到下面这样的一个矩形对象:
var rectangle = {
    length: 0,
    width: 0
};
// 过后,程序有需要一个正方形,由于正方形就是一个长(length)和宽(width)都一样的特殊矩形,所以我们觉得创建一个正方形代替矩形。
var square = {};
(function() {
    var length = 0, width = 0;
    Object.defineProperty(square, "length", {
        get: function() { return length; },
        set: function(value) { length = width = value; }
    });
    Object.defineProperty(square, "width", {
        get: function() { return width; },
        set: function(value) { length = width = value; }
    });
})();

不幸的是,当我们使用正方形代替矩形执行代码的时候发现了问题,其中一个计算矩形面积的方法如下:
var g = function(rectangle) {
    rectangle.length = 3;
    rectangle.width = 4;
    write(rectangle.length);
    write(rectangle.width);
    write(rectangle.length * rectangle.width);
};

When this method is called, the result is 16 instead of the expected 12. Our square object violates the LSP principle . The length and width properties of square imply that it is not 100% compatible with rectangles, but we do not always clear hint.

The Liskov Substitution Principle (LSP) expresses not the relationship of inheritance, but any method (as long as the behavior of the method can understand the behavior of the other).

4. Interface Segregation Principle (ISP)

definition:

Clients should not be forced to depend on methods they do not use.
A client should not rely on interfaces it does not need. Replace complex interfaces consisting of multiple methods with multiple fine-grained interfaces, each serving a submodule

Class A depends on class C through interface interface, class B depends on class D through interface interface, if interface interface is not the smallest interface (fat interface) for class A and class B, then class C and class D must implement what they do not need method.

Simply put, establish a single professional interface, refine the interface according to functional responsibilities, and minimize the methods in the interface.

example:

 var rectangle = {
    area: function() {
        /* 代码 */
    },
    draw: function() {
        /* 代码 */
    }
};

var geometryApplication = {
    getLargestRectangle: function(rectangles) {
        /* 代码 */
    }
};

var drawingApplication = {
    drawRectangles: function(rectangles) {
       /* 代码 */
    }
};

When a rectangle substitute needs only the area() method of the rectangle to satisfy the getLargestRectangle of the new object geometryApplication, it violates the LSP (because it doesn't use the draw method which the drawRectangles method does).

5. Dependence Inversion Principle (DIP)

High-level modules should not depend on low-level modules, both should depend on their abstractions. Abstractions should not depend on details, and details should depend on abstractions.

The most important issue of the Dependency Inversion Principle is to ensure that the main components of an application or framework are decoupled from the implementation details of non-critical low-level components. This will ensure that the most important parts of the program are not affected by changes in low-level components.

In JavaScript, the applicability of the Dependency Inversion Principle is limited to semantic coupling between high-level modules and low-level modules. For example, DIP can add interfaces as needed instead of coupling implicit interfaces defined by low-level modules.

 $.fn.trackMap = function(options) {
    var defaults = {
        /* defaults */
    };
    options = $.extend({}, defaults, options);

    var mapOptions = {
        center: new google.maps.LatLng(options.latitude,options.longitude),
        zoom: 12,
        mapTypeId: google.maps.MapTypeId.ROADMAP
    },
        map = new google.maps.Map(this[0], mapOptions),
        pos = new google.maps.LatLng(options.latitude,options.longitude);

    var marker = new google.maps.Marker({
        position: pos,
        title: options.title,
        icon: options.icon
    });

    marker.setMap(map);

    options.feed.update(function(latitude, longitude) {
        marker.setMap(null);
        var newLatLng = new google.maps.LatLng(latitude, longitude);
        marker.position = newLatLng;
        marker.setMap(map);
        map.setCenter(newLatLng);
    });

    return this;
};

var updater = (function() {
    // private properties

    return {
        update: function(callback) {
            updateMap = callback;
        }
    };
})();

$("#map_canvas").trackMap({
    latitude: 35.044640193770725,
    longitude: -89.98193264007568,
    icon: 'http://bit.ly/zjnGDe',
    title: 'Tracking Number: 12345',
    feed: updater
});

In the above code, a small JS library converts a DIV into a Map to display the currently tracked location information. The trackMap function has two dependencies: the third-party Google Maps API and the Location feed. The responsibility of the feed object is to call a callback (provided during initialization) when the icon position is updated and pass in the latitude and longitude. The Google Maps API is used to render the interface.

The interface of the feed object may or may not be designed according to the requirements of the trackMap function. In fact, its role is very simple, focusing on simple different implementations, and does not need to be so dependent on Google Maps. Since trackMap is semantically coupled to the Google Maps API, if you need to switch between different map providers, you have to rewrite the trackMap function to adapt to different providers.

In order to reverse the semantic coupling of the Google maps class library, we need to rewrite the design of the trackMap function to semantically couple an implicit interface (the interface that abstracts the map provider provider), and we also need an API that adapts to Google Maps An implementation object of , as follows is the refactored trackMap function:

 $.fn.trackMap = function(options) {
    var defaults = {
        /* defaults */
    };

    options = $.extend({}, defaults, options);

    options.provider.showMap(
        this[0],
        options.latitude,
        options.longitude,
        options.icon,
        options.title);

    options.feed.update(function(latitude, longitude) {
        options.provider.updateMap(latitude, longitude);
    });

    return this;
};

$("#map_canvas").trackMap({
    latitude: 35.044640193770725,
    longitude: -89.98193264007568,
    icon: 'http://bit.ly/zjnGDe',
    title: 'Tracking Number: 12345',
    feed: updater,
    provider: trackMap.googleMapsProvider
});

In this version, we redesigned the trackMap function and a required map provider interface, and then moved the implementation details to a separate googleMapsProvider component, which may be independently packaged into a separate JavaScript module. Here is my googleMapsProvider implementation:

 trackMap.googleMapsProvider = (function() {
    var marker, map;

    return {
        showMap: function(element, latitude, longitude, icon, title) {
            var mapOptions = {
                center: new google.maps.LatLng(latitude, longitude),
                zoom: 12,
                mapTypeId: google.maps.MapTypeId.ROADMAP
            },
                pos = new google.maps.LatLng(latitude, longitude);

            map = new google.maps.Map(element, mapOptions);

            marker = new google.maps.Marker({
                position: pos,
                title: title,
                icon: icon
            });

            marker.setMap(map);
        },
        updateMap: function(latitude, longitude) {
            marker.setMap(null);
            var newLatLng = new google.maps.LatLng(latitude,longitude);
            marker.position = newLatLng;
            marker.setMap(map);
            map.setCenter(newLatLng);
        }
    };
})();

After making the above changes, the trackMap function will become very flexible. It does not have to rely on the Google Maps API. On the contrary, other map providers can be replaced at will, that is to say, any map provider can be adapted according to the needs of the program.


看见了
876 声望16 粉丝

前端开发,略懂后台;