Maison  >  Article  >  interface Web  >  Quels sont les modèles de conception en javascript

Quels sont les modèles de conception en javascript

青灯夜游
青灯夜游original
2022-01-26 16:49:363481parcourir

Les modèles de conception en JavaScript incluent : le mode singleton, le mode stratégie, le mode proxy, le mode itérateur, le mode « publication-abonnement », le mode commande, le mode combinaison, le mode méthode modèle, le mode poids mouche, le mode chaîne de responsabilité, le modèle intermédiaire, le décorateur. modèle, modèle d'état, modèle d'adaptateur, modèle d'apparence, etc.

Quels sont les modèles de conception en javascript

L'environnement d'exploitation de ce tutoriel : système Windows 7, JavaScript version 1.8.5, ordinateur Dell G3.

Quinze modèles de conception courants en JavaScript

1. Modèle Singleton

1 Définition

Assurez-vous qu'une classe n'a qu'une seule instance et fournissez un point d'accès global pour y accéder

2. . Le noyau

garantit qu'il n'y a qu'une seule instance et fournit un accès global

3. Implémentation

Supposons que vous souhaitiez définir un administrateur et ne le définissez qu'une seule fois pour plusieurs appels. Variables internes pour implémenter ce singleton

function SetManager(name) {
    this.manager = name;
}

SetManager.prototype.getName = function() {
    console.log(this.manager);
};

var SingletonSetManager = (function() {
    var manager = null;

    return function(name) {
        if (!manager) {
            manager = new SetManager(name);
        }

        return manager;
    } 
})();

SingletonSetManager('a').getName(); // a
SingletonSetManager('b').getName(); // a
SingletonSetManager('c').getName(); // a

C'est une approche relativement simple, mais et si on veut aussi mettre en place un RH ? Vous devez recopier le code

Donc, vous pouvez réécrire l'intérieur du singleton pour le rendre plus général

// 提取出通用的单例
function getSingleton(fn) {
    var instance = null;

    return function() {
        if (!instance) {
            instance = fn.apply(this, arguments);
        }

        return instance;
    }
}

Rappelez, le résultat est toujours le même

// 获取单例
var managerSingleton = getSingleton(function(name) {
    var manager = new SetManager(name);
    return manager;
});

managerSingleton('a').getName(); // a
managerSingleton('b').getName(); // a
managerSingleton('c').getName(); // a

À ce moment, quand on ajoute HR, il y a pas besoin de changer les composants internes du singleton a été implémenté, il vous suffit d'implémenter ce qui est nécessaire pour ajouter HR, puis de l'appeler

function SetHr(name) {
    this.hr = name;
}

SetHr.prototype.getName = function() {
    console.log(this.hr);
};

var hrSingleton = getSingleton(function(name) {
    var hr = new SetHr(name);
    return hr;
});

hrSingleton('aa').getName(); // aa
hrSingleton('bb').getName(); // aa
hrSingleton('cc').getName(); // aa

Ou, vous voulez simplement créer une couche p, sans instancier l'objet, appelez le. fonctionner directement

Le résultat est qu'il n'y a que le premier sur la page A créée p

function createPopup(html) {
    var div = document.createElement('div');
    div.innerHTML = html;
    document.body.append(div);

    return div;
}

var popupSingleton = getSingleton(function() {
    var div = createPopup.apply(this, arguments);
    return div;
});

console.log(
    popupSingleton('aaa').innerHTML,
    popupSingleton('bbb').innerHTML,
    popupSingleton('bbb').innerHTML
); // aaa  aaa  aaa

2. Modèle de stratégie

1 Définition

Définir une série d'algorithmes, les encapsuler un par un, et les rendre interchangeables.

2. Le noyau

sépare l'utilisation de l'algorithme de la mise en œuvre de l'algorithme.

Un programme basé sur le modèle de stratégie se compose d'au moins deux parties :

La première partie est un ensemble de classes de stratégie. La classe de stratégie encapsule des algorithmes spécifiques et est responsable du processus de calcul spécifique.

La deuxième partie est la classe d'environnement Context. Context accepte la demande du client puis délègue la demande à une certaine classe de stratégie. Pour ce faire, il est nécessaire de conserver une référence à un certain objet stratégique dans le contexte

3. Mise en œuvre

Le modèle de stratégie peut être utilisé pour combiner une série d'algorithmes ou une série de règles métier

Supposons que vous doivent réussir Les notes sont utilisées pour calculer les notes finales des élèves, et chaque note a une valeur pondérée correspondante. On peut définir directement cette politique de groupe sous forme de littéraux d'objet

// 加权映射关系
var levelMap = {
    S: 10,
    A: 8,
    B: 6,
    C: 4
};

// 组策略
var scoreLevel = {
    basicScore: 80,

    S: function() {
        return this.basicScore + levelMap['S']; 
    },

    A: function() {
        return this.basicScore + levelMap['A']; 
    },

    B: function() {
        return this.basicScore + levelMap['B']; 
    },

    C: function() {
        return this.basicScore + levelMap['C']; 
    }
}

// 调用
function getScore(level) {
    return scoreLevel[level] ? scoreLevel[level]() : 0;
}

console.log(
    getScore('S'),
    getScore('A'),
    getScore('B'),
    getScore('C'),
    getScore('D')
); // 90 88 86 84 0

En termes de combinaison de règles métier, la plus classique est la méthode de vérification de formulaire. Voici les éléments clés

