4

俗话说,一个模式三个坑。 中介者模式应该算最坑的一个模式,坑不在于他的原理。而在于他的名字和其他模式的使用,真尼玛像。首先,“中介者“ 好像是一切模式里面都有的一个东西,比如,享元模式中-元对象,订阅发布模式中-全局监听Event... 但是,这个模式偏偏又叫做中介者模式(哎,曾经说模式的时候,感觉什么都是中介者模式)。

所以,这里我们首先要攻克的难关就是,中介者模式的features。

中介者模式

首先,我们需要回忆一下使用订阅发布模式中,是怎样一个场景

恩,中间需要有一个连接节点,即,发布者只需要和连接者有关联,而订阅者同样也只需要和连接者有关联。 所以,这个点就是中介者模式最独特的feature. 下图可以清楚的看出,中介者模式的特点。

知道了吧,中介者模式最突出的就是,由中介者来掌管一切,比订阅者中的连接节点的地位好像就是爸爸和儿子的关系。

中介者模式的实现

在发布订阅模式中有着全局对象Event的管理,那中介者模式中的boss应该怎样表达呢?

首先,我们需要说明一下,中介者模式主要的应用场景是什么。

  1. 有大量相互关联的对象

  2. 每个对象都能改变状态

  3. 你写的代码比较烂

差不多中介者模式能够解决以上的问题。

徒儿,给师傅去要个栗子来。
好的,师傅!!!

栗子来了:

大家好,(我假装我是一名学霸) 同学们可能经常去的地方,应该就是图书馆了,经常会去图书管理处借一些书来看,比如: 藏地密码,阿弥陀佛么么哒...等等。我们凭借着尽职的图书馆阿姨,往往可以借到我们想要的书本。如果已经被借走了,还可以在阿姨那里登记,并且如果书还回来的,会通知你过来取。

恩,总结一下:

图书馆阿姨其实就是一个中介者,我们找书,都是通过图书管理处询问,然后他们负责给我们查询。 如果没有这个管理处的话,就像我们平时一样,在群里问问,"大家有xxx书吗?能借我看两天吗?"。当然,这样往往会石沉大海。

我们用代码模拟一下:

//我们先假设图书管理系统只有借和还的功能
//个人
function Person(name){
    this.name = name;
}
Person.prototype.lend = function(bookName){
    Manager.assign('lend',this,bookName);
}
Person.prototype.back = function(bookName){
    Manager.assign('back',this,bookName);
}
//创建一个工厂模式
var peopleFactory = (function(){
    var people = {};
    return function(name){
        var person = people[name];
        if(person){
            return person;
        }
        person = new Person(name);
        people[name] = person;
        return person;
    }
})();
//中介者,图书管理处
var Manager = (function(){
    var lendList = {},
        stock = {},
        operations = {};
    operations.lend = function(person,bookName){
        var num = stock[bookName];

        if(num===undefined){  //判断是否有书
            console.log("图书馆没有该书");
            return;
        }
        if(num===0){  //书本已经借完
            console.log("该书已经借完");
            return;
        }
        stock[bookName]--;  //将数量减一
        lendList[person.name] = bookName;
        console.log('借阅成功');
    }
    operations.back = function(person,bookName){
        var bookName = lendList[person.name];  //返回借书人借的书名
        if(bookName === null){
            throw "该人,并没有借书";
        }
        stock[bookName]++;  //还书
        lendList[person.name] = null;  //将借书人的清空
    }
    operations.addStock = function(){  //初始库存,addStock({bookName:jimmy,num:2})
        for(var i = 0,book;book = arguments[i++];){
            stock[book.bookName] = book.num;
        }
    }
    var assign = function(){
        var order = Array.prototype.shift.call(arguments);
        operations[order].apply(this,arguments);
    }
    return {
        assign
    }
})();
Manager.assign('addStock',{bookName:"藏地密码",num:1},{bookName:"阿弥陀佛么么哒",num:3});
var xiaoMing = peopleFactory("xiaoMing");
var jimmy = peopleFactory("jimmy");
var hanMM = peopleFactory("hanMM");
xiaoMing.lend("藏地密码");
jimmy.lend("藏地密码");
//还书的过程
xiaoMing.back("藏地秘密");
jimmy.lend('藏地密码');  //终于借成功了

以上是一个简单的中介者模式的缩影,要始终记着那张图代表的内涵,中介者模式是不需要在将请求传递出去的(或者说,情感上没有传递出去)。

中介者模式的现实意义

上面意淫了一个图书管理处(实话说,没有卵用). 我们来个真的。

徒儿,给师傅找个栗子。
好,师傅!!!

