前端设计与应用

87 阅读9分钟

设计模式相关知识 | 青训营笔记

这是我参与[第四届青训营]笔记创作活动的第4天

一、首先,让我们来了解设计模式是什么?

设计模式是软件设计中常见问题的解决方案模型,历史经验的总结,与特定语言无关;目的是为了提高代码的可复用性、可读性、可维护性。设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。

二、设计模式分类——23种设计模式

  • 5种创建型——如何创建一个对象
  • 7种结构型——如何灵活地将对象组装成较大的结构
  • 11种行为型——负责对象间的高效通信和职责划分
    2.1浏览器中的设计模式
    2.1.1单例模式
    它是全局唯一访问对象,主要应用于缓存、全局状态管理
    下面代码是使用单例模式实现请求缓存:
import {api} from "./utils";
export class Requset {
    static instance:Requset;
    private cache:Record<string,string>;
    
    constructor(){
        this.cache = {};
        }
        static getInstance(){
            if(this.instance){
                return this.instance;
                }
                this.instance = new Requset();
                return this.instance;
            }

            public async request(url:string){
                if(this.cache[url]){
                    return this.cache[url];
                }
                const response = await api(url);
                this.cache[url] = response;

                return response;
            }
        } 
import {api} from "./utils";
export class Requset {
    static instance:Requset;
    private cache:Record<string,string>;
    
    constructor(){
        this.cache = {};
        }
        static getInstance(){
            if(this.instance){
                return this.instance;
                }
                this.instance = new Requset();
                return this.instance;
            }

            public async request(url:string){
                if(this.cache[url]){
                    return this.cache[url];
                }
                const response = await api(url);
                this.cache[url] = response;

                return response;
            }
        } 

2.1.2 发布订阅模式 它是一种订阅机制,可以再被订阅对象发生变化时通知订阅者,通常应用于从系统架构之间的解耦,到业务中一些实现模式,比如邮件订阅、上线订阅等等。

三、JavaScript中的设计模式

  • 原型模式
  • 代理模式
  • 迭代器模式
    3.1原型模式 是复制已有对象来创建新的对象,是JS中对象创建的基本模式。 例如:有一只羊,知道名字、年龄、颜色,希望创建和它属性完全相同的10只羊。可以用原型模式来进行羊的克隆
package com.ygp.prototype.improve;

public class Sheep implements Cloneable { private String name;
private int age; private String color;
private String address = "蒙古羊";
public Sheep friend; //是对象,  克隆是会如何处理,  默认是浅拷贝
public Sheep(String name, int age, String color) { super();
this.name = name; this.age = age; this.color = color;
}
public String getName() { return name;
}
public void setName(String name) { this.name = name;
}
public int getAge() { return age;
}
public void setAge(int age) {

this.age = age;
}
public String getColor() { return color;
}
public void setColor(String color) { this.color = color;
}
@Override
public String toString() {
return "Sheep [name=" + name + ", age=" + age + ", color=" + color + ", address=" + address + "]";
}
//克隆该实例,使用默认的 clone 方法来完成
@Override
protected Object clone()	{

Sheep sheep = null; try {
sheep = (Sheep)super.clone();
} catch (Exception e) {
// TODO: handle exception System.out.println(e.getMessage());
}
// TODO Auto-generated method stub return sheep;
}

}
package com.ygp.prototype.improve;

public class Client {

public static void main(String[] args) {
System.out.println("原型模式完成对象的创建");
// TODO Auto-generated method stub
Sheep sheep = new Sheep("tom", 1, "白色");

sheep.friend = new Sheep("jack", 2, "黑色");

Sheep sheep2 = (Sheep)sheep.clone(); //克隆Sheep sheep3 = (Sheep)sheep.clone(); //克隆Sheep sheep4 = (Sheep)sheep.clone(); //克隆Sheep sheep5 = (Sheep)sheep.clone(); //克隆

System.out.println("sheep2 =" + sheep2 + "sheep2.friend=" + sheep2.friend.hashCode());

System.out.println("sheep3 =" + sheep3 + "sheep3.friend=" + sheep3.friend.hashCode()); System.out.println("sheep4 =" + sheep4 + "sheep4.friend=" + sheep4.friend.hashCode()); System.out.println("sheep5 =" + sheep5 + "sheep5.friend=" + sheep5.friend.hashCode());
}
}

3.2代理模式
可自定义控制对原对象的访问方式,并且允许在更新前后做一些额外处理,通常应用于监控、代理工具、前端框架实现等

package com.zqh.proxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface IMessage{//传统代理设计必须有接口
    public void send();
}
class MessageReal implements IMessage{
    @Override
    public void send() {
        System.out.println("消息发送");
    }

}
class MessageProxy implements IMessage{
    private IMessage message;//代理对象,一定是业务接口实例
    public MessageProxy(IMessage message){
        this.message = message;
    }
    public boolean connect(){
        System.out.println("消息代理——连接");
        return true;
    }
    public void close(){
        System.out.println("消息代理——关闭");
    }
    @Override
    public void send() {
        if(this.connect()){
            this.message.send();
            this.close();
        }
    }
}

class ProxyHandler implements InvocationHandler{
    private Object target;//保存真实业务对象
    /**
     * 进行真实业务对象与代理业务对象之间的绑定处理
     * @param target 真实业务对象
     * @return Proxy生成的代理业务对象
     */
    public Object bind(Object target){
        this.target = target;
        /**
         * 对于动态对象的创建是由JVM底层完成的,此时主要依靠的时java.lang.reflect.Proxy程序类,只提供有一个核心方法:
         * 代理对象:public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,
         InvocationHandler h) throws IllegalArgumentException
         * 		ClassLoader loader:获取当前真实主体类的ClassLoader;
         * 		Class<?>[] interfaces:代理是围绕接口进行的,所以一定要获取真实主题类的接口信息;
         * 		InvocationHandler h:代理处理的方法。
         */
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }
    public boolean connect(){
        System.out.println("消息代理——连接");
        return true;
    }
    public void close(){
        System.out.println("消息代理——关闭");
    }
    /**
     * public interface InvocationHandler
     * 代理方法调用,代理主题类里面执行的方法最终都是此方法
     * @param proxy 要代理的对象
     * @param method 要执行的接口方法名称
     * @param args 传递的参数
     * @return 某一个方法的返回值
     * @throws Throwable 方法调用时出现的错误继续向上抛出
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("执行方法:\t"+method);
        Object returnData = null;
        if(this.connect()){
            returnData = method.invoke(this.target, args);
            this.close();
        }
        return returnData;
    }
}
public class ProxyImpl {
    public static void main(String[] args) {
//静态代理设计模式
        System.out.println("-----静态代理设计模式-----");
        /**
         * 核心是有真实业务实现类与代理业务实现类,并且代理类要完成比真实业务更多的处理操作。
         * 传统代理模式的弊端:需要首先定义出核心接口的组成。
         */
        IMessage message = new MessageProxy(new MessageReal());
        message.send();
        /**
         * 客户端的接口与具体的子类产生了耦合问题,最好再引入工厂设计模式进行代理对象的获取。
         * 静态代理设计的特点在于:一个代理类只为一个接口服务
         */
//动态代理设计模式
        System.out.println("-----动态代理设计模式-----");
        /**
         * 不管是动态代理还是静态代理都一定要接收真实业务实现子类对象;
         * 由于动态代理类不再与某一个具体接口进行捆绑,所以应该可以动态获取类的接口信息。
         */
        message = (IMessage) new ProxyHandler().bind(new MessageReal());
        message.send();
        /**
         * 观察系统中提供的Proxy.newProxyInstance()方法会方法该方法会使用大量的底层机制来进行代理对象的动态创建,
         * 所有的代理类是符合所有向相关功能需求的操作功能类,它不再代表具体的接口,这样在处理的时候就必须依赖于类加载器于接口进行代理对象的伪造。
         */

//CGLIB实现代理设计模式
        System.out.println("-----CGLIB实现代理设计模式-----");
        /**
         * 有一部分开发者就认为不应该强迫性的基于接口来实现代理设计,
         * 开发者设计了CGLIB开发包,可以实现基于类的代理设计模式。
         * 1.CGLIB是一个第三方的程序包,导包
         * 2.编写程序类,该类不实现任何接口。
         * 3.利用CGLIB编写代理类,但是这个代理类需要做一个明确,此时相当于使用类的形式实现了代理设计的处理,
         * 		所以该代理设计需要通过CGLIB来生成代理对象。
         * 4.此时如果要想创建代理类对象,则就必须进行一系列的CGLIB处理。
         */
        Message realObject = new Message();//真实主题对象
        Enhancer enhancer = new Enhancer();//负责代理操作的程序类
        enhancer.setSuperclass(realObject.getClass());//假定一个父类
        enhancer.setCallback(new ZQHProxy2(realObject));//设置代理类
        Message proxyObject = (Message) enhancer.create();//创建代理对象
        proxyObject.send();
        //但从正常的设计角度来讲,强烈建议还是基于接口的设计会比较合理。
    }
}