// 错误提示
var errorMsgs = {
    default: '输入数据格式不正确',
    minLength: '输入数据长度不足',
    isNumber: '请输入数字',
    required: '内容不为空'
};

// 规则集
var rules = {
    minLength: function(value, length, errorMsg) {
        if (value.length < length) {
            return errorMsg || errorMsgs[&#39;minLength&#39;]
        }
    },
    isNumber: function(value, errorMsg) {
        if (!/\d+/.test(value)) {
            return errorMsg || errorMsgs[&#39;isNumber&#39;];
        }
    },
    required: function(value, errorMsg) {
        if (value === &#39;&#39;) {
            return errorMsg || errorMsgs[&#39;required&#39;];
        }
    }
};

// 校验器
function Validator() {
    this.items = [];
};

Validator.prototype = {
    constructor: Validator,
    
    // 添加校验规则
    add: function(value, rule, errorMsg) {
        var arg = [value];

        if (rule.indexOf(&#39;minLength&#39;) !== -1) {
            var temp = rule.split(&#39;:&#39;);
            arg.push(temp[1]);
            rule = temp[0];
        }

        arg.push(errorMsg);

        this.items.push(function() {
            // 进行校验
            return rules[rule].apply(this, arg);
        });
    },
    
    // 开始校验
    start: function() {
        for (var i = 0; i < this.items.length; ++i) {
            var ret = this.items[i]();
            
            if (ret) {
                console.log(ret);
                // return ret;
            }
        }
    }
};

// 测试数据
function testTel(val) {
    return val;
}

var validate = new Validator();

validate.add(testTel(&#39;ccc&#39;), &#39;isNumber&#39;, &#39;只能为数字&#39;); // 只能为数字
validate.add(testTel(&#39;&#39;), &#39;required&#39;); // 内容不为空
validate.add(testTel(&#39;123&#39;), &#39;minLength:5&#39;, &#39;最少5位&#39;); // 最少5位
validate.add(testTel(&#39;12345&#39;), &#39;minLength:5&#39;, &#39;最少5位&#39;);

var ret = validate.start();

console.log(ret);

4 Avantages et inconvénients

Avantages

Il peut efficacement éviter plusieurs instructions conditionnelles, et l'encapsulation d'une série de méthodes est plus intuitive et plus facile à maintenir

Inconvénients

. Il existe souvent de nombreux ensembles de politiques, et nous devons comprendre et définir toutes les situations à l'avance

3. Mode proxy

1. Définition

Fournir un substitut ou un espace réservé pour un objet afin d'en contrôler l'accès Accès

2. Core

Lorsqu'il n'est pas pratique pour le client d'accéder directement à un objet ou ne répond pas aux besoins, un objet de remplacement est fourni pour contrôler l'accès à l'objet. objet de remplacement.

Une fois que l'objet remplaçant a effectué un certain traitement sur la demande, il transfère ensuite la demande à l'objet ontologie

Les interfaces de l'agent et de l'ontologie sont cohérentes. L'ontologie définit les fonctions clés, et l'agent fournit ou refuse. y accéder ou y accéder. Faites quelques choses supplémentaires avant l'ontologie

3. Implémentation

Il existe trois modes de proxy principaux : proxy de protection, proxy virtuel, proxy de cache

Le proxy de protection implémente principalement le comportement de restriction des sujets d'accès. , en prenant comme exemple simple le filtrage des caractères

// 主体,发送消息
function sendMsg(msg) {
    console.log(msg);
}

// 代理,对消息进行过滤
function proxySendMsg(msg) {
    // 无消息则直接返回
    if (typeof msg === &#39;undefined&#39;) {
        console.log(&#39;deny&#39;);
        return;
    }
    
    // 有消息则进行过滤
    msg = (&#39;&#39; + msg).replace(/泥\s*煤/g, &#39;&#39;);

    sendMsg(msg);
}


sendMsg(&#39;泥煤呀泥 煤呀&#39;); // 泥煤呀泥 煤呀
proxySendMsg(&#39;泥煤呀泥 煤&#39;); // 呀
proxySendMsg(); // deny

Son intention est évidente. Il contrôle avant d'accéder au sujet, lorsqu'il n'y a pas de message, il est renvoyé directement dans le proxy, refusant l'accès au sujet

.

gère les caractères sensibles lorsqu'il y a un message. Cela appartient au mode de l'agent virtuel

L'agent virtuel ajoute quelques opérations supplémentaires lors du contrôle de l'accès au sujet

Lorsque l'événement de défilement est déclenché, il n'est peut-être pas nécessaire de le déclencher fréquemment. .Nous pouvons introduire la limitation des fonctions, qui est une implémentation d'un agent virtuel

// 函数防抖,频繁操作中不处理,直到操作完成之后(再过 delay 的时间)才一次性处理
function debounce(fn, delay) {
    delay = delay || 200;
    
    var timer = null;

    return function() {
        var arg = arguments;
          
        // 每次操作时,清除上次的定时器
        clearTimeout(timer);
        timer = null;
        
        // 定义新的定时器,一段时间后进行操作
        timer = setTimeout(function() {
            fn.apply(this, arg);
        }, delay);
    }
};

var count = 0;

// 主体
function scrollHandle(e) {
    console.log(e.type, ++count); // scroll
}

// 代理
var proxyScrollHandle = (function() {
    return debounce(scrollHandle, 500);
})();

window.onscroll = proxyScrollHandle;

L'agent de mise en cache peut fournir une mise en cache temporaire pour certains résultats d'opérations coûteux afin d'améliorer l'efficacité

Par exemple, les opérations d'ajout de cache

// 主体
function add() {
    var arg = [].slice.call(arguments);

    return arg.reduce(function(a, b) {
        return a + b;
    });
}

// 代理
var proxyAdd = (function() {
    var cache = [];

    return function() {
        var arg = [].slice.call(arguments).join(&#39;,&#39;);
        
        // 如果有,则直接从缓存返回
        if (cache[arg]) {
            return cache[arg];
        } else {
            var ret = add.apply(this, arguments);
            return ret;
        }
    };
})();

console.log(
    add(1, 2, 3, 4),
    add(1, 2, 3, 4),

    proxyAdd(10, 20, 30, 40),
    proxyAdd(10, 20, 30, 40)
); // 10 10 100 100

4.

1. Définition

Le modèle d'itérateur fait référence à la fourniture d'une méthode pour accéder séquentiellement aux éléments individuels d'un objet agrégé sans exposer la représentation interne de l'objet.

2. Core

Après avoir utilisé le modèle d'itérateur, même si vous ne vous souciez pas de la structure interne de l'objet, vous pouvez accéder à chaque élément de manière séquentielle

3.

JS中数组的map forEach 已经内置了迭代器

[1, 2, 3].forEach(function(item, index, arr) {
    console.log(item, index, arr);
});

不过对于对象的遍历,往往不能与数组一样使用同一的遍历代码

我们可以封装一下

function each(obj, cb) {
    var value;

    if (Array.isArray(obj)) {
        for (var i = 0; i < obj.length; ++i) {
            value = cb.call(obj[i], i, obj[i]);

            if (value === false) {
                break;
            }
        }
    } else {
        for (var i in obj) {
            value = cb.call(obj[i], i, obj[i]);

            if (value === false) {
                break;
            }
        }
    }
}

each([1, 2, 3], function(index, value) {
    console.log(index, value);
});

each({a: 1, b: 2}, function(index, value) {
    console.log(index, value);
});

// 0 1
// 1 2
// 2 3

// a 1
// b 2

再来看一个例子,强行地使用迭代器,来了解一下迭代器也可以替换频繁的条件语句

虽然例子不太好,但在其他负责的分支判断情况下,也是值得考虑的

function getManager() {
    var year = new Date().getFullYear();

    if (year <= 2000) {
        console.log(&#39;A&#39;);
    } else if (year >= 2100) {
        console.log(&#39;C&#39;);
    } else {
        console.log(&#39;B&#39;);
    }
}

getManager(); // B

将每个条件语句拆分出逻辑函数,放入迭代器中迭代

function year2000() {
    var year = new Date().getFullYear();

    if (year <= 2000) {
        console.log(&#39;A&#39;);
    }

    return false;
}

function year2100() {
    var year = new Date().getFullYear();

    if (year >= 2100) {
        console.log(&#39;C&#39;);
    }

    return false;
}

function year() {
    var year = new Date().getFullYear();

    if (year > 2000 && year < 2100) {
        console.log(&#39;B&#39;);
    }

    return false;
}

function iteratorYear() {
    for (var i = 0; i < arguments.length; ++i) {
        var ret = arguments[i]();

        if (ret !== false) {
            return ret;
        }
    }
}

var manager = iteratorYear(year2000, year2100, year); // B

五、发布-订阅模式

1. 定义

也称作观察者模式,定义了对象间的一种一对多的依赖关系,当一个对象的状态发 生改变时,所有依赖于它的对象都将得到通知

2. 核心

取代对象之间硬编码的通知机制,一个对象不用再显式地调用另外一个对象的某个接口。

与传统的发布-订阅模式实现方式(将订阅者自身当成引用传入发布者)不同,在JS中通常使用注册回调函数的形式来订阅

3. 实现

JS中的事件就是经典的发布-订阅模式的实现

// 订阅
document.body.addEventListener(&#39;click&#39;, function() {
    console.log(&#39;click1&#39;);
}, false);

document.body.addEventListener(&#39;click&#39;, function() {
    console.log(&#39;click2&#39;);
}, false);

// 发布
document.body.click(); // click1  click2

自己实现一下

小A在公司C完成了笔试及面试,小B也在公司C完成了笔试。他们焦急地等待结果,每隔半天就电话询问公司C,导致公司C很不耐烦。

一种解决办法是 AB直接把联系方式留给C,有结果的话C自然会通知AB

这里的“询问”属于显示调用,“留给”属于订阅,“通知”属于发布

// 观察者
var observer = {
    // 订阅集合
    subscribes: [],

    // 订阅
    subscribe: function(type, fn) {
        if (!this.subscribes[type]) {
            this.subscribes[type] = [];
        }
        
        // 收集订阅者的处理
        typeof fn === &#39;function&#39; && this.subscribes[type].push(fn);
    },

    // 发布  可能会携带一些信息发布出去
    publish: function() {
        var type = [].shift.call(arguments),
            fns = this.subscribes[type];
        
        // 不存在的订阅类型,以及订阅时未传入处理回调的
        if (!fns || !fns.length) {
            return;
        }
        
        // 挨个处理调用
        for (var i = 0; i < fns.length; ++i) {
            fns[i].apply(this, arguments);
        }
    },
    
    // 删除订阅
    remove: function(type, fn) {
        // 删除全部
        if (typeof type === &#39;undefined&#39;) {
            this.subscribes = [];
            return;
        }

        var fns = this.subscribes[type];

        // 不存在的订阅类型,以及订阅时未传入处理回调的
        if (!fns || !fns.length) {
            return;
        }

        if (typeof fn === &#39;undefined&#39;) {
            fns.length = 0;
            return;
        }

        // 挨个处理删除
        for (var i = 0; i < fns.length; ++i) {
            if (fns[i] === fn) {
                fns.splice(i, 1);
            }
        }
    }
};

// 订阅岗位列表
function jobListForA(jobs) {
    console.log(&#39;A&#39;, jobs);
}

function jobListForB(jobs) {
    console.log(&#39;B&#39;, jobs);
}

// A订阅了笔试成绩
observer.subscribe(&#39;job&#39;, jobListForA);
// B订阅了笔试成绩
observer.subscribe(&#39;job&#39;, jobListForB);


// A订阅了笔试成绩
observer.subscribe(&#39;examinationA&#39;, function(score) {
    console.log(score);
});

// B订阅了笔试成绩
observer.subscribe(&#39;examinationB&#39;, function(score) {
    console.log(score);
});

// A订阅了面试结果
observer.subscribe(&#39;interviewA&#39;, function(result) {
    console.log(result);
});

observer.publish(&#39;examinationA&#39;, 100); // 100
observer.publish(&#39;examinationB&#39;, 80); // 80
observer.publish(&#39;interviewA&#39;, &#39;备用&#39;); // 备用

observer.publish(&#39;job&#39;, [&#39;前端&#39;, &#39;后端&#39;, &#39;测试&#39;]); // 输出A和B的岗位


// B取消订阅了笔试成绩
observer.remove(&#39;examinationB&#39;);
// A都取消订阅了岗位
observer.remove(&#39;job&#39;, jobListForA);

observer.publish(&#39;examinationB&#39;, 80); // 没有可匹配的订阅,无输出
observer.publish(&#39;job&#39;, [&#39;前端&#39;, &#39;后端&#39;, &#39;测试&#39;]); // 输出B的岗位

4. 优缺点

优点

一为时间上的解耦,二为对象之间的解耦。可以用在异步编程中与MV*框架中

缺点

创建订阅者本身要消耗一定的时间和内存,订阅的处理函数不一定会被执行,驻留内存有性能开销

弱化了对象之间的联系,复杂的情况下可能会导致程序难以跟踪维护和理解

六、命令模式

1. 定义

用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦合关系

命令(command)指的是一个执行某些特定事情的指令

2. 核心

命令中带有execute执行、undo撤销、redo重做等相关命令方法,建议显示地指示这些方法名

3. 实现

简单的命令模式实现可以直接使用对象字面量的形式定义一个命令

var incrementCommand = {
    execute: function() {
        // something
    }
};

不过接下来的例子是一个自增命令,提供执行、撤销、重做功能

采用对象创建处理的方式,定义这个自增

// 自增
function IncrementCommand() {
    // 当前值
    this.val = 0;
    // 命令栈
    this.stack = [];
    // 栈指针位置
    this.stackPosition = -1;
};

IncrementCommand.prototype = {
    constructor: IncrementCommand,

    // 执行
    execute: function() {
        this._clearRedo();
        
        // 定义执行的处理
        var command = function() {
            this.val += 2;
        }.bind(this);
        
        // 执行并缓存起来
        command();
        
        this.stack.push(command);

        this.stackPosition++;

        this.getValue();
    },
    
    canUndo: function() {
        return this.stackPosition >= 0;
    },
    
    canRedo: function() {
        return this.stackPosition < this.stack.length - 1;
    },

    // 撤销
    undo: function() {
        if (!this.canUndo()) {
            return;
        }
        
        this.stackPosition--;

        // 命令的撤销,与执行的处理相反
        var command = function() {
            this.val -= 2;
        }.bind(this);
        
        // 撤销后不需要缓存
        command();

        this.getValue();
    },
    
    // 重做
    redo: function() {
        if (!this.canRedo()) {
            return;
        }
        
        // 执行栈顶的命令
        this.stack[++this.stackPosition]();

        this.getValue();
    },
    
    // 在执行时,已经撤销的部分不能再重做
    _clearRedo: function() {
        this.stack = this.stack.slice(0, this.stackPosition + 1);
    },
    
    // 获取当前值
    getValue: function() {
        console.log(this.val);
    }
};

再实例化进行测试,模拟执行、撤销、重做的操作

var incrementCommand = new IncrementCommand();

// 模拟事件触发,执行命令
var eventTrigger = {
    // 某个事件的处理中,直接调用命令的处理方法
    increment: function() {
        incrementCommand.execute();
    },

    incrementUndo: function() {
        incrementCommand.undo();
    },

    incrementRedo: function() {
        incrementCommand.redo();
    }
};


eventTrigger[&#39;increment&#39;](); // 2
eventTrigger[&#39;increment&#39;](); // 4

eventTrigger[&#39;incrementUndo&#39;](); // 2

eventTrigger[&#39;increment&#39;](); // 4

eventTrigger[&#39;incrementUndo&#39;](); // 2
eventTrigger[&#39;incrementUndo&#39;](); // 0
eventTrigger[&#39;incrementUndo&#39;](); // 无输出

eventTrigger[&#39;incrementRedo&#39;](); // 2
eventTrigger[&#39;incrementRedo&#39;](); // 4
eventTrigger[&#39;incrementRedo&#39;](); // 无输出

eventTrigger[&#39;increment&#39;](); // 6

此外,还可以实现简单的宏命令(一系列命令的集合)

var MacroCommand = {
    commands: [],

    add: function(command) {
        this.commands.push(command);

        return this;
    },

    remove: function(command) {
        if (!command) {
            this.commands = [];
            return;
        }

        for (var i = 0; i < this.commands.length; ++i) {
            if (this.commands[i] === command) {
                this.commands.splice(i, 1);
            }
        }
    },

    execute: function() {
        for (var i = 0; i < this.commands.length; ++i) {
            this.commands[i].execute();
        }
    }
};

var showTime = {
    execute: function() {
        console.log(&#39;time&#39;);
    }
};

var showName = {
    execute: function() {
        console.log(&#39;name&#39;);
    }
};

var showAge = {
    execute: function() {
        console.log(&#39;age&#39;);
    }
};

MacroCommand.add(showTime).add(showName).add(showAge);

MacroCommand.remove(showName);

MacroCommand.execute(); // time age

七、组合模式

1. 定义

是用小的子对象来构建更大的 对象,而这些小的子对象本身也许是由更小 的“孙对象”构成的。

2. 核心

可以用树形结构来表示这种“部分- 整体”的层次结构。

调用组合对象 的execute方法,程序会递归调用组合对象 下面的叶对象的execute方法

但要注意的是,组合模式不是父子关系,它是一种HAS-A(聚合)的关系,将请求委托给 它所包含的所有叶对象。基于这种委托,就需要保证组合对象和叶对象拥有相同的 接口

此外,也要保证用一致的方式对待 列表中的每个叶对象,即叶对象属于同一类,不需要过多特殊的额外操作

3. 实现

使用组合模式来实现扫描文件夹中的文件

// 文件夹 组合对象
function Folder(name) {
    this.name = name;
    this.parent = null;
    this.files = [];
}

Folder.prototype = {
    constructor: Folder,

    add: function(file) {
        file.parent = this;
        this.files.push(file);

        return this;
    },

    scan: function() {
        // 委托给叶对象处理
        for (var i = 0; i < this.files.length; ++i) {
            this.files[i].scan();
        }
    },

    remove: function(file) {
        if (typeof file === &#39;undefined&#39;) {
            this.files = [];
            return;
        }

        for (var i = 0; i < this.files.length; ++i) {
            if (this.files[i] === file) {
                this.files.splice(i, 1);
            }
        }
    }
};

// 文件 叶对象
function File(name) {
    this.name = name;
    this.parent = null;
}

File.prototype = {
    constructor: File,

    add: function() {
        console.log(&#39;文件里面不能添加文件&#39;);
    },

    scan: function() {
        var name = [this.name];
        var parent = this.parent;

        while (parent) {
            name.unshift(parent.name);
            parent = parent.parent;
        }

        console.log(name.join(&#39; / &#39;));
    }
};

构造好组合对象与叶对象的关系后,实例化,在组合对象中插入组合或叶对象

var web = new Folder(&#39;Web&#39;);
var fe = new Folder(&#39;前端&#39;);
var css = new Folder(&#39;CSS&#39;);
var js = new Folder(&#39;js&#39;);
var rd = new Folder(&#39;后端&#39;);

web.add(fe).add(rd);

var file1 = new File(&#39;HTML权威指南.pdf&#39;);
var file2 = new File(&#39;CSS权威指南.pdf&#39;);
var file3 = new File(&#39;JavaScript权威指南.pdf&#39;);
var file4 = new File(&#39;MySQL基础.pdf&#39;);
var file5 = new File(&#39;Web安全.pdf&#39;);
var file6 = new File(&#39;Linux菜鸟.pdf&#39;);

css.add(file2);
fe.add(file1).add(file3).add(css).add(js);
rd.add(file4).add(file5);
web.add(file6);

rd.remove(file4);

// 扫描
web.scan();

扫描结果为

 4. 优缺点

优点

可 以方便地构造一棵树来表示对象的部分-整体 结构。在树的构造最终 完成之后,只需要通过请求树的最顶层对 象,便能对整棵树做统一一致的操作。

缺点

创建出来的对象长得都差不多,可能会使代码不好理解,创建太多的对象对性能也会有一些影响

八、模板方法模式

1. 定义

模板方法模式由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类。

2. 核心

在抽象父类中封装子类的算法框架,它的 init方法可作为一个算法的模板,指导子类以何种顺序去执行哪些方法。

由父类分离出公共部分,要求子类重写某些父类的(易变化的)抽象方法

3. 实现

模板方法模式一般的实现方式为继承

以运动作为例子,运动有比较通用的一些处理,这部分可以抽离开来,在父类中实现。具体某项运动的特殊性则有自类来重写实现。

最终子类直接调用父类的模板函数来执行

// 体育运动
function Sport() {

}

Sport.prototype = {
    constructor: Sport,
    
    // 模板,按顺序执行
    init: function() {
        this.stretch();
        this.jog();
        this.deepBreath();
        this.start();

        var free = this.end();
        
        // 运动后还有空的话,就拉伸一下
        if (free !== false) {
            this.stretch();
        }
        
    },
    
    // 拉伸
    stretch: function() {
        console.log(&#39;拉伸&#39;);
    },
    
    // 慢跑
    jog: function() {
        console.log(&#39;慢跑&#39;);
    },
    
    // 深呼吸
    deepBreath: function() {
        console.log(&#39;深呼吸&#39;);
    },

    // 开始运动
    start: function() {
        throw new Error(&#39;子类必须重写此方法&#39;);
    },

    // 结束运动
    end: function() {
        console.log(&#39;运动结束&#39;);
    }
};

// 篮球
function Basketball() {

}

Basketball.prototype = new Sport();

// 重写相关的方法
Basketball.prototype.start = function() {
    console.log(&#39;先投上几个三分&#39;);
};

Basketball.prototype.end = function() {
    console.log(&#39;运动结束了,有事先走一步&#39;);
    return false;
};


// 马拉松
function Marathon() {

}

Marathon.prototype = new Sport();

var basketball = new Basketball();
var marathon = new Marathon();

// 子类调用,最终会按照父类定义的顺序执行
basketball.init();
marathon.init();

九、享元模式

1. 定义

享元(flyweight)模式是一种用于性能优化的模式,它的目标是尽量减少共享对象的数量

2. 核心

运用共享技术来有效支持大量细粒度的对象。

强调将对象的属性划分为内部状态(属性)与外部状态(属性)。内部状态用于对象的共享,通常不变;而外部状态则剥离开来,由具体的场景决定。

3. 实现

在程序中使用了大量的相似对象时,可以利用享元模式来优化,减少对象的数量

举个栗子,要对某个班进行身体素质测量,仅测量身高体重来评判

// 健康测量
function Fitness(name, sex, age, height, weight) {
    this.name = name;
    this.sex = sex;
    this.age = age;
    this.height = height;
    this.weight = weight;
}

// 开始评判
Fitness.prototype.judge = function() {
    var ret = this.name + &#39;: &#39;;

    if (this.sex === &#39;male&#39;) {
        ret += this.judgeMale();
    } else {
        ret += this.judgeFemale();
    }

    console.log(ret);
};

// 男性评判规则
Fitness.prototype.judgeMale = function() {
    var ratio = this.height / this.weight;

    return this.age > 20 ? (ratio > 3.5) : (ratio > 2.8);
};

// 女性评判规则
Fitness.prototype.judgeFemale = function() {
    var ratio = this.height / this.weight;
    
    return this.age > 20 ? (ratio > 4) : (ratio > 3);
};


var a = new Fitness(&#39;A&#39;, &#39;male&#39;, 18, 160, 80);
var b = new Fitness(&#39;B&#39;, &#39;male&#39;, 21, 180, 70);
var c = new Fitness(&#39;C&#39;, &#39;female&#39;, 28, 160, 80);
var d = new Fitness(&#39;D&#39;, &#39;male&#39;, 18, 170, 60);
var e = new Fitness(&#39;E&#39;, &#39;female&#39;, 18, 160, 40);

// 开始评判
a.judge(); // A: false
b.judge(); // B: false
c.judge(); // C: false
d.judge(); // D: true
e.judge(); // E: true

评判五个人就需要创建五个对象,一个班就几十个对象

可以将对象的公共部分(内部状态)抽离出来,与外部状态独立。将性别看做内部状态即可,其他属性都属于外部状态。

这么一来我们只需要维护男和女两个对象(使用factory对象),而其他变化的部分则在外部维护(使用manager对象)

// 健康测量
function Fitness(sex) {
    this.sex = sex;
}

// 工厂,创建可共享的对象
var FitnessFactory = {
    objs: [],

    create: function(sex) {
        if (!this.objs[sex]) {
            this.objs[sex] = new Fitness(sex);
        }

        return this.objs[sex];
    }
};

// 管理器,管理非共享的部分
var FitnessManager = {
    fitnessData: {},
    
    // 添加一项
    add: function(name, sex, age, height, weight) {
        var fitness = FitnessFactory.create(sex);
        
        // 存储变化的数据
        this.fitnessData[name] = {
            age: age,
            height: height,
            weight: weight
        };

        return fitness;
    },
    
    // 从存储的数据中获取,更新至当前正在使用的对象
    updateFitnessData: function(name, obj) {
        var fitnessData = this.fitnessData[name];

        for (var item in fitnessData) {
            if (fitnessData.hasOwnProperty(item)) {
                obj[item] = fitnessData[item];
            }
        }
    }
};

// 开始评判
Fitness.prototype.judge = function(name) {
    // 操作前先更新当前状态(从外部状态管理器中获取)
    FitnessManager.updateFitnessData(name, this);

    var ret = name + &#39;: &#39;;

    if (this.sex === &#39;male&#39;) {
        ret += this.judgeMale();
    } else {
        ret += this.judgeFemale();
    }

    console.log(ret);
};

// 男性评判规则
Fitness.prototype.judgeMale = function() {
    var ratio = this.height / this.weight;

    return this.age > 20 ? (ratio > 3.5) : (ratio > 2.8);
};

// 女性评判规则
Fitness.prototype.judgeFemale = function() {
    var ratio = this.height / this.weight;
    
    return this.age > 20 ? (ratio > 4) : (ratio > 3);
};


var a = FitnessManager.add(&#39;A&#39;, &#39;male&#39;, 18, 160, 80);
var b = FitnessManager.add(&#39;B&#39;, &#39;male&#39;, 21, 180, 70);
var c = FitnessManager.add(&#39;C&#39;, &#39;female&#39;, 28, 160, 80);
var d = FitnessManager.add(&#39;D&#39;, &#39;male&#39;, 18, 170, 60);
var e = FitnessManager.add(&#39;E&#39;, &#39;female&#39;, 18, 160, 40);

// 开始评判
a.judge(&#39;A&#39;); // A: false
b.judge(&#39;B&#39;); // B: false
c.judge(&#39;C&#39;); // C: false
d.judge(&#39;D&#39;); // D: true
e.judge(&#39;E&#39;); // E: true

不过代码可能更复杂了,这个例子可能还不够充分,只是展示了享元模式如何实现,它节省了多个相似的对象,但多了一些操作。

factory对象有点像单例模式,只是多了一个sex的参数,如果没有内部状态,则没有参数的factory对象就更接近单例模式了

十、职责链模式

1. 定义

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链 传递该请求,直到有一个对象处理它为止

2. 核心

请求发送者只需要知道链中的第一个节点,弱化发送者和一组接收者之间的强联系,可以便捷地在职责链中增加或删除一个节点,同样地,指定谁是第一个节点也很便捷

3. 实现

以展示不同类型的变量为例,设置一条职责链,可以免去多重if条件分支

// 定义链的某一项
function ChainItem(fn) {
    this.fn = fn;
    this.next = null;
}

ChainItem.prototype = {
    constructor: ChainItem,
    
    // 设置下一项
    setNext: function(next) {
        this.next = next;
        return next;
    },
    
    // 开始执行
    start: function() {
        this.fn.apply(this, arguments);
    },
    
    // 转到链的下一项执行
    toNext: function() {
        if (this.next) {
            this.start.apply(this.next, arguments);
        } else {
            console.log(&#39;无匹配的执行项目&#39;);
        }
    }
};

// 展示数字
function showNumber(num) {
    if (typeof num === &#39;number&#39;) {
        console.log(&#39;number&#39;, num);
    } else {
        // 转移到下一项
        this.toNext(num);
    }
}

// 展示字符串
function showString(str) {
    if (typeof str === &#39;string&#39;) {
        console.log(&#39;string&#39;, str);
    } else {
        this.toNext(str);
    }
}

// 展示对象
function showObject(obj) {
    if (typeof obj === &#39;object&#39;) {
        console.log(&#39;object&#39;, obj);
    } else {
        this.toNext(obj);
    }
}

var chainNumber = new ChainItem(showNumber);
var chainString = new ChainItem(showString);
var chainObject = new ChainItem(showObject);

// 设置链条
chainObject.setNext(chainNumber).setNext(chainString);

chainString.start(&#39;12&#39;); // string 12
chainNumber.start({}); // 无匹配的执行项目
chainObject.start({}); // object {}
chainObject.start(123); // number 123

这时想判断未定义的时候呢,直接加到链中即可


// 展示未定义
function showUndefined(obj) {
    if (typeof obj === &#39;undefined&#39;) {
        console.log(&#39;undefined&#39;);
    } else {
        this.toNext(obj);
    }
}

var chainUndefined = new ChainItem(showUndefined);
chainString.setNext(chainUndefined);

chainNumber.start(); // undefined

由例子可以看到,使用了职责链后,由原本的条件分支换成了很多对象,虽然结构更加清晰了,但在一定程度上可能会影响到性能,所以要注意避免过长的职责链。

十一、中介者模式

1. 定义

所有的相关 对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可

2. 核心

使网状的多对多关系变成了相对简单的一对多关系(复杂的调度处理都交给中介者)

使用中介者后

3. 实现

多个对象,指的不一定得是实例化的对象,也可以将其理解成互为独立的多个项。当这些项在处理时,需要知晓并通过其他项的数据来处理。

如果每个项都直接处理,程序会非常复杂,修改某个地方就得在多个项内部修改

我们将这个处理过程抽离出来,封装成中介者来处理,各项需要处理时,通知中介者即可。

var A = {
    score: 10,

    changeTo: function(score) {
        this.score = score;

        // 自己获取
        this.getRank();
    },
    
    // 直接获取
    getRank: function() {
        var scores = [this.score, B.score, C.score].sort(function(a, b) {
            return a < b;
        });

        console.log(scores.indexOf(this.score) + 1);
    }
};

var B = {
    score: 20,

    changeTo: function(score) {
        this.score = score;

        // 通过中介者获取
        rankMediator(B);
    }
};

var C = {
    score: 30,

    changeTo: function(score) {
        this.score = score;

        rankMediator(C);
    }
};

// 中介者,计算排名
function rankMediator(person) {
    var scores = [A.score, B.score, C.score].sort(function(a, b) {
        return a < b;
    });

    console.log(scores.indexOf(person.score) + 1);
}

// A通过自身来处理
A.changeTo(100); // 1

// B和C交由中介者处理
B.changeTo(200); // 1
C.changeTo(50); // 3

ABC三个人分数改变后想要知道自己的排名,在A中自己处理,而B和C使用了中介者。B和C将更为轻松,整体代码也更简洁

最后,虽然中介者做到了对模块和对象的解耦,但有时对象之间的关系并非一定要解耦,强行使用中介者来整合,可能会使代码更为繁琐,需要注意。

十二、装饰者模式

1. 定义

以动态地给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象。

是一种“即用即付”的方式,能够在不改变对 象自身的基础上,在程序运行期间给对象动态地 添加职责

2. 核心

是为对象动态加入行为,经过多重包装,可以形成一条装饰链

3. 实现

最简单的装饰者,就是重写对象的属性

var A = {
    score: 10
};

A.score = '分数:' + A.score;

可以使用传统面向对象的方法来实现装饰,添加技能

function Person() {}

Person.prototype.skill = function() {
    console.log(&#39;数学&#39;);
};

// 装饰器,还会音乐
function MusicDecorator(person) {
    this.person = person;
}

MusicDecorator.prototype.skill = function() {
    this.person.skill();
    console.log(&#39;音乐&#39;);
};

// 装饰器,还会跑步
function RunDecorator(person) {
    this.person = person;
}

RunDecorator.prototype.skill = function() {
    this.person.skill();
    console.log(&#39;跑步&#39;);
};

var person = new Person();

// 装饰一下
var person1 = new MusicDecorator(person);
person1 = new RunDecorator(person1);

person.skill(); // 数学
person1.skill(); // 数学 音乐 跑步

在JS中,函数为一等对象,所以我们也可以使用更通用的装饰函数

// 装饰器,在当前函数执行前先执行另一个函数
function decoratorBefore(fn, beforeFn) {
    return function() {
        var ret = beforeFn.apply(this, arguments);
        
        // 在前一个函数中判断,不需要执行当前函数
        if (ret !== false) {
            fn.apply(this, arguments);
        }
    };
}


function skill() {
    console.log(&#39;数学&#39;);
}

function skillMusic() {
    console.log(&#39;音乐&#39;);
}

function skillRun() {
    console.log(&#39;跑步&#39;);
}

var skillDecorator = decoratorBefore(skill, skillMusic);
skillDecorator = decoratorBefore(skillDecorator, skillRun);

skillDecorator(); // 跑步 音乐 数学

十三、状态模式

1. 定义

事物内部状态的改变往往会带来事物的行为改变。在处理的时候,将这个处理委托给当前的状态对象即可,该状态对象会负责渲染它自身的行为

2. 核心

区分事物内部的状态,把事物的每种状态都封装成单独的类,跟此种状态有关的行为都被封装在这个类的内部

3. 实现

以一个人的工作状态作为例子,在刚醒、精神、疲倦几个状态中切换着

// 工作状态
function Work(name) {
    this.name = name;
    this.currentState = null;

    // 工作状态,保存为对应状态对象
    this.wakeUpState = new WakeUpState(this);
    // 精神饱满
    this.energeticState = new EnergeticState(this);
    // 疲倦
    this.tiredState = new TiredState(this);

    this.init();
}

Work.prototype.init = function() {
    this.currentState = this.wakeUpState;
    
    // 点击事件,用于触发更新状态
    document.body.onclick = () => {
        this.currentState.behaviour();
    };
};

// 更新工作状态
Work.prototype.setState = function(state) {
    this.currentState = state;
}

// 刚醒
function WakeUpState(work) {
    this.work = work;
}

// 刚醒的行为
WakeUpState.prototype.behaviour = function() {
    console.log(this.work.name, &#39;:&#39;, &#39;刚醒呢,睡个懒觉先&#39;);
    
    // 只睡了2秒钟懒觉就精神了..
    setTimeout(() => {
        this.work.setState(this.work.energeticState);
    }, 2 * 1000);
}

// 精神饱满
function EnergeticState(work) {
    this.work = work;
}

EnergeticState.prototype.behaviour = function() {
    console.log(this.work.name, &#39;:&#39;, &#39;超级精神的&#39;);
    
    // 才精神1秒钟就发困了
    setTimeout(() => {
        this.work.setState(this.work.tiredState);
    }, 1000);
};

// 疲倦
function TiredState(work) {
    this.work = work;
}

TiredState.prototype.behaviour = function() {
    console.log(this.work.name, &#39;:&#39;, &#39;怎么肥事,好困&#39;);
    
    // 不知不觉,又变成了刚醒着的状态... 不断循环呀
    setTimeout(() => {
        this.work.setState(this.work.wakeUpState);
    }, 1000);
};


var work = new Work(&#39;曹操&#39;);

点击一下页面,触发更新状态的操作

4. 优缺点

优点

状态切换的逻辑分布在状态类中,易于维护

缺点

多个状态类,对于性能来说,也是一个缺点,这个缺点可以使用享元模式来做进一步优化

将逻辑分散在状态类中,可能不会很轻易就能看出状态机的变化逻辑

十四、适配器模式

1. 定义

是解决两个软件实体间的接口不兼容的问题,对不兼容的部分进行适配

2. 核心

解决两个已有接口之间不匹配的问题

3. 实现

比如一个简单的数据格式转换的适配器

// 渲染数据,格式限制为数组了
function renderData(data) {
    data.forEach(function(item) {
        console.log(item);
    });
}

// 对非数组的进行转换适配
function arrayAdapter(data) {
    if (typeof data !== &#39;object&#39;) {
        return [];
    }

    if (Object.prototype.toString.call(data) === &#39;[object Array]&#39;) {
        return data;
    }

    var temp = [];

    for (var item in data) {
        if (data.hasOwnProperty(item)) {
            temp.push(data[item]);
        }
    }

    return temp;
}

var data = {
    0: &#39;A&#39;,
    1: &#39;B&#39;,
    2: &#39;C&#39;
};

renderData(arrayAdapter(data)); // A B C

十五、外观模式

1. 定义

为子系统中的一组接口提供一个一致的界面,定义一个高层接口,这个接口使子系统更加容易使用

2. 核心

可以通过请求外观接口来达到访问子系统,也可以选择越过外观来直接访问子系统

3. 实现

外观模式在JS中,可以认为是一组函数的集合

// 三个处理函数
function start() {
    console.log(&#39;start&#39;);
}

function doing() {
    console.log(&#39;doing&#39;);
}

function end() {
    console.log(&#39;end&#39;);
}

// 外观函数,将一些处理统一起来,方便调用
function execute() {
    start();
    doing();
    end();
}


// 调用init开始执行
function init() {
    // 此处直接调用了高层函数,也可以选择越过它直接调用相关的函数
    execute();
}

init(); // start doing end

【相关推荐:javascript学习教程

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn