• Comments
  • Comments
  • Comments
  • Comments
  • Comments
  • Comments
  • Comments

23种设计模式的完整例子和分类

本文是对国内外大学教材和专业出版书籍的自主学习和总结,部分代码和文字说明均出自书籍(文章中某些原创的个人见解和内容没有一一标注,请自行甄别),也可能会有自己的理解和改动,以及平时的实战积累与逻辑转化。

设计模式是可复用面向对象软件的基础,也许不知不觉从工作的那时候开始,就已经隐约使用了一些设计模式,但是它们到底是什么呢?常用的设计模式大家并不陌生,搜索引擎也可以搜索到,现在详细回顾一下23种设计模式,如何使用JavaScript实现?

先打个比方,MVC是采用模型/视图/控制器来构建用户界面,它的主要关系还是由观察者模式(用来让视图关联,可以互相影响),组合模式(视图可以嵌套),策略模式(不同的算法构成不同的视图效果)。设计模式无处不在,那么我们还是详细了解一下吧,顺便使用前端来模拟一下,加深印象。

为了更好地代码阅读,实例代码有经过我的优化和改动。

几个重要概念

设计模式的四个要素:

模式名、问题、解决方案、效果

面向对象设计的因素:

封装、粒度、依赖关系、灵活性、性能、演化、复用等。

接口:

对象声明的每一个操作指定操作名、作为参数的对象和返回值,是所谓的操作的[型构]。对象操作所定义的所有操作型构的集合被称为该对象的接口。

多态:

为不同数据类型的实现提供统一的接口。

鸭子类型:

只关心对象的行为,不关心对象本身(起源:意大利软件工程师、Python软件基金会研究员Alex Martelli 于2000年左右在Python的邮件组中最早将这个概念引入了程序设计范畴中。)

面向对象设计的原则:

  • a)针对接口编程,而不是针对实现编程
  • b) 优先使用对象组合,而不是类继承(组合和继承也经常同时使用)

分类

Singleton

● 中文名 ●

单例

● 可变的方面 ●

一个类的唯一实例(提供一个访问它的全局访问点)

● 使用场景 ●

当我创建了一个JS库,为了方便全站使用,并且有一个醒目的调用名字,可以把它导出为一个唯一的实例,这个实例可以是惰性单例(是指在需要的时候才创建,即使用new关键字),但是我常常会直接把工具类的单例new之后再导出,这样就可以在脚本中直接调用。

重要特点是每次请求只能创建一个实例。

const Singleton = (function () {
    let instance;

    function createInstance() {
        const object = new Object("I am the instance");
        return object;
    }

    return {
        getInstance: function () {
            if (!instance) {
                instance = createInstance();
            }
            return instance;
        }
    };
})();


function run() {
    const instance1 = Singleton.getInstance();
    const instance2 = Singleton.getInstance();
    console.log("Same instance? " + (instance1 === instance2));  
}
run();  // Same instance? true

Abstract Factory

● 中文名 ●

抽象工厂

● 可变的方面 ●

产品对象家族(创建相关或相互依赖的结构,无需指定具体类)

● 使用场景 ●

不在 JavaScript 中直接使用, 而是声明一个用于创建产品的接口。

function Employee(name) {
    this.name = name;

    this.say = function () {
        console.log("I am employee " + name);
    };
}

function EmployeeFactory() {

    this.create = function (name) {
        return new Employee(name);
    };
}

function Vendor(name) {
    this.name = name;

    this.say = function () {
        console.log("I am vendor " + name);
    };
}

function VendorFactory() {

    this.create = function (name) {
        return new Vendor(name);
    };
}


function run() {
    const persons = [];
    const employeeFactory = new EmployeeFactory();
    const vendorFactory = new VendorFactory();

    persons.push(employeeFactory.create("Joan DiSilva"));
    persons.push(employeeFactory.create("Tim O'Neill"));
    persons.push(vendorFactory.create("Gerald Watson"));
    persons.push(vendorFactory.create("Nicole McNight"));

    for (let i = 0, len = persons.length; i < len; i++) {
        persons[i].say();
    }
}
run();
/*
I am employee Joan DiSilva
I am employee Tim O'Neill
I am vendor Gerald Watson
I am vendor Nicole McNight
*/