//  👇👇👇CGLIB👇👇👇
class Message {
    public void send() {
        System.out.println("消息发送");
    }

}
class ZQHProxy2 implements MethodInterceptor {//拦截器配置
    private Object target;//保存真实主题对象
    public ZQHProxy2(Object target){
        this.target = target;
    }
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        Object returnData = null;
        if(this.connect()){
            returnData = method.invoke(this.target, args);
            this.close();
        }
        return returnData;
    }
    public boolean connect(){
        System.out.println("CGLIB消息代理——连接");
        return true;
    }
    public void close(){
        System.out.println("CGLIB消息代理——关闭");
    }
}

3.3迭代器模式
在不暴露数据类型的情况下访问数据的集合,主要应用于数据结构中多种数据类型、列表、树等,提供通用操作接口,用for of语句进行所有迭代

const number = [1,2,3];
const map = new Map();
map.set("K1","V1");
map.set("K2","V2");

const set = new Set(["1","2","3"]);
for(const number of numbers){
//...
}
for(const [key,value] of map){
//...
}
for(const key of set){
//...
}

四、在设计模式中还有十大原则

1.单一职责原则 SRP

实现类要职责单一:如果一段代码块(函数 类 模块)负责多个功能,那么当 A 功能需求发生改变的时候改动了代码,就有可能导致 B 功能出现问题,所以一段代码块只应该负责一个职责。

2.开放-封闭原则 OCP

要对扩展开放,对修改关闭:通过修改老代码来实现新功能可能导致老模块出现 BUG,所以我们应该通过开发新代码块来实现新功能。

3.里氏替换原则 LSP

不要破坏继承体系:程序中的子类应该可以替换父类出现的任何地方并保持预期不变。所以子类尽量不要改变父类方法的预期行为。

4.接口隔离原则 ISP

设计接口的时候要精简单一:当类 A 只需要接口 B 中的部分方法时,因为实现接口需要实现其所有的方法,于是就造成了类 A 多出了部分不需要的代码。这时应该将 B 接口拆分,将类A需要和不需要的方法隔离开来。

5.依赖倒置原则 DIP

面向接口编程:抽象不应该依赖细节,细节应该依赖于抽象。核心是面向接口编程,我们应该依赖于抽象接口,而不是具体的接口实现类或具体的对象。

6.最少知识原则(迪米特原则)LOD

降低耦合度:一个类或对象应该对其它对象保持最少的了解。只与直接的朋友(耦合)通信。

7.组合/聚合复用原则 CRP

多用组合少用继承:尽可能通过组合已有对象(借用他们的能力)来实现新的功能,而不是使用继承来获取这些能力。

8.不要重复你自己 DRY

功能语义重复应该合并,代码执行重复应该删减,代码逻辑重复但语义不同应该保留。

9.尽量保持简单 KISS

尽可能使用简单可读性高的代码实现功能,而不用逻辑复杂、实现难度高、可读性差的方式。

10.不要过度设计你暂时用不到的逻辑 YAGNI

不要过度优化、不要过度预留扩展点、不要设计同事看不懂的代码。\

五、总结

今天跟着老师了解了设计模式的相关知识,因为自己之前从来没有上过设计模式这门课,虽然有些有点不懂,但是今天老师讲的有关浏览器和JavaScript中的相关设计模式是清楚,然后今天刚好还在掘金开发者社区青训营精品学习资源合集中看到了关于设计模式的一篇文章,更加帮助我去了解了设计模式,在写这篇文章的时候,自己也百度了对应模式的相关代码去加深理解。

设计模式10大原则参考于:作者:一袋米要扛几楼
链接:juejin.cn/post/695342… 来源:稀土掘金
因为自己能力有限,所以文中部分代码来源于CSDN