你理解的设计模式? 能用一句话来概括设计模式的原则?
- 设计模式含义:一套被反复使用、多数人知晓、经过分类编目的、使用设计经验的总结。是软件开发人员在软件开发过程中面临的一般问题的解决方案
- 使用设计模式目的:是为了可重复用代码、让代码更容易理解并且保证代码可靠性。
- 原则:
- 开闭原则:对拓展开发,对修改关闭
- 里氏转换原则:子类继承父类,单独调用可以完全运行。
- 依赖倒置原则:抽象不应该依赖于具体类,具体类应当依赖于抽象。针对接口编程
- 单一职责原则:一个类只负责有相同职责的功能
- 迪米特法则:一个软件实体应当尽可能少地与其他实体发生相互作用
- 接口分离原则:使用多个专门地接口,而不使用单一总接口。即客户端不应该依赖那些不属于他的接口
单例模式特性?应用场景?实现你描述的应用场景?
- 单例模式含义:是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
- 单列模式特性:
- 单例类只能有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
- 主要思路:
- 一个全局使用的类频繁地创建与销毁
- 判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
- 构造函数是私有的。
- 缺点:
- 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
- 使用场景:
- vue 项目中的 全局消息提示,减少频繁的创建和销毁消息组件
- 要求生产唯一序列号。
- WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
- 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
//第一个场景的列子 import Notice from "@/components/Notice.vue"; import Vue from "vue"; // 给Notice添加一个创建组件实例的方法,可以动态编译自身模板并挂载 Notice.getInstance = props => { // 创建一个Vue实例 const instance = new Vue({ render(h) { // 渲染函数:用于渲染指定模板为虚拟dom // <Notice foo="bar"> return h(Notice, { props }); } }).$mount(); // 执行挂载,若不指定选择器,则模板将被渲染为文档之外的元素 // 必须使用原生dom api把它插入文档中 // $el指的是渲染的Notice中真实dom元素 document.body.appendChild(instance.$el); // 获取notice实例,$children指的是当前Vue实例中包含的所有组件实例 const notice = instance.$children[0]; return notice; }; // 设计单例模式,全局范围唯一创建一个Notice实例 let msgInstance = null; function getInstance() { msgInstance = msgInstance || Notice.getInstance(); return msgInstance; } // 暴露接口 export default { info({ duration = 2, content = "" }) { getInstance().add({ content, duration }); } };
策略模式特性?应用场景?实现你描述的应用场景?
- 策略模式含义:一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。 在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。
- 策略模式特性:
- 根据不同参数可以命中不同的策略。
- 算法可以自由切换。
- 避免使用多重条件判断。
- 扩展性良好。
- 主要思路:
- 在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。
- 一个系统有许多许多类,而区分它们的只是他们直接的行为。
- 将这些算法封装成一个一个的类,任意地替换。
- 实现同一个接口。
- 缺点:
- 策略类会增多。
- 所有策略类都需要对外暴露。
- 使用场景:
- 根据不同的薪资水平和工资,获取年终奖
- 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
- 一个系统需要动态地在几种算法中选择一种。
- 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
//第一个场景的列子 const strategy = { 'S': function(salary) { return salary * 4 }, 'A': function(salary) { return salary * 3 }, 'B': function(salary) { return salary * 2 } } const calculateBonus = function(level, salary) { return strategy[level](salary) } calculateBonus('A', 10000) // 30000
代理模式特性?应用场景?实现你描述的应用场景?
- 含义:一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。
- 特性:代理对象和本体对象具有一致的接口
- 主要思路:
- 在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
- 想在访问一个类时做一些控制。
- 增加中间层。
- 实现与被代理类组合。
- 缺点:
- 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
- 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
- 生活列子:猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。买火车票不一定在火车站买,也可以去代售点。
- 使用场景:
- 虚拟代理实现图片预加载。
const myImage = (function() { const imgNode = document.createElement('img') document.body.appendChild(imgNode) return { setSrc: function(src) { imgNode.src = src } } })() const proxyImage = (function() { const img = new Image() img.onload = function() { // http 图片加载完毕后才会执行 myImage.setSrc(this.src) } return { setSrc: function(src) { myImage.setSrc('loading.jpg') // 本地 loading 图片 img.src = src } } })() proxyImage.setSrc('http://loaded.jpg')
发布订阅模式特性?应用场景?实现你描述的应用场景?
- 含义:发布订阅模式又叫观察者模式,它定义了对象间的一种一对多的关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知。观察者模式属于行为型模式
- 特性:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
- 主要思路:
- 确认发布者
- 然后给发布者添加一个缓存列表,用于存放回调函数来通知订阅者
- 最后就是发布消息,发布者遍历这个缓存列表,依次触发里面存放的订阅者回调函数
- 缺点:
- 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
- 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
- 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
- 生活列子:小红最近在淘宝网上看上一双鞋子,但是呢 联系到卖家后,才发现这双鞋卖光了,但是小红对这双鞋又非常喜欢,所以呢联系卖家,问卖家什么时候有货,卖家告诉她,要等一个星期后才有货,卖家告诉小红,要是你喜欢的话,你可以收藏我们的店铺,等有货的时候再通知你,所以小红收藏了此店铺,但与此同时,小明,小花等也喜欢这双鞋,也收藏了该店铺;等来货的时候就依次会通知他们
- 使用场景:
- 一个对象必须通知其他对象,而并不知道这些对象是谁。
- 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
- 需要在系统中创建一个触发链,A 对象的行为将影响 B 对象,B 对象的行为将影响 C 对象……,可以使用观察者模式创建一种链式触发机制。
var Event = function() { this.obj = {} }//发布者 Event.prototype.on = function(eventType, fn) { if (!this.obj[eventType]) { this.obj[eventType] = [] } this.obj[eventType].push(fn) }//用以设置添加缓存表 Event.prototype.emit = function() { var eventType = Array.prototype.shift.call(arguments) var arr = this.obj[eventType] for (let i = 0; i < arr.length; i++) { arr[i].apply(arr[i], arguments) } }//发布缓存表中的消息 var ev = new Event() ev.on('click', function(a) { // 订阅函数 console.log(a) // 1 }) ev.emit('click', 1) // 发布函数
命令模式特性?应用场景?实现你描述的应用场景?
- 含义:是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。
- 特性:将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化
- 主要思路:
- 通过调用者调用接受者执行命令,顺序:调用者→接受者→命令。
- received 真正的命令执行对象
- Command
- invoker 使用命令对象的入口
- 缺点:使用命令模式可能会导致某些系统有过多的具体命令类。
- 生活列子:我们经常会在天猫上购买东西,然后下订单,下单后我就想收到货,并且希望货物是真的,对于用户来讲它并不关心下单后卖家怎么发货,当然卖家发货也有时间的,比如24小时内发货等,用户更不关心快递是给谁派送,当然有的人会关心是什么快递送货的; 对于用户来说,只要在规定的时间内发货,且一般能在相当的时间内收到货就可以,当然命令模式也有撤销命令和重做命令,比如我们下单后,我突然不想买了,我在发货之前可以取消订单,也可以重新下单(也就是重做命令)
- 使用场景:
- 对HTML中的按钮和事件进行了抽离, 因此可以复杂项目中可以使用命令模式将界面的代码和功能的代码交付给不同的人去写。
- GUI 中每一个按钮都是一条命令。
- 模拟 CMD。
const setCommand = function(button, command) { button.onClick = function() { command.excute() } } // -------------------- 上面的界面逻辑由A完成, 下面的由B完成 const menu = { updateMenu: function() { console.log('更新菜单') }, } const UpdateCommand = function(receive) { return { excute: receive.updateMenu, } } const updateCommand = UpdateCommand(menu) // 创建命令 const button1 = document.getElementById('button1') setCommand(button1, updateCommand)
组合模式模式特性?应用场景?实现你描述的应用场景?
- 含义:又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。 这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式
- 特性:
- 组合模式在对象间形成树形结构;
- 组合模式中基本对象和组合对象被一致对待;
- 无须关心对象有多少层, 调用时只需在根部进行调用;
- 主要思路:树枝内部组合该接口,并且含有内部属性 List,里面放 Component。
- 缺点:在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。
- 生活列子:扫描文件夹时, 文件夹下面可以为另一个文件夹也可以为文件, 我们希望统一对待这些文件夹和文件, 这种情形适合使用组合模式。
- 使用场景:
- 扫描文件夹:
const Folder = function(folder) { this.folder = folder this.lists = [] } Folder.prototype.add = function(resource) { this.lists.push(resource) } Folder.prototype.scan = function() { console.log('开始扫描文件夹: ', this.folder) for (let i = 0, folder; folder = this.lists[i++];) { folder.scan() } } const File = function(file) { this.file = file } File.prototype.add = function() { throw Error('文件下不能添加其它文件夹或文件') } File.prototype.scan = function() { console.log('开始扫描文件: ', this.file) } const folder = new Folder('根文件夹') const folder1 = new Folder('JS') const folder2 = new Folder('life') const file1 = new File('深入React技术栈.pdf') const file2 = new File('JavaScript权威指南.pdf') const file3 = new File('小王子.pdf') folder1.add(file1) folder1.add(file2) folder2.add(file3) folder.add(folder1) folder.add(folder2) folder.scan() // 开始扫描文件夹: 根文件夹 // 开始扫描文件夹: JS // 开始扫描文件: 深入React技术栈.pdf // 开始扫描文件: JavaScript权威指南.pdf // 开始扫描文件夹: life // 开始扫描文件: 小王子.pdf
装饰者模式特性?应用场景?实现你描述的应用场景?
- 含义:允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
- 特性:动态地给函数赋能
- 主要思路:
- Component 类充当抽象角色,不应该具体实现。
- 修饰类引用和继承 Component 类,具体扩展类重写父类方法。
- 缺点:
- 临时变量会变得越来越多;
- this 指向有时会出错
- 生活列子:
- 孙悟空有 72 变,当他变成 "庙宇" 后,他的根本还是一只猴子,但是他又有了庙宇的功能。
- 天气冷了, 就添加衣服来保暖;天气热了, 就将外套脱下;这个例子很形象地含盖了装饰器的神韵, 随着天气的冷暖变化, 衣服可以动态的穿上脱下
- 使用场景:
let wear = function() { console.log('穿上第一件衣服') } const _wear1 = wear wear = function() { _wear1() console.log('穿上第二件衣服') } const _wear2 = wear wear = function() { _wear2() console.log('穿上第三件衣服') } wear() // 穿上第一件衣服 // 穿上第二件衣服 // 穿上第三件衣服