Builder

● 中文名 ●

创建者

● 可变的方面 ●

如何创建一个组合对象

● 使用场景 ●

不在 JavaScript 中直接使用, builder用于声明用于创建复杂产品的多个接口(也可以说成需要多个步骤才能创建出一个复杂的产品)。

class Shop {
    init(builder) {
        builder.step1();
        builder.step2();
        return builder.get();
    }
}

function CarBuilder() {
    this.car = null;

    this.step1 = () => this.car = new Car();
    this.step2 = () => this.car.addParts();
    this.get = () => this.car;
}

function TruckBuilder() {
    this.truck = null;

    this.step1 = () => this.truck = new Truck();
    this.step2 = () => this.truck.addParts();
    this.get = () => this.truck;
}

function Car() {
    this.doors = 0;

    this.addParts = () => this.doors = 4;
    this.say = () => console.log("I am a " + this.doors + "-door car");
}

function Truck() {
    this.doors = 0;

    this.addParts = () => this.doors = 2;
    this.say = () => console.log("I am a " + this.doors + "-door truck");
}

function run() {
    const shop = new Shop();
    const carBuilder = new CarBuilder();
    const truckBuilder = new TruckBuilder();
    const car = shop.init(carBuilder);
    const truck = shop.init(truckBuilder);

    car.say();
    truck.say();
}
run();
/*
I am a 4-door car
I am a 2-door truck
*/

Factory Method

● 中文名 ●

工厂方法

● 可变的方面 ●

被实例化的子类(定义一个用于创建对象的接口,让子类决定将哪一个类实例化)

● 使用场景 ●

Factory创建新产品的“工厂”对象, 所有产品都支持相同的接口(属性和方法)。

const Factory = function () {
    this.createEmployee = function (type) {
        let employee;

        switch(type) {
            case 'fulltime' :
                employee = new FullTime();
                break;
            case 'parttime' :
            employee = new PartTime();
            break;

        }

        employee.type = type;
        employee.say = function () {
            console.log(this.type + ": rate " + this.hourly + "/hour");
        }

        return employee;
    }
}

const FullTime = function () {
    this.hourly = "$25";
};

const PartTime = function () {
    this.hourly = "$8";
};


function run() {

    const employees = [];
    const factory = new Factory();

    employees.push(factory.createEmployee("fulltime"));
    employees.push(factory.createEmployee("parttime"));

    for (let i = 0, len = employees.length; i < len; i++) {
        employees[i].say();
    }
}
run();
/*
fulltime: rate $25/hour
parttime: rate $8/hour
*/

Prototype

● 中文名 ●

原型

● 可变的方面 ●

被实例化的类

● 使用场景 ●

可以通过原型克隆来创建一个新对象。

class CustomerPrototype {
    constructor(product) {
        this.productAttrs = product;
    }

    clone() {
        const customer = new Customer();
        customer.first = this.productAttrs.first;
        customer.last = this.productAttrs.last;
        customer.status = this.productAttrs.status;

        return customer;
    }
    
}


function Customer(first, last, status) {

    this.first = first;
    this.last = last;
    this.status = status;

    this.say = function () {
        return `name: ${this.first} ${this.last}, status: ${this.status}`;
    };
}

function run() {

    const product = new Customer("F", "L", "pending");
    console.log( 'product: ', product.say() );

    //
    const newProduct = new CustomerPrototype(product);
    const customer = newProduct.clone();
    console.log( 'newProduct: ', customer.say() );
    
}
run();
/*
product:  name: F L, status: pending
newProduct:  name: F L, status: pending
*/

Adapter

● 中文名 ●

适配器

● 可变的方面 ●

对象的接口(将一个类的接口转化为另一个解决兼容性)

● 使用场景 ●
客户端调用适配器请求服务后,实现客户期望的接口。同样的输入,可以变成不同的输出或者界面。

