1-3、前端编程思想进阶-常用设计模式(3)

109 阅读7分钟

一、常见设计模式(3)

1. 课堂知识点&课堂目标

课堂知识点

  • 工厂模式及自定义事件
  • 抽离英雄基类
  • 设计原则
  • 单例模式使用
  • 装饰者模式使用
  • 代理模式
  • 观察者模式
  • 适配器模式...

课堂目标

  • 学会各种设计模式的使用
  • 学会面向对象中抽象使用
  • 理解类中的继承
  • oop思想规划项目

2. 设计原则

SOLID(稳定的)

  • 1、单一职责原则(Single Responsibility Principle)

    • 一个类应该只有一个发生变化的原因。简而言之就是每个类只需要负责自己的那部分,类的复杂度就会降低。
  • 2、开闭原则(Open Closed Principle)

    • 一个软件实体,如类、模块和函数应该对扩展开放,对修改关闭
  • 3、里氏替换原则(Liskov Substitution Principle)

    • 所有引用基类的地方必须能透明地使用其子类的对象,也就是说子类对象可以替换其父类对象,而程序执行效果不变。
// 里氏替换原则
class Hero{
    constructor(name){
        this.name = name;
    }
    fly(){
        console.log("飞");
    }
}

class Yase extends Hero{
    constructor(){
        super("亚瑟"); //继承了Hero,继承了飞的功能
    }
    // fly(){ //子类尽量不要重写父类,违背了我们的设计原则
        // console.log("不能飞");
    // }
}

let yase = new Yase();
yase.fly()
  • 4、迪米特法则(Law of Demeter)

    • 迪米特法则(Law of Demeter)又叫作最少知识原则(The Least Knowledge Principle),一个类对于其他类知道的越少越好,就是说一个对象应当对其他对象有尽可能少的了解,只和朋友通信,不和陌生人说话。

    A和B通信 B和C通信 不建议A和C跨层级通信 容易造成混乱;

  • 5、接口隔离原则(Interface Segregation Principle) flow 、ts

    • 多个特定的客户端接口要好于一个通用性的总接口
  • 6、依赖倒置原则(Dependence Inversion Principle)

    • 上层模块不应该依赖底层模块,它们都应该依赖于抽象。
    • 抽象不应该依赖于细节,细节应该依赖于抽象(张三 依赖于 人类)

案例内容扩展

  • 扩展皮肤功能
  • 抽离英雄基类
    • 每个英雄的共有属性
    • 共有方法及独特方法
  • 抽离技能基类

3. 设计模式

设计模式是软件开发人员在软件开发过程中面临的一些具有代表性问题的解决方案。 这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的;

(1)单例模式(Singleton Pattern)

单例模式 (Singleton Pattern)又称为单体模式,保证一个类只有一个实例,并提供一个访问它的全局访问点。也就是说,第二次使用同一个类创建新对象的时候,应该得到与第一次创建的对象完全相同的对象。

// window document store
// 单例模式
class Person{
    static instance; //静态变量的方式:将实例化对象保存起来
    constructor(name){
        if(!Person.instance){
            Person.instance = *this*;
        }else{
            return Person.instance;
        }
        this.name = name;
    }
}

let zhangsan = new Person("张三");
let lisi = new Person("李四");
console.log(zhangsan===lisi); //true 都打印张三

image.png

//两个对象其实也是 简单的单例,只不过不需要实例化
//let obj = new Object()
let obj1 = {
    name:"张三",
    age:20
}
let obj2 = {
    name:"张三",
    age:20
}
console.log(obj1===obj2);//false 对象地址不一样

image.png

image.png

通过静态属性创建单例

class Person{
    static instance = null;
    constructor(name){
        if(Person.instance){
            return Person.instance;
        }
        Person.instance = this;
        this.name = name;
    }
}

通过函数创建单例

let instance;
function createInstance(...arg){
    if(!instance){
        instance = new Game(...arg);
    }
    return instance;
}

实现game类的单例

  • 优:单例模式节约内存开支和实例化时的性能开支,节约性能;
  • 缺:单例模式扩展性不强

通用单例(多个类,都变成单例)

class Perosn{
    constructor(name){
        this.name = name;
    }
}
class Animal{
    constructor(name){
        this.name = name;
    }
}

// 创建通用单例(instance实例)
function createInstance(fn){
    let instance;
    return function(...args){
        if(!instance){
            instance = new fn(...args);
        }
        return instance
    }
}

单例应用(单个实例化)

// 单例应用
class Dialog{
    constructor(){
        let dialog = document.createElement("div");
        this.dialog = dialog;
        this.dialog.style.display"none";
        this.isShowfalse;
    }