我们要学习帝吧人民,进能打td,退能刷淘宝。 我们这里不说淘宝的事,但说一个电子商务的事。 现在Mooc这么火,各种线上课程也是numberous。我也上过。 一个线上的课程需要购买,购买的流程基本上就是,选择你想要的课程,然后,选择你要上课的人数(你是一个妈妈,你可以给你两个孩纸各买一个ID),如果上课人数未满的话,恭喜,你在家等开课就over了。如果课程满的话,你要么等下一期,要么,直接找另外一门课。

如果使用,面向过程的思维写的话,我相信这个,不是一般的复杂。

所以,我们使用面向对象的思维重构一下。

首先,我们得拿到课程的数据,比如,课程名,已经报的课程人数,课程价格等。当我们选择一个课程的时候,界面上肯定需要作出相应的处理,比如,渲染课程价格,剩余人数。但我们添加购买人数的时候,如果未超出,则可以购买,如果超出,则需要将购买的按钮改为不可选中状态。

恩,大致过程就是这样,我们使用中介者模式想一想。

首先,数据需要放在中介者模式内,用户的一切操作,都会传递给中介者模式,由他来选择是哪一个html部分发生改变。

好,我们用代码实现以下。

课程购买

上面是整个逻辑和页面,这里我主要针对js说明一下。

(function() {
    console.dir($(".courses"))
    bind($(".courses"), function(e) { //课程内容改变时
        mediator.command(e.target);
    }, 'change');
    bind($(".num"), function(e) { //报名人数改变时
        mediator.command(e.target);
    }, 'keyup');

    bind($(".buy"), function(e) { //绑定够买函数
        mediator.command(e.target);
    }, "click");

    var utils = (function() { //工具函数
        var change = function(ele, val) { //改变内容
            ele.innerHTML = val;
        }
        return {
            change
        };
    })();



    var mediator = (function() { //中介者
        var price = $(".price"), //课程价格
            remainder = $('.remainder'), //剩余人数
            num = $(".num"), //购买人数
            buyBtn = $('.buy'), //购买btn
            course;
        var changePR = function(courseName) { //改变价格和人数
            course = data[courseName];
            console.log(course);
            utils.change(price, course.price); //改变价格
            utils.change(remainder, course.remainder); //改变人数
        }
        var prohitBtn = (function() { //改变购买btn状态
            var use = `<button class="shoppingBtn">购买</button>`,
                ban = `<button class="prohit" disabled>人数已满</button>`,
                status = true;
            return function(flag) {
                if (status === flag) { //如果状态不变,则不改变内容
                    return;
                }
                if (flag === true) { //可以购买
                    buyBtn.innerHTML = use;
                    status = true;
                } else { //禁止购买
                    buyBtn.innerHTML = ban;
                    status = false;
                }
            }
        })();
        var detect = function() { //检测购买输入的内容
                var number = Number(num.value.trim());
                if (!course) {
                    alert("请先选择课程");
                    return;
                }
                if (number > course.remainder) {
                    prohitBtn(false); //不能够买
                }  else {
                    prohitBtn(true); //可以够买
                }
            }
            //当课程类改变时,触发改变名额,价格以及根据购买人数改变购买Btn的状态
        var coursesChange = function(courseName) {
            //改变价格和人数
            changePR(courseName);
            //根据input框的值,改变btn的状态
            detect();
        }
        var detectBuy = function() {
            var number = Number(num.value.trim());
            if (number === null || number == 0) {
                alert('请先输入购买数量~');
            } else {
                alert("购买成功");
            }
        }
        var command = function(target) {
            var classList = target.classList;
            if (classList.contains('courses')) {
                console.dir(target.value);
                var val = target.value;
                //执行
                coursesChange(val);
            } else if (classList.contains('num')) {
                var val =target.value.trim(),
                    reg = /\d+/;
                if (!reg.test(val)) {
                    alert("输入内容只能为数字");
                    return;
                }
                //执行
                detect();
            } else if(classList.contains("shoppingBtn")){
                detectBuy();
            }
        }
        return {
            command
        }
    })();
})();

可以从上面的代码看出,比较清晰的将沟壑关系解除,上面的中介者模式中也会用到代理模式等其他相关的知识。当然,这段代码并不是特别好,关键在于处理的逻辑较多,有大量的if判断语句,所以也希望读者,可以使用以前所学的js模式进行重构,我相信到时候你的代码整洁程度一定远优于鄙人写的代码。

浅说中介者模式

其实,中介者模式是我最喜欢使用的模式之一,因为他好写易上手。但是缺点也是显而易见的,就是,你会在程序中增加一个巨大的对象,而你的噩梦就是维护这个对象。 中介者里面会包含大量的逻辑,设计较多的节点获取,造成的维护难度也是显而易见的。所以,还是那句话,不要为了模式而模式,这个世界上没有万能的模式,就和没有绝对安全的系统一样。

ending~


villainhr
7.8k 声望2.2k 粉丝