关于设计模式:设计模式设计原则

4次阅读

共计 5221 个字符,预计需要花费 14 分钟才能阅读完成。

设计准则(SOLID 准则)

在程序设计畛域,SOLID(繁多性能、开闭准则、里氏替换、接口隔离以及依赖反转)是由罗伯特·C·马丁在 21 世纪晚期引入,指代了面向对象编程和面向对象设计的五个根本准则。当这些准则被一起利用时,它们使得一个程序员开发一个容易进行软件维护和扩大的零碎变得更加可能。

1. 繁多职责准则(SRP)

就一个类而言,应该仅有 一个 引起它变动的起因。在 JavaScript 中,须要用到类的场景并不太多,繁多职责准则更多地是被使用在对象或者办法级别上。

如果咱们有两个动机去改写一个办法,那么这个办法就具备两个职责。每个职责都是变动的一个轴线,如果一个办法承当了过多的职责,那么在需要的变迁过程中,须要改写这个办法的可能性就越大。
简略说就是:一个对象(办法)只做一件事。

1.1 优缺点

  • 长处:升高了单个类或对象复杂度,依照职责把对象划分为更小粒度,有利于代码复用和单元测试。
  • 毛病:减少编写代码复杂度,对象划分为更小粒度,对象间接的分割也变得更简单。

2. 凋谢 - 关闭准则(OCP)

凋谢 - 关闭准则最早由 Eiffel 语言的设计者 Bertrand Meyer 在其著述 Object-Oriented Software Construction 中提出。它的定义如下:

软件实体(类、模块、函数)等应该对扩大凋谢,然而批改关闭。

拓展 window.onload 函数


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("拓展的代码")
})

通过动静装璜函数的形式,间接拓展了新的函数,而不是间接批改之前的 onload 相干代码。

3. 里氏替换准则(The Liskov Substitution Principle,LSP)

定义如下:

子类的设计要保障在替换父类的时候,不扭转原有程序的逻辑以及不毁坏原有程序
的正确性

矩形例子:

// 思考咱们有一个程序用到上面这样的一个矩形对象:
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);
};

该办法在调用的时候,后果是 16,而不是冀望的 12,咱们的正方形 square 对象 违反了 LSP 准则,square 的长度和宽度属性暗示着并不是和矩形 100% 兼容,但咱们并不总是这样明确的暗示。

里氏替换准则(LSP)表白的意思不是继承的关系,而是任何办法(只有该办法的行为能领会另外的行为就行)。

4. 接口隔离准则(ISP)

定义:

Clients should not be forced to depend on methods they do not use.
客户端不应该依赖它不须要的接口。用多个细粒度的接口来代替由多个办法组成的简单接口,每个接口服务于一个子模块

类 A 通过接口 interface 依赖类 C,类 B 通过接口 interface 依赖类 D,如果接口 interface 对于类 A 和类 B 来说不是最小接口(胖接口),则类 C 和类 D 必须去实现他们不须要的办法。

简略说就,建设繁多业余接口,依照性能职责细化接口,接口中的办法尽量少。

例子:

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

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

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

当一个 rectangle 替代品为了满足新对象 geometryApplication 的 getLargestRectangle 的时候,它仅仅须要 rectangle 的 area()办法,但它却违反了 LSP(因为他基本用不到其中 drawRectangles 办法能力用到的 draw 办法)。

5. 依赖倒置准则(Dependence Inversion Principle, DIP)

高层模块不应该依赖底层模块,二者都该依赖其形象。形象不应该依赖细节,细节应该依赖形象。

依赖倒置准则的最重要问题就是确保应用程序或框架的次要组件从非重要的底层组件实现细节解耦进去,这将确保程序的最重要的局部不会因为低层次组件的变动批改而受影响。

在 JavaScript 里,依赖倒置准则的适用性仅仅限于高层模块和低层模块之间的语义耦合,比方,DIP 能够依据须要去减少接口而不是耦合低层模块定义的隐式接口。

$.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
});

在上述代码里,有个小型的 JS 类库将一个 DIV 转化成 Map 以便显示以后跟踪的地位信息。trackMap 函数有 2 个依赖:第三方的 Google Maps API 和 Location feed。该 feed 对象的职责是当 icon 地位更新的时候调用一个 callback 回调(在初始化的时候提供的)并且传入纬度 latitude 和精度 longitude。Google Maps API 是用来渲染界面的。

feed 对象的接口可能依照装,也可能没有照装 trackMap 函数的要求去设计,事实上,他的角色很简略,着重在简略的不同实现,不须要和 Google Maps 这么依赖。介于 trackMap 语义上耦合了 Google Maps API,如果须要切换不同的地图提供商的话那就不得不对 trackMap 函数进行重写以便能够适配不同的 provider。

为了将于 Google maps 类库的语义耦合翻转过去,咱们须要重写设计 trackMap 函数,以便对一个隐式接口(形象出地图提供商 provider 的接口)进行语义耦合,咱们还须要一个适配 Google Maps API 的一个实现对象,如下是重构后的 trackMap 函数:

$.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
});

在该版本里,咱们从新设计了 trackMap 函数以及须要的一个地图提供商接口,而后将实现的细节挪到了一个独自的 googleMapsProvider 组件,该组件可能独立封装成一个独自的 JavaScript 模块。如下是我的 googleMapsProvider 实现:


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);
        }
    };
})();

做了上述这些扭转当前,trackMap 函数将变得十分有弹性了,不用依赖于 Google Maps API,相同能够任意替换其它的地图提供商,那就是说能够依照程序的需要去适配任何地图提供商。

正文完
 0