S.O.L.I.D五大原则- 深入了解javascript

/*
    S.O.L.I.D五大原则
    1. 单一原则
    2. 开闭原则
    3. 里氏替换原则
    4. 接口分离原则
    5. 依赖反转原则
*/
/*单一原则:类发生更改的原因只有一个
    如何知道一个对象的多个行为构造多个职责或单个职责?
    判断标准:
    1. 存储对象:提供对象信息给其他对象
    2. 维护对象:维护对象和信息之间的关系
    3. 服务对象:处理工作并提供服务给其他对象
    4. 控制对象:控制决策一系列负责的任务处理
    5. 协调对象:不做处理工作,只是delegate工作到其他对象
    5. 接口对象:在系统各个部分转化信息(或请求)
*/
//商品实体,有ID和描述
function Product(id, desc) {
    this.getId = function () {
        return id;
    }
    this.getDesc = function () {
        return desc;
    }
}
//购物车,添加清单
function Cart(evetAggregator) {
    var items = [];
    this.addItem = function (item) {
        items.push(item);
    }
}
(function () {
    //初始化一系列商品
    var products = [new Product(1, "a"), new Product(2, "b"), new Product(3, "c")],
        cart = new Cart();
    //商品双击后获取ID,将商品添加到CART中
    function addToCart() {
        var productId = $(this).attr('id');
        var product = $.grep(products, function (x) {
            return x.getId() == productId;
        })[0];
        cart.addItem(product);

        var newItem = $('<li></li>').html(product.getDesc()).attr('id-cart', product.getId()).apendTo('#cart');
    }
    //将商品遍历,初始化事件,添加到商品集合中
    products.forEach(function (prodcuct) {
        var newItem = $('<li></li>').html(product.getDesc()).attr('id', product.getId()).dbclick(addToCart).apendTo("#products");
    });
}());
/*
    如上,职责分析
    1. 有products的声明
    2. 将product集合绑定到#products中,同时添加购物车事件处理
    3. 有购物车展示功能
    4. 添加产品到购物车并显示功能
*/
/*
    采用事件聚合理论(event aggreagator)优化,分为2部分
    1. Event,用于Handler回调代码
    2. EventAggregator,订阅和发布事件
*/
function Event(name) {
    var handlers = [];
    this.getName = function () {
        return name;
    };
    this.addHandler = function (handler) {
        handlers.push(handler);
    };
    this.removeHandler = function (handler) {
        for (var i = 0; i < handlers.length; i++) {
            if (handlers[i]==handler) {
                handlers.slice(i, 1);
                break;
            }
        }
    };
    this.fire = function (eventArgs) {
        handlers.forEach(function (h) {
            h(eventArgs);
        });
    }
}

function EventAggregator() {
    var events = [];
    function getEvent(eventName) {
        return $.grep(events, function (x) {
            return event.getName() == eventName;
        })[0];
    }
    this.publish = function (eventName, eventArgs) {
        var event = getEvent(eventName);

        if (!event) {
            event = new Event(eventName);
            events.push(event);
        }
        event.fire(eventArgs);
    }
    this.subscribe = function (eventName,handler) {
        var event = getEvent(eventName);
        if (!event) {
            event = new Event(eventName);
            events.push(event);
        }
        event.addHandler(handler);
    }
}
//修改Cart
function Cart(eventAggregator) {
    var items = [];
    this.addItem = function (item) {
        items.push(item);
        eventAggregator.publish('itemAdd', item);
    }
}
//新增CartController,接收cart对象的事件聚合器,通过订阅itemAdd来增加一个li节点,通过订阅productSelected事件来添加product
function CartController(cart, eventAggregator) {
    eventAggregator.subscribe('itemAdd', function(eventArgs) {
        var newItem = $('<li></li>').html(eventArgs.getDesc()).attr('id-cart', eventArgs.getId()).appendTo("#cart");
    });
    eventAggregator.subscribe("productSelected", function (eventArgs) { cart.addItem(eventArgs.product); });
}
//新增repository:ajax获取数据,暴露get方法
function ProductRepository() {
    var products = [new Product(1, "a"), new Product(2, "b"), new Product(3, "c")];
    this.getProducts = function () {
        return products;
    }
}
//新增ProductController: 定义OnProductSelect方法,主要发布触发ProductSelected事件,forEach主要用于绑定数据到产品列表上
function ProductController(eventAggregator,productRepository) {
    var products = productRepository.getProducts();
    function onProductSelected() {
        var productId = $(this).attr('id');
        var product = $.grep(products, function (x) {
            return x.getId() == productId;
        })[0];
        eventAggregator.publish('productSelected', { product: product });
    }
    products.forEach(function (product) {
        var newItem = $('<li></li>').html(product.getDesc()).attr('id', product.getId()).dblclick(onProductSelected).appendTo("#products");
    });
}
//最后声明匿名函数
(function () {
    var eventAggregator = new EventAggregator(),
        cart = new Cart(eventAggregator),
        cartController = new CartController(cart, eventAggregator),
        productRepository = new ProductRepository(),
        productController = new ProductController(eventAggregator, productRepository);

}());