// old interface
function Shipping() {
    this.request = function (weight) {
        return parseFloat(weight) * 1.5;
    }
}

// new interface
function AdvancedShipping() {
    this.login = function (credentials) { /* ... */ };
    this.calculate = function (weight) { 
        return parseFloat(weight) * 2.5;
    };
}

// adapter interface
function ShippingAdapter(credentials) {
    const shipping = new AdvancedShipping();

    shipping.login(credentials);

    return {
        request: function (weight) {
            return shipping.calculate(weight);
        }
    };
}

function run() {

    const shipping = new Shipping();
    const credentials = { token: "30a8-6ee1" };
    const adapter = new ShippingAdapter(credentials);

    let cost = shipping.request("2kg");
    console.log("Old cost: $" + cost);

    //
    cost = adapter.request("2kg");
    console.log("New cost: $" + cost);
}
run();
/*
Old cost: $3
New cost: $5
*/

Bridge

● 中文名 ●

桥接

● 可变的方面 ●

对象的实现(抽象与实现分离)

● 使用场景 ●

不在 JavaScript 中直接使用,实现和扩展抽象定义的接口。(属性和方法相同,可能存在接口覆盖,改变其输出的结果)

// input devices
const EventGestures = function (output) {
    this.output = output;

    this.tap = function () { this.output.click(); }
    this.swipe = function () { this.output.move(); }
};

// output devices
const Screen = function () {
    this.click = function () { console.log("Screen select"); }
    this.move = function () { console.log("Screen move"); }
};

function run() {
    const screen = new Screen();
    const hand = new EventGestures(screen);

    hand.tap();
    hand.swipe();
}

run();
/*
Screen select
Screen move
*/

Composite

● 中文名 ●

组合

● 可变的方面 ●

一个对象的结构和组成(单个对象和组合对象的使用一致性)

● 使用场景 ●

使用Node(结点)来表示组合中的分支(或子树),维护子组件的集合。

class Node {
    constructor(name) {
        this.children = [];
        this.name = name;
    }

    add(child) {
        this.children.push(child);
    }
    remove(child) {
        const length = this.children.length;
        for (let i = 0; i < length; i++) {
            if (this.children[i] === child) {
                this.children.splice(i, 1);
                return;
            }
        }
    }
    getChild(i) {
        return this.children[i];
    }

}


function run() {
    const tree = new Node("root");
    const left = new Node("left")
    const right = new Node("right");

    tree.add(left);
    tree.add(right);
    tree.remove(right);
    tree.add(right);

    console.log(tree);

}

run();
/*
{
    "children": [
        {
            "children": [],
            "name": "left"
        },
        {
            "children": [],
            "name": "right"
        }
    ],
    "name": "root"
}
*/

Decorator

● 中文名 ●

装饰

● 可变的方面 ●

对象的职责,不生成子类(动态添加职责)

● 使用场景 ●

定义一个新的接口(符合原始组件的接口),来扩展原组件的功能。

const User = function (name) {
    this.name = name;

    this.say = function () {
        console.log("User: " + this.name);
    };
}

const DecoratedUser = function (user, street, city) {
    this.user = user;
    this.name = user.name;  // ensures interface stays the same
    this.street = street;
    this.city = city;

    this.say = function () {
        console.log("Decorated User: " + this.name + ", " +
            this.street + ", " + this.city);
    };
}

function run() {

    const user = new User("Kelly");
    user.say();

    const decorated = new DecoratedUser(user, "Broadway", "New York");
    decorated.say();
}
run();
/*
User: Kelly
Decorated User: Kelly, Broadway, New York
*/

Facade

● 中文名 ●

外观

● 可变的方面 ●

一个子系统的接口(定义一个高层接口,一致的界面)

● 使用场景 ●

将目标请求委托给适当的子系统对象(例如:A对象传入,分别用函数a,b,c来运行A对象),实现并执行专门的子系统功能,得出不同的结果。

const Mortgage = function (name) {
    this.name = name;
}