    showDialog(){
        if(!this.isShow){
            this.dialog.innerHTML"对话框";
            document.body.appendChild(this.dialog);
            this.dialog.style.display"block";
            this.isShowtrue;
        }else{
            console.log("已经显示了一个")
        }
    }
}

    //dialog1,dialog2 保证了每个实例化里都是只显示一个弹框,所以会显示两个弹框显示;
    //不能保证只能有一个实例!!! 打印两次不一样的数据
    let dialog1 = new Dialog();
    // let dialog2 = new Dialog();

    //createInstance 只能实例化一次(见上——通用单例)打印两条一样的实例
    let createInstanceFn = createInstance(Dialog);
    let dialog1 = **new** createInstanceFn();
    // let dialog2 = new createInstanceFn();

    document.querySelector("button").onclickfunction(){
        dialog1.showDialog();
        // dialog2.showDialog();
    }

(2)工厂模式(Factory Pattern)

  • 工厂模式 (Factory Pattern),封装具体实例创建逻辑和过程,外部只需要根据不同条件返回不同的实例。
    • 优点:实现代码复用性,封装良好,抽象逻辑;
    • 缺点:增加了代码复杂程度;
class Luban {
    constructor() {
        this.name"鲁班";
    }
}
class Yase {
    constructor() {
        this.name"亚瑟";
    }
}
function Factory(heroName) {
    switch (heroName) {
        case 'luban':
            return new Luban();
            break;
        case 'yase':
            return new Yase();
            break;
        default:
            console.log("无英雄");
            break;
    }
}

let yase = Factory("yase");
let luban = Factory("luban");
console.log(yase,luban);

(3)装饰者模式(Decorator Pattern)

  • 装饰者模式 (Decorator Pattern)使用一种更为灵活的方式来动态给一个对象/函数等添加额外信息
    • 扩展功能 和继承类似
    • 扩展不同类的功能,和原始类并无关联;
//装饰者模式: 功能扩展 extends
class Yase{
    constructor(){
        this.name"亚瑟";
    }
    release(){
        console.log("释放技能");
    }
}

let yase = new Yase();
// yase.release();

function hurt(){
    console.log("造成100点伤害");
}
function walk(){
    console.log("走");
}
Function.prototype.Decorator = function(fn){
    let _this = this;
    return function(){
        _this();
        fn();
    }
}

// yase.release.Decorator(hurt)(); //relase‘释放技能’—>扩展
// 装饰者链                                            
yase.release.Decorator(hurt).Decorator(walk)();

(4)观察者模式(自定义事件 Observer Pattern)

  • 观察者模式 (Observer Pattern) 定义一个对象与其他对象之间的一种依赖关系,当对象发生某种变化的时候,依赖它的其它对象都会得到更新

    • 自定义事件绑定addEvent
    • 自定义事件触发trigger
    • 自定义事件移除removeEvent
  • 实现案例GameEvent类

document.querySelector(".box").addEventListener("click",function(){
    console.log("click1");
})//惰性执行,点击才执行


// 管理事件类
class MyEvent{
    constructor(){
        *this*.handles = {};
    }
    addEvent(*eventName*,*fn*){
    // {myevent1:[fn1,fn2...], myevent2:[fn1,fn2...]}
        if(typeof *this*.handles[*eventName*]==="undefined"){
            *this*.handles[*eventName*] = [];
        }
        *this*.handles[*eventName*].push(*fn*);
    }
    trigger(*eventName*){ //触发
        if(!(*eventName* in *this*.handles)){
            return ;
        }
        *this*.handles[*eventName*].forEach(*fn*=>{
            *fn*();
        })
    }
}

let obj1 = {
    fn1(){ console.log("fn1更新"); }
}
let obj2 = {
    fn2(){ console.log("fn2更新"); }
}

let eventObj = new MyEvent();
eventObj.addEvent("myevent", obj1.fn1);
eventObj.addEvent("myevent", obj2.fn2);
// eventObj.removeEvent("myevent", obj2.fn2)

setTimeout(()=>{
    eventObj.trigger("myevent")
},1000)

image.png

image.png

(5)代理模式(Proxy Pattern)

  • 代理模式 为其他对象提供一种代理以控制对这个对象的访问,类似于生活中的中介。
    • 为hero添加代理模式 控制 伤害的输出
// 代理模式
let zhangsan ={
    sellHouse(num){
        console.log("卖了"+num + "万元");
    }
}

// 中介--代理模式
// zhangsan.sellHouse(100);
let proxySeller = {
    sellHouse(hasSold, num){
        if(hasSold){
            zhangsan.sellHouse(num-2);
        }else{
            zhangsan.sellHouse(num);
        }
    }
}
proxySeller.sellHouse(true,100);
// proxy 服务器代理 转发请求
// Proxy 代理对象 (后面会讲)

// 创建图片
class CreateImage{
    constructor(){
        this.imgdocument.createElement("img");
        document.body.appendChild(this.img);
    }
    setSrc(src){
        this.img.src = src;
    }
}

// let img = new CreateImage();
let src = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fyouimg1.c-ctrip.com%2Ftarget%2Ftg%2F035%2F063%2F726%2F3ea4031f045945e1843ae5156749d64c.jpg&refer=http%3A%2F%2Fyouimg1.c-ctrip.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1621318136&t=b1dc094a27bb4f111a5787a6d4ca21c5";