/*
    开闭原则:软件开发对外扩展开发,对内修改关闭,即在不修改的前提下扩展
*/
//一、问题代码
//问题类型
var AnswerType = {
    Choice: 0,
    Inut: 1
}
//定义问题实体
function question(label, answerType, choices) {
    return {
        label: label,
        answerType: answerType,
        choices: choices
    };
}
var view = (function () {
    function renderQuestion(target, question) {
        //创建最外层DIV
        var questionWrapper = document.createElement("div");
        var answer = document.createElement('div');
        //当问题为下拉选择模式
        if (question.answerType == AnswerType.Choice) {
            var input = document.createElement('select');
            var len = question.choices.length;
            for (var i = 0; i < len; i++) {
                var option = document.createElement('option');
                option.text = question.choices[i];
                option.value = question.choices[i];
                input.appendChild(option);
            }
        }
        else if (question.answerType == AnswerType.Inut) {
            var input = document.createElement('input'); input.type = 'text';
        }
        answer.appendChild(input);
        questionWrapper.appendChild(questionLabel);
        questionWrapper.appendChild(answer);
        target.appendChild(questionWrapper);
    }
    // 遍历所有的问题列表进行展示
    return {
        rendeer: function (target, questions) {
            for (var i = 0; i < questions.length; i++) {
                renderQuestion(target, questions[i]);
            }
        }
    }
}());
/*
    问题代码分析:
        违背开闭原则,如果增加一个question类型,则需要再次修改renderQuestion函数
*/
//choiceQuestionCreator函数和inputQuestionCreator函数分别对应下拉菜单和input输入框的renderInput实现,通过内部调用统一的questionCreator(spec, my)然后返回that对象(同一类型哦)。
function questionCreator(spec, my) {
    var that = {};
    my = my || {};
    my.label = spec.label;
    my.renderInput = function () {
        throw "not implemented";
    }
    that.render = function (target) {
        var questionWrapper = document.createElement("div");
        var answer = my.renderInput();
        questionWrapper.appendChild(answer);
        return questionWrapper;
    }
    return that;
}
function choiceQuestionCreator(spec, my) {
    var my = {},
        that = questionCreator(spec, my);
    //实现renderInput
    my.renderInput = function () {
        var input = document.createElement('select');
        var len = spec.choices.length;
        for (var i = 0; i < len; i++) {
            var option = document.createElement('option');
            option.text = spec.choices[i];
            option.value = spec.choices[i];
            input.appendChild(option);
        }
        return input;
    }
    return that;
}
function inputQuestionCreator(spec, my) {
    var my = {},
        that = questionCreator(spec, my);
    // input类型的renderInput实现    
    my.renderInput = function () {
        var input = document.createElement('input');
        input.type = 'text';
        return input;
    };
    return that;
}
var view = {
    render: function (target, questions) {
        for (var i = 0; i < questions.length; i++) {
            target.appendChild(question[i].render());
        }
    }
}
var question = [
    choiceQuestionCreator({ label: '', choices: ['yes', 'no'] }),
    inputQuestionCreator({ label: '' })
];
var questionRegion = document.getElementById('questions');
view.render(questionRegion, questions);
/*
    1. 首先,questionCreator采用模板方法模式将处理问题的功能delegate给每个问题类型扩展代码renderInput上
    2. 其次,用spec替换替换label、choices,防止属性暴露给外部代码
    3. 然后,每个实现都必须含有renderInput覆盖原有的questionCreator方法里的renderInput代码:策略模式
*/