Mortgage.prototype = {

    applyFor: function (amount) {
        // access multiple subsystems...
        const status = "approved";
        if (!new Bank().verify(this.name, amount)) {
            status = "denied";
        } else if (!new Credit().get(this.name)) {
            status = "denied";
        } else if (!new Background().check(this.name)) {
            status = "denied";
        }
        return `name: ${this.name}, status:  ${status}, amount: ${amount}`;
    }
}

const Bank = function () {
    this.verify = function (name, amount) {
        // complex logic ...
        return true;
    }
}

const Credit = function () {
    this.get = function (name) {
        // complex logic ...
        return true;
    }
}

const Background = function () {
    this.check = function (name) {
        // complex logic ...
        return true;
    }
}

function run() {
    const mortgage = new Mortgage("David");
    const result = mortgage.applyFor("$100,000");

    console.log(result);
}
run();  // name: David, status:  approved, amount: $100,000

Flyweight

● 中文名 ●

享元

● 可变的方面 ●

对象的存储开销(共享技术)

● 使用场景 ●

调用享元工厂获取享元对象(享元工厂内可能会有多个对象),根据对象是否存在,来维护要在应用程序之间共享的内部数据(可以输出数据,也可以处理数据)。也就是将原数据传递下去,共用,分析和处理它们的异同,根据异同来输出不同的结果,于此同时,不需要在其它函数上再次录入新的数据或者原数据。

const Flyweight = function(make, model, processor) {
    this.make = make;
    this.model = model;
    this.processor = processor;
};


class FlyWeightFactory {
    static data = {};

    static get(make, model, processor) {
        if ( this.data[`${make}-${model}`] === undefined ) {
            this.data[`${make}-${model}`] = new Flyweight(make, model, processor);
        }
        return this.data[`${make}-${model}`];
    }

    static getCount() {
        let count = 0;
        for (const f in this.data) count++;
        return count;
    }
}

function ComputerCollection() {
    const computers = {};
    let count = 0;

    
    const Computer = function (make, model, processor, memory, tag) {
        this.flyweight = FlyWeightFactory.get(make, model, processor);
        this.memory = memory;
        this.tag = tag;
        this.getMake = function () {
            return this.flyweight.make;
        }
        // ...
    };

    return {
        add: function (make, model, processor, memory, tag) {
            computers[tag] = new Computer(make, model, processor, memory, tag);
            count++;
        },

        getCount: function () {
            return count;
        }
    };
}



function run() {
    const computers = new ComputerCollection();

    computers.add("Dell", "XPS", "Intel", "5G", "Y755P");
    computers.add("Dell", "XPS", "Intel", "6G", "X997T");
    computers.add("HP", "Envy", "Intel", "4G", "CNU883701");

    console.log("Computers: " + computers.getCount());
    console.log("Flyweights: " + FlyWeightFactory.getCount());
}
run();
/*
Computers: 3
Flyweights: 2
*/

Proxy

● 中文名 ●

代理

● 可变的方面 ●

如何访问一个对象;该对象的位置

● 使用场景 ●

客户端调用代理请求操作,代理提供了最后的结果,并维护一个允许代理访问真实对象的引用。代理会处理各种请求,并转发到真正的对象中。

代理缓存了频繁请求,如果对象未被缓存则执行真正的服务并将结果存储在缓存中。

function RealOperation() {
    this.getLatLng = function (address) {
        switch (address) {
            case 'Amsterdam' : return "52.3700° N, 4.8900° E";
            case 'London' : return "51.5171° N, 0.1062° W";
            default : return '';
        }
    };
}

function GeoProxy() {
    const geocoder = new RealOperation();
    const geocache = {};

    return {
        getLatLng: function (address) {
            if (!geocache[address]) {
                geocache[address] = geocoder.getLatLng(address);
            }
            console.log(address + ": " + geocache[address]);
            return geocache[address];
        },
        getCount: function () {
            let count = 0;
            for (const code in geocache) { count++; }
            return count;
        }
    };
};

function run() {

    const geo = new GeoProxy();

    geo.getLatLng("Paris");
    geo.getLatLng("London");
    geo.getLatLng("Amsterdam");
    geo.getLatLng("Amsterdam");
    geo.getLatLng("London");
    geo.getLatLng("London");
    geo.getLatLng("London");

    console.log("\nCache size: " + geo.getCount());

}
run();
/*

Paris: 
London: 51.5171° N, 0.1062° W
Amsterdam: 52.3700° N, 4.8900° E
Amsterdam: 52.3700° N, 4.8900° E
London: 51.5171° N, 0.1062° W
London: 51.5171° N, 0.1062° W
London: 51.5171° N, 0.1062° W

Cache size: 3
*/

Chain of Responsibility

● 中文名 ●

职责链

● 可变的方面 ●

满足一个请求的对象(解除请求的发送者和接收者之间的耦合,使多个对象都有机会处理这个请求)

● 使用场景 ●

就算很常用的原型链的写法,一个关键点就是方法中的 return, 要返回这个对象,这样才能形成后继的结果链。

const Request = function (amount) {
    this.amount = amount;
    console.log("Requested: $" + amount + "\n");
}

Request.prototype = {
    get: function (bill) {
        const count = Math.floor(this.amount / bill);
        this.amount -= count * bill;
        console.log("Dispense " + count + " $" + bill + " bills");
        return this; //关键
    }
}
function run() {
    const request = new Request(378);
    request.get(100).get(50).get(20);
}
run();
/*

Requested: $378

Dispense 3 $100 bills
Dispense 1 $50 bills
Dispense 1 $20 bills
*/

Command

● 中文名 ●

命令

● 可变的方面 ●

何时、怎样满足一个请求(封装请求,参数化)

● 使用场景 ●

客户端引用一个命令接收器(对象),接收器知道如何执行与命令相关的操作。把这些命令和相关的函数绑定即可。

function add(x, y) { return x + y; }
function sub(x, y) { return x - y; }

const Command = function (execute, undo, value) {
    this.execute = execute;
    this.undo = undo;
    this.value = value;
}

const AddCommand = function (value) {
    return new Command(add, sub, value);
};

const SubCommand = function (value) {
    return new Command(sub, add, value);
};


const Calculator = function () {
    let current = 0;
    const commands = [];

    function action(command) {
        const name = command.execute.toString().substr(9, 3);
        return name.charAt(0).toUpperCase() + name.slice(1);
    }

    return {
        execute: function (command) {
            current = command.execute(current, command.value);
            commands.push(command);
            console.log(action(command) + ": " + command.value);
        },

        undo: function () {
            const command = commands.pop();
            current = command.undo(current, command.value);
            console.log("Undo " + action(command) + ": " + command.value);
        },

        getCurrentValue: function () {
            return current;
        }
    }
}

function run() {

    const calculator = new Calculator();

    // issue commands
    calculator.execute(new AddCommand(100));
    calculator.execute(new SubCommand(24));

    // reverse last two commands
    calculator.undo();

    console.log("\nValue: " + calculator.getCurrentValue());
}
run();
/*

Add: 100
Sub: 24
Undo Sub: 24

Value: 100
*/

Interpreter

● 中文名 ●

解释器

● 可变的方面 ●

一个语言的文法及解释

● 使用场景 ●

输入一个字符串,构建出这个字符串的相应的语法树,然后使用相应的表达式输出最后的结果。

const Context = function (input) {
    this.input = input;
    this.output = 0;
}

Context.prototype = {
    startsWith: function (str) {
        return this.input.substr(0, str.length) === str;
    }
}

const Expression = function (name, one, four, five, nine, multiplier) {
    this.name = name;
    this.one = one;
    this.four = four;
    this.five = five;
    this.nine = nine;
    this.multiplier = multiplier;
}

Expression.prototype = {
    interpret: function (context) {
        if (context.input.length == 0) {
            return;
        }
        else if (context.startsWith(this.nine)) {
            context.output += (9 * this.multiplier);
            context.input = context.input.substr(2);
        }
        else if (context.startsWith(this.four)) {
            context.output += (4 * this.multiplier);
            context.input = context.input.substr(2);
        }
        else if (context.startsWith(this.five)) {
            context.output += (5 * this.multiplier);
            context.input = context.input.substr(1);
        }
        while (context.startsWith(this.one)) {
            context.output += (1 * this.multiplier);
            context.input = context.input.substr(1);
        }
    }
}

function run() {
    const roman = "MCMXXVIII"
    const context = new Context(roman);
    const tree = [];

    tree.push(new Expression("thousand", "M", " ", " ", " ", 1000));
    tree.push(new Expression("hundred", "C", "CD", "D", "CM", 100));
    tree.push(new Expression("ten", "X", "XL", "L", "XC", 10));
    tree.push(new Expression("one", "I", "IV", "V", "IX", 1));

    for (let i = 0, len = tree.length; i < len; i++) {
        tree[i].interpret(context);
    }

    console.log(roman + " = " + context.output);
}
run();
/*

MCMXXVIII = 1928
*/

Iterator

● 中文名 ●

迭代器

● 可变的方面 ●

如何遍历、访问一个聚合的各元素

● 使用场景 ●

客户端调用迭代器(通常传入一个数组),迭代器使用方法 first()、next() 等实现迭代器接口遍历对象。


const Iterator = function (items) {
    this.index = 0;
    this.items = items;
}

Iterator.prototype = {
    first: function () {
        this.reset();
        return this.next();
    },
    next: function () {
        return this.items[this.index++];
    },
    hasNext: function () {
        return this.index <= this.items.length;
    },
    reset: function () {
        this.index = 0;
    },
    each: function (callback) {
        for (let item = this.first(); this.hasNext(); item = this.next()) {
            callback(item);
        }
    }
}

function run() {

    const items = ["one", 2, "three", true];
    const iter = new Iterator(items);

    // using for loop
    for (let item = iter.first(); iter.hasNext(); item = iter.next()) {
        console.log(item);
    }
    console.log("-----");

    // using Iterator's each method
    iter.each(function (item) {
        console.log(item);
    });
}
run();
/*

one
2
three
true
-----
one
2
three
true
*/

Mediator

● 中文名 ●

中介者

● 可变的方面 ●

对象间怎样交互、和谁交互(用一个中介对象封装交互)

● 使用场景 ●

中介者提供一个通信的接口(会有多个参与者加入,参与者作为参数传递给中介者),维护和管理一个或者多个对象的引用。

const Participant = function (name) {
    this.name = name;
    this.chatroom = null;
};

Participant.prototype = {
    send: function (message, to) {
        this.chatroom.send(message, this, to);
    },
    receive: function (message, from) {
        console.log(from.name + " to " + this.name + ": " + message);
    }
};

const Chatroom = function () {  // Mediator
    const participants = {};

    return {

        register: function (participant) {
            participants[participant.name] = participant;
            participant.chatroom = this;
        },

        send: function (message, from, to) {
            if (to) {                      // single message
                to.receive(message, from);
            } else {                       // broadcast message
                for (key in participants) {
                    if (participants[key] !== from) {
                        participants[key].receive(message, from);
                    }
                }
            }
        }
    };
};

function run() {

    const yoko = new Participant("A");
    const john = new Participant("B");

    const chatroom = new Chatroom();
    chatroom.register(yoko);
    chatroom.register(john);

    yoko.send("I am A.");
    yoko.send("I am 15.");
    john.send("Hello", yoko);
}
run();
/*
A to B: I am A.
A to B: I am 15.
B to A: Hello
*/

Memento

● 中文名 ●

备忘录

● 可变的方面 ●

一个对象中那些私有信息存放在该对象之外,以及在什么时候进行存储(不破坏封装,捕获对象内部的状态)

● 使用场景 ●

通过一个对象来存储原来的对象的属性,它并不改变原来的对象。这样可以方便在不同的状态时使用存储的数据。

const Person = function (name, state) {
    this.name = name;
    this.state = state;
}