// img.setSrc(src); 此种方法可行,但网络很差的情况, 加载就会很慢
//加载过程中显示一个loading图,加载完全才显示图片;               
function proxyImg(src){
    let myImg = new CreateImage();
    let loadImg = new Image();

    myImg.setSrc("./loading.gif");
    loadImg.src = src;
    loadImg.onloadfunction(){
        myImg.setSrc(src);
    }
}

proxyImg(src);

(6)适配器模式(Adaptor Pattern)

  • 两个不兼容的接口之间的桥梁,将一个类的接口转换成客户希望的另外一个接口。 适配器模式使得原本由于 接口不兼容而不能一起工作的那些类可以一起工作。
// 适配器模式(不兼容的东西,通过适配器变的兼容起来)
function getUsers(){
    return [{
        name: "zhangsan",
        age: 20
    },{
        name: "lisi",
        age: 30
    }]
}

// [{zhangsan: 20}, {lisi: 30}] 想要这种形式
function Adaptor(users){
    let arr = [];
    for(let i=0;i<users.length;i++){
        arr[users[i].name] = users[i].age;
    }
    return arr;
}

let res = Adaptor(getUsers());
console.log(res);

前端应用的知识点有 axios (平台适配) mixins混入模式,如下例子

// 混入模式
class Yase{
    constructor(){
        this.name"亚瑟";
    }
}

class Skills{
    hurt(){ console.log("造成伤害") }
    walk(){ console.log("走路"); }
    release(){ console.log("释放技能"); }
}

//多继承方式是没有的,所以采用混入模式;
// let yase = new Yase();
// 混入模式
function mixin(receivingClass, givingClass){
    if(typeof arguments[2] !== "undefined"){
        for(let i=2;i<arguments.length;i++){
            receivingClass.prototype[arguments[i]]= givingClass.prototype[arguments[i]];
        }
    }
}
//前两参数混入的类,后三混入的方法
mixin(Yase,Skills,"hurt","walk","release");

//Skils里面的方法,混入到Yase里
let yase = new Yase();
console.log(yase);
yase.release();

(7)享元模式-共享,flyweight 轻量级

享元模式: 运用共享技术来有效地支持对象的复用,以减少创建的对象的数量。

  • 通过共享对象节约内存资源,提高性能和效率

享元模式

//享元模式
let userNumber = 10; //单数是大人 双数是小孩;
let horseNum = 0;

class CreateHorse{
    constructor(type){
        horseNum++;
        this.horseNum = horseNum;
        this.type = type ? '大马' : '小马';
    }

    ride(uid){
        console.log(`人${uid}在骑${this.horseNum}${this.type}`);
    }
}

// 更多实例
for(let i=1;i<=userNumber;i++){
    let horse;
    if(i%2===0){
        horse = new CreateHorse(false);
    }else{
        horse = new CreateHorse(true);
    }
    horse.ride(i);
}
console.log(horseNum);

//如上做了10次实例化操作,会造成资源浪费,优化如下;
//记录了实例化次数

image.png

优化享元模式

// 优化享元模式
let smallHorse = new CreateHorse(false);//小马
let bigHorse = new CreateHorse(true);   //大马
for(let i=1;i<=userNumber;i++){
    if(i%2===0){
        smallHorse.ride(i);
    }else{
        bigHorse.ride(i);
    }
}
console.log(horseNum);

image.png

改进享元模式—执行时间,内存占用时间

// 改进享元模式(马的使用时长,每个用户占用时间)
class CreateHorse{
    constructor(type){
        horseNum++;
        this.horseNum = horseNum;
        this.type = type ? '大马' : '小马';
        this.finishtrue;
    }

    ride(uid){
        return new Promise(resolve =>{
            console.log(`人${uid}在骑${this.horseNum}${this*.type}`);
            this.finishfalse;
            setTimeout(() => {
                resolve(`人${uid}在骑${this.horseNum}${this.type}骑行完毕!!`);
                this.finishtrue;
            }, 2000);
        })
    }
}

//马池

class HorsePool{
    constructor(){
        this.horse = [new CreateHorse(true), new CreateHorse(true),new CreateHorse(true)];
        this.people = [1,2,3,4,5,6,7,8,9,10];
    }

    rideHorse(){
        this.people.forEach(uid => {
            let horse = this.getHorse();
            if(horse){
                horse.ride(uid).then(res => {
                    console.log(res);
                    this.people.shift() && this.rideHorse() && this.people.length;
                })
            }
        })
    }

    getHorse(){
        return this.horse.find(item => item.finish);
    }
}

let horsePool = new HorsePool();
horsePool.rideHorse();

4. 课程总结

  • 工厂模式及观察者模式
  • 抽离英雄基类
  • 抽离技能基类
  • 扩展皮肤类
  • 单例模式使用
  • 装饰者模式使用
  • 代理模式