/*
    里氏替换原则:派生类必须可以替换他的基类型
*/
//一、定义Vehicle函数,提供基本操作
function Vehicle(my) {
    var my = my || {};
    my.speed = 0;
    my.running = false;
    this.speed = function () {
        return my.speed;
    }
    this.start = function () {
        my.running = true;
    }
    this.stop = function () {
        my.running = false;
    }
    this.accelerate = function() {        
        my.speed++;    
    };    
    this.decelerate = function () {
        my.speed--;
    }
    return my;
}
//FastVehicle违背里氏替换原则因为加减速的数据不一样。子类已非等价于基类的期望行为
function FastVehicle(my) {
    var my = my || {};
    var that = new Vehicle(my);
    that.accelerate = function () {
        my.speed += 3;
    };
    return that;
}
/*
    二、减少LSP妨碍
    1. 使用契约
        a.检查使用TDD来指导代码设计
        b.设计可重用类库时候可随意使用契约设计技术
    2. 避免继承,多组合
*/
// 三、与行为有关,而不是继承
// LSP本质是:行为兼容性,并非继承


/*
    接口隔离原则 Interface Segregation Principle
    不应该强迫用户依赖于他们不同的方法
*/
//  一、JS没有接口的概念,但可以当做一个contract提供约束
var myBindApply = {};
myBindApply.modelObserver = (function () {
    //私有变量
    return {
        observe: function (model) {
            /*代码*/
            return newModel
        },
        onChange: function (callback) {
            /*代码*/
        }
    };
}());
myBindApply.viewAdaptor = (function () {
    return {
        bind: function (model) {

        }
    };
}());
//公共接口是bind方法
myBindApply.bind = function (model) {
    /*私有变量*/
    myBindApply.modelObserver.onChange(function () { });
    var vm = myBindApply.modelObserver.observe(model);
    myBindApply.viewAdaptor.bind(vm);
};

/*
    依赖倒置原则 Dependency Inversion Principle
    高层模块不依赖于底层模块,二者应该依赖于抽象。
    抽象不依赖于细节,细节依赖于抽象
*/
/*
    举例:传统架构中,高层模块(封装了核心业务逻辑)总依赖一些基础点模块 ==》 依赖倒置 ==》底层模块依赖高层模块定义的接口
    传统程序持久化依赖于持久化模块API ==》 依赖倒置 ==》 核心模块需要定义持久化API接口,然后持久化模块需要实现这个API接口
*/
//在JavaScript里,依赖倒置原则的适用性仅仅限于高层模块和低层模块之间的语义耦合,比如,DIP可以根据需要去增加接口而不是耦合低层模块定义的隐式接口。
$.fn.traceMap = function (options) {
    var defaults = {};
    options = $.extend({}, defaults, options);

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

    options.fee.update(function () {
        map.setCenter();
    });
    return this;
};
var updater = (function () {
    return {
        update: function (callback) {
            updateMap = callback;
        }
    };
})();
$("#map_canvas").trackMap({ feed: updater });
//trackMap函数有2个依赖:第三方的Google Maps API和Location feed

//解决方案:消除MAP依赖,提供trackMap.googleMapsProvider隐式接口(抽象出地图提供商provider接口)
$.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
    });
//配合MaP API的实现对象
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);
            }
        };
    })();
/*
    何时依赖注入?
    依赖注入是控制反转的一个特殊形式,反转的意思一个组件如何获取它的依赖。
    依赖注入的意思就是:依赖提供给组件,而不是组件去获取依赖,
                        意思是创建一个依赖的实例,通过工厂去请求这个依赖,通过Service Locator或组件自身的初始化去请求这个依赖。
    依赖倒置原则和依赖注入都是关注依赖,并且都是用于反转。不过,依赖倒置原则没有关注组件如何获取依赖,而是只关注高层模块如何从低层模块里解耦出来。
    某种意义上说,依赖倒置原则是控制反转的另外一种形式,这里反转的是哪个模块定义接口(从低层里定义,反转到高层里定义)。

*/