Person.prototype = {

    hydrate: function () {
        const memento = JSON.stringify(this);
        return memento;
    },

    dehydrate: function (memento) {
        const m = JSON.parse(memento);
        this.name = m.name;
        this.state = m.state;
    }
}

const CareTaker = function () {
    this.mementos = {};
    this.add = function (key, memento) {
        this.mementos[key] = memento;
    }
    this.get = function (key) {
        return this.mementos[key];
    }
}

function run() {

    const mike = new Person("AB", "TX");
    const john = new Person("CD", "CA");
    const caretaker = new CareTaker();

    // save state
    caretaker.add(1, mike.hydrate());
    caretaker.add(2, john.hydrate());

    // mess up their names
    mike.name = "GGG";
    john.name = "MMM";

    // restore original state
    mike.dehydrate(caretaker.get(1));
    john.dehydrate(caretaker.get(2));

    console.log(mike.name);
    console.log(john.name);
}
run();
/*
AB
CD

注释掉 dehydrate方法则输出:

GGG
MMM

*/

Observer

● 中文名 ●

观察者

● 可变的方面 ●

多个对象依赖于另外一个对象,而这些对象又如何保持一致(得到通知并自动刷新)

● 使用场景 ●

这种模式实现了松耦合,任意数量的观察者对象都可以观察一个目标对象,实现一个让观察者对象订阅或取消订阅的接口。

当它的状态改变时向它的观察者发送通知,然后可以调用观察者的绑定的函数来触发事件。

它也可以说是发布订阅模式,他们都可以增加一个第三者(经纪人:Broker),来通知发布者/订阅者,它们也可以完全解耦,实质上都是为了让对象之间互不相识却可以相互通信。

function Click() {
    this.handlers = [];  // observers
}

Click.prototype = {

    subscribe: function (fn) {
        this.handlers.push(fn);
    },

    unsubscribe: function (fn) {
        this.handlers = this.handlers.filter(
            function (item) {
                if (item !== fn) {
                    return item;
                }
            }
        );
    },

    fire: function (o, thisObj) {
        const scope = thisObj || window;
        this.handlers.forEach(function (item) {
            item.call(scope, o);
        });
    }
}

function run() {

    const clickHandler = function (item) {
        console.log("fired: " + item);
    };

    const click = new Click();

    click.subscribe(clickHandler);
    click.fire('event #1');
    click.unsubscribe(clickHandler);
    click.fire('event #2');
    click.subscribe(clickHandler);
    click.fire('event #3');
}
run();
/*
fired: event #1
fired: event #3
*/

State

● 中文名 ●

状态

● 可变的方面 ●

对象的状态(状态改变时改变它的行为)

● 使用场景 ●

创建一个函数 (上下文,即起到一个承上启下的作用,作为一个中间量,根据输入控制输出) 公开支持目标对象的接口,它用来维护这个引用的对象,并允许改变对象的状态属性。根据这些不同的状态,会有不同的事件触发。

const TrafficLight = function () {
    let count = 0;
    let currentState = new Red(this);

    this.change = function (state) {
        // limits number of changes
        if (count++ >= 10) return;
        currentState = state;
        currentState.go();
    };

    this.start = function () {
        currentState.go();
    };
}

const Red = function (light) {
    this.light = light;

    this.go = function () {
        console.log("Red --> for 1 minute");
        light.change(new Green(light));
    }
};

const Yellow = function (light) {
    this.light = light;

    this.go = function () {
        console.log("Yellow --> for 10 seconds");
        light.change(new Red(light));
    }
};

const Green = function (light) {
    this.light = light;

    this.go = function () {
        console.log("Green --> for 1 minute");
        light.change(new Yellow(light));
    }
};

function run() {

    const light = new TrafficLight();
    light.start();
}
run();
/*
Red --> for 1 minute
Green --> for 1 minute
Yellow --> for 10 seconds
Red --> for 1 minute
Green --> for 1 minute
Yellow --> for 10 seconds
Red --> for 1 minute
Green --> for 1 minute
Yellow --> for 10 seconds
Red --> for 1 minute
Green --> for 1 minute
*/

