面向对象设计原则
面向对象设计的六个设计原则:
中文名称 | 定义 |
---|---|
开闭原则 | 一个软件实体如类、模块和函数面向扩展开放,面向修改关闭 |
单一职责原则 | 一个类只允许有一个职责,即只有一个导致该类变更的原因 |
里氏替换原则 | 超类存在的地方,子类是可以替换的 |
迪米特法则(最少知道原则) | 一个软件实体应当尽可能少的与其他实体发生相互作用 |
接口分离原则 | 应当为客户端提供尽可能小的单独的接口,而不是提供大的总的接口 |
依赖倒置原则 | 实现尽量依赖抽象,不依赖具体实现 |
单例模式
定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。 实现的方法为先判断实例存在与否,如果存在则直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。
适用场景:一个单一对象。
- 引用第三方库(多次引用只会使用一个库引用,如 jQuery)
- 弹窗(登录框,信息提升框)
- 购物车 (一个用户只有一个购物车)
- 全局态管理 store (Vuex / Redux)
优点:适用于单一对象,只生成一个对象实例,避免频繁创建和销毁实例,减少内存占用。
缺点:不适用动态扩展对象,或需创建多个相似对象的场景。
最简版示例:
let Singleton = function(name){
this.name = name
this.instance = null
}
Singleton.prototype.getName = function() {
console.log(this.name)
}
Singleton.getInstance = function(name) {
if(this.instance){
return this.instance
}
return this.instance = new Singleton(name)
}
let a = Singleton.getInstance('a');
let b = Singleton.getInstance('b');
console.log(a === b); // true
console.log(a.getName()); // 'a'
console.log(b.getName()); // 'a'
上述单例无法使用new
进行实例化,且管理单例的操作与功能代码耦合在一起,不符合"单一职责原则",因此我们做如下改造。
代理版示例:
let Singleton = function(name){
this.name = name
}
Singleton.prototype.getName = function() {
console.log(this.name)
}
let ProxySingletonCreator = (function() {
let instance;
return function(name) {
if(instance){
return instance;
}
return instance = new Singleton(name)
}
})()
let a = new ProxySingletonCreator('a');
let b = new ProxySingletonCreator('b');
console.log(a === b); // true
console.log(a.getName()); // 'a'
console.log(b.getName()); // 'a'
代理模式
定义:为一个对象提供一个代用品或占位符,以便控制对它的访问。Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
适用场景:
- 懒加载:比如图片懒加载机制,先通过一张loading图占位,然后通过异步的方式加载图片,等图片加载好了再把完成的图片加载到img标签里面
- 缓存代理:可以将一些开销很大的方法的运算结果进行缓存,再次调用该函数时,若参数一致,则可以直接返回缓存中的结果,而不用再重新进行运算。
- 验证代理:比如实现表单验证器 另外,还有保护代理,比如当需要控制对一个对象的访问,为不同用户提供不同级别的访问权限时可以使用。
ES6所提供Proxy构造函数能够让我们轻松的使用代理模式:
var proxy = new Proxy(target, handler);
Proxy构造函数传入两个参数,第一个参数target表示所要代理的对象,第二个参数handler也是一个对象用来设置对所代理的对象的行为。 handler支持的拦截操作一共有13种。
- get(target, propKey, receiver) //拦截对象属性的读取,比如proxy.foo和proxy['foo']
- set(target, propKey, value, receiver) //拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值
- apply(target, object, args) //拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)
- construct(target, args) //拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)
- has(target, propKey)
- ownKeys(target)
- getPrototypeOf(target)
- setPrototypeOf(target, proto)
- isExtensible(target)
- preventExtensions(target)
- getOwnPropertyDescriptor(target, propKey)
- defineProperty(target, propKey, propDesc)
- deleteProperty(target, propKey)
示例:
const handler = {
get: function(obj, prop) {
return prop in obj ? obj[prop] : 37;
}
};
const p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;
console.log(p.a, p.b); // 1, undefined
console.log('c' in p, p.c); // false, 37
懒加载示例:
const getLayLoadProxy = (obj) => {
return new Proxy(obj, {
set: function(target, prop, value) {
const defaultImage = 'https://img-blog.csdn.net/20150603165425396' //或者本地图片loading.gif
if (!value) {
return Reflect.set(target, prop, value)
}
target[prop] = defaultImage
const img = new Image()
img.src = value
img.onload = function() {
target[prop] = value
}
},
})
}
let img = document.createElement('img')
document.body.appendChild(img)
let imgPrxoy = getLayLoadProxy(img)
imgPrxoy.src = 'https://i.ibb.co/SK5TGGN/1280-800.jpg'
缓存代理示例:
//原函数
const getFib = (number) => {
if (number <= 2) {
return 1;
} else {
return getFib(number - 1) + getFib(number - 2);
}
}
const getCacheProxy = (func, caches = new Map()) =>{
return new Proxy(func,{
apply: function(target, object, args) {
const args_ = args.join('-')
if(caches.has(args_)){
return caches.get(args_)
}
const result = func(...args)
caches.set(args_, result)
return result
}
})
}
//代理函数
const getFibProxy = getCacheProxy(getFib);
console.log(getFibProxy(10)) // 50
console.log(getFibProxy(10)) // 50 from cache
验证代理示例:
const userForm = {
password: '',
}
const validators = {
password: (value) => {
return {
valid: value.length >= 6,
error: '密码长度不能少于6位',
}
}
}
const getValidProxy = (obj, validators) => {
return new Proxy(obj, {
set: function(target, prop, value) {
if (!value) return target[prop] = false
const result = validators[prop](value)
if (result.valid) {
return Reflect.set(target, prop, value)
} else {
console.error(result.error)
return target[prop] = false
}
},
})
}
const userFormProxy = getValidProxy(userForm, validators)
userFormProxy.password = '12345' // 控制台输出:密码长度不能少于6位
策略模式
定义:定义一系列的算法,把他们一个个封装起来,并且使他们可以相互替换。策略模式的目的就是将算法的使用与算法的实现分离开来。
适用场景:某个“类”中包含有大量的条件性语句,比如if...else 或者 switch。每一个条件分支都会引起该“类”的特定行为以不同的方式作出改变。 与其维护一段庞大的条件性语句,不如将每一个行为划分为多个独立的对象。每一个对象被称为一个策略。
示例(未使用策略模式前):
var calculateBonus = function( performanceLevel, salary ){
if ( performanceLevel === 'S' ){
return salary * 4;
}
if ( performanceLevel === 'A' ){
return salary * 3;
}
if ( performanceLevel === 'B' ){
return salary * 2;
}
};
calculateBonus( 'B', 20000 ); // 输出:40000
calculateBonus( 'S', 6000 ); // 输出:24000
示例(使用策略模式后):
var strategies = {
"S": function( salary ){
return salary * 4;
},
"A": function( salary ){
return salary * 3;
},
"B": function( salary ){
return salary * 2;
}
};
var calculateBonus = function( level, salary ){
return strategies[ level ]( salary );
};
console.log( calculateBonus( 'S', 20000 ) ); // 输出:80000
console.log( calculateBonus( 'A', 10000 ) ); // 输出:30000
装饰器模式
定义:装饰者模式的定义:在不改变对象自身的基础上,在程序运行期间给对象动态地添加方法。
适用场景:日志记录、性能统计、安全控制、事务处理、异常处理等等。
面向切面编程(Aspect-oriented programming,AOP)是一种编程范式。做后端 Java web 的同学,特别是用过 Spring 的同学肯定对它非常熟悉。AOP 是 Spring 框架里面其中一个重要概念。 在ES6之前,要使用装饰器模式,通常通过高阶函数:Function.prototype.before做前置装饰,和Function.prototype.after做后置装饰。
示例:
Function.prototype.before = function(beforeFn) {
var self = this
return function() {
beforeFn.apply(this,arguments)
return self.apply(this,arguments)
}
}
Function.prototype.after = function(afterFn) {
var self = this
return function() {
var result = self.apply(this,arguments)
afterFn.apply(this,arguments)
return result
}
}
var func = function() {
console.log('func');
}
var func1 = function() {
console.log('beforeFunc');
}
var func3 = function() {
console.log('afterFunc');
}
func = func.before(func1).after(func3)
func() // beforeFunc func afterFunc
ES6中,装饰器(Decorator)是一种与类(class)相关的语法,用来注释或修改类和类方法。装饰器是一种函数,写成@ + 函数名
。它可以放在类和类方法的定义前面。
但不能用于函数,因为存在函数提升。如果一定要装饰函数,可以上面介绍的采用高阶函数的形式直接执行。
@frozen
class Foo {
@configurable(false)
@enumerable(true)
method() {}
@throttle(500)
expensiveMethod() {}
}
上面代码一共使用了四个装饰器,一个用在类本身,另外三个用在类方法。它们不仅增加了代码的可读性,清晰地表达了意图,而且提供一种方便的手段,增加或修改类的功能。
示例:
class Math {
@log
add(a, b) {
return a + b;
}
}
function log(target, name, descriptor) {
var oldValue = descriptor.value;
descriptor.value = function() {
console.log(`Calling ${name} with`, arguments);
return oldValue.apply(this, arguments);
};
return descriptor;
}
const math = new Math();
// passed parameters should get logged now
math.add(2, 4);
上面代码中,@log 装饰器的作用就是在执行原始的操作之前,执行一次console.log,从而达到输出日志的目的。