设计模式

作者: shaokang 时间: July 4, 2023字数:8264

MVC、MVP、MVVM 模式

MVC、MVP 和 MVVM 是三种常见的软件设计模式,它们主要用于构建用户界面和分离业务逻辑。

MVC(Model-View-Controller)

模型(Model):负责处理数据和业务逻辑。

视图(View):负责展示信息给用户。

控制器(Controller):接收用户的输入并调用模型或视图的更新方法。

在 MVC 模式中,用户操作通常由视图(View)接收,然后传递给控制器(Controller)处理。Controller 根据业务逻辑,更新模型数据(Model)。View 和 Model 应用了观察者模式,当 Model 层发生改变的时候它会通知有关 View 层更新页面。

// Model:管理数据和业务逻辑,并通知观察者(视图)
class Model {
    constructor() {
        this.data = 0;
        this.observers = []; // 存放观察者
    }

    // 添加观察者
    addObserver(observer) {
        this.observers.push(observer);
    }

    // 移除观察者
    removeObserver(observer) {
        this.observers = this.observers.filter(obs => obs !== observer);
    }

    // 通知所有观察者
    notifyObservers() {
        this.observers.forEach(observer => observer.update(this));
    }

    increment() {
        this.data++;
        this.notifyObservers(); // 数据变化后,通知观察者
    }

    decrement() {
        this.data--;
        this.notifyObservers(); // 数据变化后,通知观察者
    }

    getData() {
        return this.data;
    }
}

class View {
    constructor() {
        this.display = document.getElementById('display');
        this.incrementButton = document.getElementById('increment');
        this.decrementButton = document.getElementById('decrement');
    }

    bindClick(controller) {
        this.incrementButton.addEventListener('click', () => {
            controller.increment();
        });
        this.decrementButton.addEventListener('click', () => {
            controller.decrement();
        });
    }

    // 当模型变化时,模型调用此方法,传入自身
    update(model) {
        this.render(model.getData());
    }

    render(data) {
        this.display.innerText = data;
    }
}

class Controller {
    constructor(model, view) {
        this.model = model;
        this.view = view;

        // 将视图注册为模型的观察者
        this.model.addObserver(this.view);

        // 让视图知道控制器,以便通知用户交互
        this.view.bindClick(this);

        // 初始化视图显示
        this.view.render(this.model.getData());
    }

    handleIncrement() {
        this.model.increment();
    }

    handleDecrement() {
        this.model.decrement();
    }
}

const app = new Controller(new Model(), new View());

MVP(Model-View-Presenter)

模型(Model):与 MVC 中的相同。

视图(View):与 MVC 中的相同,但通常包含一个接口来定义如何与 Presenter 交互。

演示者(Presenter):充当 View 与 Model 的中间人,它封装了所有与用户交互相关的代码,并将这些操作转换为对模型的请求。

在 MVP 模式中,视图不直接与模型交互,而是通过演示者来处理。这使得代码更加模块化,易于测试和维护。

class Model {
    constructor() {
        this.data = 0;
    }

    increment() {
        this.data++;
    }

    decrement() {
        this.data--;
    }

    getData() {
        return this.data;
    }
}

class View {
    constructor(presenter) {
        this.display = document.getElementById('display');
        this.incrementButton = document.getElementById('increment');
        this.decrementButton = document.getElementById('decrement');

        this.incrementButton.addEventListener('click', () => {
            presenter.handleIncrement();
        });
        this.decrementButton.addEventListener('click', () => {
            presenter.handleDecrement();
        });
    }

    render(data) {
        this.display.innerText = data;
    }
}

class Presenter {
    constructor(model, view) {
        this.model = model;
        this.view = view;
    }

    handleIncrement() {
        this.model.increment();
        this.view.render(this.model.getData());
    }

    handleDecrement() {
        this.model.decrement();
        this.view.render(this.model.getData());
    }
}

const app = new Presenter(new Model(), new View(app));

MVVM(Model-View-ViewModel)

模型(Model):与 MVC/MVP 中的相同。

视图(View):与 MVC/MVP 中的相同,但它通常是双向绑定的,即当数据变化时自动更新视图,反之亦然。

视图模型(ViewModel):作为连接视图和模型的桥梁,它将视图的状态映射到模型

单例模式

单例模式的定义是: 保证一个类仅有一个实例,并提供一个访问它的全局访问点。

var Singleton = function (name) {
    this.name = name;
    this.instance = null;
};
Singleton.prototype.getName = function () {
    alert(this.name);
};
Singleton.getInstance = function (name) {
    if (!this.instance) {
        this.instance = new Singleton(name);
    }
    return this.instance;
};
var a = Singleton.getInstance('sven1');
var b = Singleton.getInstance('sven2');
alert(a === b); // true

或者:

var Singleton = function (name) {
    this.name = name;
};
Singleton.prototype.getName = function () {
    alert(this.name);
};
Singleton.getInstance = (function () {
    var instance = null;
    return function (name) {
        if (!instance) {
            instance = new Singleton(name);
        }
        return instance;
    };
})();

惰性单例:合适的时候才创建对象,且只创建唯一的。

var getSingle = function (fn) {
    var result;
    return function () {
        return result || (result = fn.apply(this, arguments));
    };
};

策略模式

策略模式的定义是: 定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。

看一个例子:绩效为 S 的人年终奖有 4 倍工资,绩效为 A 的人年终奖有 3 倍工资,而绩效为 B 的人年终奖是 2 倍工资。写一段代码,计算员工的年终奖。

将每一种计算方式定义成单独的函数

var strategies = {
    S: function (salary) {
        return salary * 4;
    },
    A: function (salary) {
        return salary * 3;
    },
    B: function (salary) {
        return salary * 2;
    },
};

var calculateBonus = function (level, salary) {
    return strategies[level](salary);
};
console.log(calculateBonus('S', 20000));
console.log(calculateBonus('A', 10000));

再看一个例子:用策略模式来重构表单校验。

// 定义校验规则
var strategies = {
    isNonEmpty: function (value, errorMsg) {
        if (!value) {
            return errorMsg;
        }
    },
    minLength: function (value, length, errorMsg) {
        if (value.length < length) {
            return errorMsg;
        }
    },
    isMobile: function (value, errorMsg) {
        if (!/^1[34578]\d{9}$/.test(value)) {
            return errorMsg;
        }
    },
};

// 校验函数
var Validator = function () {
    this.cache = [];
};

Validator.prototype.add = function (dom, rule, errorMsg) {
    var ary = rule.split(':');
    this.cache.push(function () {
        var strategy = ary.shift();
        ary.unshift(dom.value);
        ary.push(errorMsg);
        return strategies[strategy].apply(dom, ary);
    });
};

Validator.prototype.start = function () {
    for (var i = 0, validatorFunc; (validatorFunc = this.cache[i++]); ) {
        var msg = validatorFunc();
        if (msg) {
            return msg;
        }
    }
};

// 校验表单
var v = new Validator();
v.add(document.getElementById('username'), 'isNonEmpty', '用户名不能为空');
v.add(
    document.getElementById('username'),
    'minLength:3',
    '用户名长度不能小于 3 位'
);
v.add(document.getElementById('phoneNumber'), 'isMobile', '手机号码格式不正确');

优缺点

优点:

  • 策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句。
  • 策略模式提供了对开放—封闭原则的完美支持,将算法封装在独立的 strategy 中,使得它们易于切换,易于理解,易于扩展。
  • 策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复的复制粘贴工作。
  • 在策略模式中利用组合和委托来让 Context 拥有执行算法的能力,这也是继承的一种更轻便的替代方案。

缺点:

  • 策略模式将造成系统产生很多具体策略类,任何细小的变化都将导致系统要增加一个新的具体策略类。
  • 必须知道所有的策略类,并自行决定使用哪一个策略类,违反最少知识原则。

代理模式

代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。

虚拟代理实现图片预加载:

var myImage = (function () {
    var imgNode = document.createElement('img');
    document.body.appendChild(imgNode);
    return function (src) {
        imgNode.src = src;
    };
})();

var proxyImage = (function () {
    var img = new Image();
    img.onload = function () {
        myImage(img.src);
    };
    return function (src) {
        myImage('loading.gif');
        img.src = src;
    };
})();
proxyImage('1.jpg');

虚拟代理实现 console

var miniConsole = (function () {
    var cache = [];
    var handler = function (ev) {
        if (ev.keyCode === 113) {
            var script = document.createElement('script');
            script.onload = function () {
                for (var i = 0, fn; (fn = cache[i++]); ) {
                    fn();
                }
            };
            script.src = 'miniConsole.js';
            document.getElementsByTagName('head')[0].appendChild(script);
            document.body.removeEventListener('keydown', handler); // 只加载一次 miniConsole.js
        }
    };
    document.body.addEventListener('keydown', handler, false);
    return {
        log: function () {
            var args = arguments;
            cache.push(function () {
                return miniConsole.log.apply(miniConsole, args);
            });
        },
    };
})();
miniConsole.log(11); // miniConsole.js 代码
miniConsole = {
    log: function () {
        console.log(Array.prototype.join.call(arguments));
    },
};

为各种计算方式创建缓存代理

/**************** 计算乘积 *****************/
var mul = function () {
    var a = 1;
    for (var i = 0, len = arguments.length; i < len; i++) {
        a *= arguments[i];
    }
    return a;
};

/**************** 计算加和 *****************/
var plus = function () {
    var a = 0;
    for (var i = 0, len = arguments.length; i < len; i++) {
        a += arguments[i];
    }
    return a;
};