Strategy

● 中文名 ●

策略

● 可变的方面 ●

算法(封装一系列算法,独立于使用它的客户)

● 使用场景 ●

创建一个函数 (上下文,即起到一个承上启下的作用,作为一个中间量,根据输入控制输出) 公开支持目标对象的接口,针对当前引用的对象(这个对象可以是纯数据,也可以是其它),接口允许客户端请求策略计算。

即相同的数据,提供了不同的算法,来输出不同的结果。

const Shipping = function () {
    this.company = "";
};

Shipping.prototype = {
    setStrategy: function (company) {
        this.company = company;
    },

    calculate: function (package) {
        return this.company.calculate(package);
    }
};

const UPS = function () {
    this.calculate = function (package) {
        // calculations...
        return "$15";
    }
};


const Fedex = function () {
    this.calculate = function (package) {
        // calculations...
        return "$25";
    }
};

function run() {

    const package = { from: "76712", to: "10012", weigth: "lkg" };

    // the 3 strategies
    const ups = new UPS();
    const fedex = new Fedex();

    const shipping = new Shipping();

    shipping.setStrategy(ups);
    console.log("UPS Strategy: " + shipping.calculate(package));
    shipping.setStrategy(fedex);
    console.log("Fedex Strategy: " + shipping.calculate(package));
}
run();
/*
UPS Strategy: $15
Fedex Strategy: $25
*/

Template Method

● 中文名 ●

模板方法

● 可变的方面 ●

算法中的某些步骤(定义骨架)

● 使用场景 ●

首先为一个函数提供一些接口,这些接口是实现定义算法基本步骤的钩子(也可以为空,也可以指定默认的代码),开发人员可以轻松通过覆盖的方式定义自己的方法(也就是定义一个自定义的模版)

const datastore = {
    process: function () {
        this.connect();
        this.select();
        this.disconnect();
        return true;
    }
};

function inherit(proto) {
    const F = function () { };
    F.prototype = proto;
    return new F();
}

function run() {
    const mySql = inherit(datastore);

    // implement template steps

    mySql.connect = function () {
        console.log("MySQL: connect step");
    };

    mySql.select = function () {
        console.log("MySQL: select step");
    };

    mySql.disconnect = function () {
        console.log("MySQL: disconnect step");
    };

    mySql.process();
}
run();
/*
MySQL: connect step
MySQL: select step
MySQL: disconnect step
*/

Visitor

● 中文名 ●

访问者

● 可变的方面 ●

某些可作用于一个(组)对象上的操作,但不修改这些对象的类(如回调函数,回调传参)

● 使用场景 ●

让对象可以通过访问者查询,让对象也可以使用访问者的方法,从而可以改变对象的属性值。

这样做的好处是无需重新创建一次相同的(不同属性值)对象来使用。

const Employee = function (name, salary, vacation) {
    const self = this;

    this.accept = function (visitor) {
        visitor.visit(self);
    };

    this.getName = function () {
        return name;
    };

    this.getSalary = function () {
        return salary;
    };

    this.setSalary = function (sal) {
        salary = sal;
    };
};

const ExtraSalary = function () {
    this.visit = function (emp) {
        emp.setSalary(emp.getSalary() * 1.1);
    };
};


function run() {

    const employees = [
        new Employee("A", 10000, 10),
        new Employee("B", 20000, 21),
    ];

    const visitorSalary = new ExtraSalary();

    for (let i = 0, len = employees.length; i < len; i++) {
        const emp = employees[i];

        emp.accept(visitorSalary);
        console.log(`Employee Name: ${emp.getName()} - $${emp.getSalary()} `);
    }
}
run();
/*
Employee Name: A - $11000 
Employee Name: B - $22000 

注释  accept方法后,输出:

Employee Name: A - $10000 
Employee Name: B - $20000 
*/

本文出自没位道 - Chuckie Chang个人网站,转载请保留出处,谢谢!
文章采用 CC-BY-4.0 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。


返回列表  分享

分享给朋友!

微信
微博
文章评论