JS 设计模式 - 怎么让你的代码提示一个档次

news2024/11/17 11:42:44

设计模式是我们在解决一些问题的时候 ,针对特定的问题给出的简介并且优化的处理方案

这篇文章说提及到的 JavaScript 设计模式将围绕着封装方法类来展开叙述

构造器模式

构造器模式本质就是我们平常在编码中常用的封装方法,重复利用构造函数

// 这是一个简单的构造器,我们创建实例的时候需要传递给他俩个参数,这俩个参数就是它实例的属性
// 这个构造器中还有一个方法,也就是它的实例所拥有的功能
function Constructors(user_name, user_age) {
	this.user_name = user_name
	this.user_age = user_age

	this.clickFunc = () => {
		console.log(`${this.user_name}---${this.user_age}`)
	}
}

// 我们创建了 Constructors 的实例
const Constructor_01 = new Constructors("brave-airPig", 22)
const Constructor_02 = new Constructors("airPig", 21)
// 我们使用通过 Constructors 构造器产生的实例的方法
Constructor_01.clickFunc()
Constructor_02.clickFunc()

// brave-airPig---22
// airPig---21

看似没有问题的代码,如果我们仔细观察会发现 clickFunc 方法在每次创建实例的时候创建这样一个相同的方法,属实是没有必要的 😑,我们有没有一种方法可以只创建一次该方法,便可以让所有实例共享呢?


原型模式

这次我们在构造器函数的原型上挂载该方法,这样就不需要每次创建实例都创建一个方法去浪费内存了,这里提到了一个关键字 prototype ,这是 JavaScript 语言中对象的原型,我们直接挂载到其原型上,这样就可以完美的解决这个问题了

function Constructors(user_name, user_age) {
	this.user_name = user_name
	this.user_age = user_age
}

Constructors.prototype.clickFunc = function () {
	console.log(`${this.user_name}--${this.user_age}`)
}

const Constructor_01 = new Constructors("brave-airPig", 22)
Constructor_01.clickFunc()

// brave-airPig--22

ES6类的写法:
使用类的书写好像更好看,逻辑更清楚一些,类中的方法本身就是在其原型上的,所以也不需要进行单独挂载了

class Constructors {
	constructor(user_name, user_age) {
		this.user_name = user_name
		this.user_age = user_age
	}

	clickFunc() {
		console.log(`${this.user_name}---${this.user_age}`)
	}
}

const Constructor_01 = new Constructors("brave-airPig", 22)
const Constructor_02 = new Constructors("airPig", 21)
Constructor_01.clickFunc()
Constructor_02.clickFunc()

在构造器与原型俩种模式的选择中,我们的代码不知不觉就被优化了,性能也相应提升了不少


工厂模式

工厂模式是由一个工厂对象决定创建某一种产品对象类的实例,主要用来创建同一类对象,也就是工厂的流水线,生产出来的都是一摸一样的物品

我们只需要一个正确的参数即可获取到所需对象,我们不需要知道创建的具体细节,但是在函数内部包含了所有的创建逻辑以及代码,每增加新的构造函数还需要修改判断逻辑代码,当我们对象很多的时候,这个工厂将会很庞大,不好维护,所以简单的工厂模式只能作用于创建的对象数量少,对象的创建逻辑不复杂的时候使用

比如下面这个例子,假如我们的系统有很多种客户角色,每一种客户所能使用的功能和看到的内容是不一样的,就像用户只能在系统中执行 func1 这个功能,而超级管理员则可以在该系统中使用 4 个功能或者说是权限,具体实现的话我们不用担心,我们可以将这个功能数组进行循环创建 Tabbar ,这样的话,没有权限压根就看不到该 Tabbar,也就无法点击以及使用其中的功能了

function UserFactory(role) {
	function User(role, pages) {
		this.role = role
		this.pages = pages
	}

	switch (role) {
		case "superadmin":
			return new User("超级管理员", ["fun1", "fun2", "fun3", "fun4"])
		case "admin":
			return new User("管理员", ["fun1", "fun2", "fun3"])
		case "loweradmin":
			return new User("低级管理员", ["fun1", "fun2"])
		case "users":
			return new User("用户", ["fun1"])
		default:
			throw new Error("参数错误!")
	}
}

console.log(UserFactory("users"))
console.log(UserFactory("superadmin"))

// User { role: '用户', pages: [ 'fun1' ] }
// User { role: '超级管理员', pages: [ 'fun1', 'fun2', 'fun3', 'fun4' ] }

这样我们就可以根据对应的身份去做对应的权限分配了 🦄

ES6写法:

class User {
	constructor(role, pages) {
		this.role = role
		this.pages = pages
	}
	static UserFactory(role) {
		switch (role) {
			case "superadmin":
				return new User("超级管理员", ["fun1", "fun2", "fun3", "fun4"])
			case "admin":
				return new User("管理员", ["fun1", "fun2", "fun3"])
			case "loweradmin":
				return new User("低级管理员", ["fun1", "fun2"])
			case "users":
				return new User("用户", ["fun1"])
			default:
				throw new Error("参数错误!")
		}
	}
}

console.log(User.UserFactory("users"))
console.log(User.UserFactory("superadmin"))

// User { role: '用户', pages: [ 'fun1' ] }
// User { role: '超级管理员', pages: [ 'fun1', 'fun2', 'fun3', 'fun4' ] }

抽象工厂模式

这也是针对工厂模式无法应对多对象多复杂对象而出的一种模式,抽象工厂模式并不是直接生成实例,而是对于产品的一个分类的创建

// 抽象父类
class User {
	constructor(name, role, pages) {
		this.name = name
		this.role = role
		this.pages = pages
	}

	//登录功能
	login() {
		console.log(`尊敬的${this.name},欢迎回来`)
	}

	// 指定用户权限对应显示的数据
	dataShow() {
		throw new Error("请实现抽象方法")
	}
}

// 超级管理员子类继承 User 父类
class SupeRadmin extends User {
	constructor(name) {
		super(name, "superadmin", ["fun1", "fun2", "fun3", "fun4"])
	}
	dataShow() {
		console.log("超级管理员可视数据")
	}
	addTest() {
		console.log("超级管理员独有的方法")
	}
}

// 管理员子类继承 User 父类
class Admin extends User {
	constructor(name) {
		super(name, "admin", ["fun1", "fun2", "fun3"])
	}
	dataShow() {
		console.log("管理员可视数据")
	}
}

// 低级管理员子类继承 User 父类
class LowerRadmin extends User {
	constructor(name) {
		super(name, "loweradmin", ["fun1", "fun2"])
	}
	dataShow() {
		console.log("低级管理员可视数据")
	}
}

// 普通用户子类继承 User 父类
class Users extends User {
	constructor(name) {
		super(name, "users", ["fun1"])
	}
	dataShow() {
		console.log("用户可视数据")
	}
	usersTest() {
		console.log("用户特有方法")
	}
}

// 构造方法,通过这个鉴权方法来判定我们应该实例化那个子类或者说实例,然后展现对应权限的数据以及实现相应的功能
function getAbstractUserFactory(role) {
	switch (role) {
		case "superadmin":
			return new SupeRadmin("超级管理员")
		case "admin":
			return new Admin("管理员")
		case "loweradmin":
			return new LowerRadmin("低级管理员")
		case "users":
			return new Users("用户")
		default:
			throw new Error("参数错误!")
	}
}

console.log(getAbstractUserFactory("superadmin"))
getAbstractUserFactory("users").login()
getAbstractUserFactory("loweradmin").login()
getAbstractUserFactory("superadmin").addTest()

// SupeRadmin {
//   name: '超级管理员',
//   role: 'superadmin',
//   pages: [ 'fun1', 'fun2', 'fun3', 'fun4' ]
// }
// 尊敬的用户,欢迎回来
// 尊敬的低级管理员,欢迎回来
// 超级管理员独有的方法

建造者模式

建造者模式是属于创建型模式的一种,提供了一种创建复杂对象的方式,它将一个复杂的对象的构建与它的表示分离开来,使得同样的构建过程可以创建不同的表示

构建者模式是通过一步一步的创建一个复杂对象,允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户无需指定内部的具体构造细节

// 模拟navbar选项卡的选项与数据从初始化到渲染到视图的简单生命周期
class Navbar {
	// 初始化
	init() {
		console.log("初始化")
	}
	getData() {
		// 模拟ajax异步请求数据
		return new Promise((resolve) => {
			setTimeout(() => {
				resolve()
				console.log("请求数据成功")
			}, 1500)
		})
	}
	// 将数据渲染到视图
	render() {
		console.log("渲染")
	}
}

class List {
	// 初始化
	init() {
		console.log("初始化")
	}
	getData() {
		// 模拟ajax异步请求数据
		return new Promise((resolve) => {
			setTimeout(() => {
				resolve()
				console.log("请求数据成功")
			}, 1500)
		})
	}
	// 将数据渲染到视图
	render() {
		console.log("渲染")
	}
}

const navbar = new Navbar()
const list = new List()

// 建造者
class Operator {
	async startBuild(build) {
		await build.init()
		await build.getData()
		await build.render()
	}
}

const op = new Operator()
op.startBuild(navbar)
op.startBuild(list)

// 初始化
// 初始化
// 请求数据成功
// 渲染
// 请求数据成功
// 渲染

单例模式

单例模式保证一个类仅有一个实例,并且提供一个访问它的全局访问点,解决全局使用的类频繁的创建和销毁,占用内存

ES5构建方法(闭包):

function User(user_name, user_age) {
	this.user_name = user_name
	this.user_age = user_age
}

let singleton = (function () {
	// 使用闭包,让 GC 不会回收该变量 那么该变量也就理所应当的成为了全局变量
	let instance = null

	return function (user_name, user_age) {
		if (!instance) instance = new User(user_name, user_age)
		return instance
	}
})()

console.log(singleton("airpig", 22))
console.log(singleton("pig", 21))

// User { user_name: 'airpig', user_age: 22 }
// User { user_name: 'airpig', user_age: 22 }

ES6 实现:

class Singleton {
	constructor(user_name, user_age) {
		if (!Singleton.instance) {
			this.user_name = user_name
			this.user_age = user_age
			Singleton.instance = this
		}
		return Singleton.instance
	}
}

console.log(new Singleton("airpig", 22))
console.log(new Singleton("pig", 21))

// Singleton { user_name: 'airpig', user_age: 22 }
// Singleton { user_name: 'airpig', user_age: 22 }

装饰器模式

装饰器模式能够很好的对已有功能进行拓展,也就是 ”锦上添花“ 的意思

它不会更改原有代码、对其他的业务产生印象、方便我们在较少的改动之下对软件功能进行拓展

Function.prototype.before = function (beforeFunc) {
	let _this = this
	return function () {
		beforeFunc.apply(this, arguments)
		return _this.apply(this, arguments)
	}
}

Function.prototype.after = function (afterFunc) {
	let _this = this
	return function () {
		const fn = _this.apply(this, arguments)
		afterFunc.apply(this, arguments)
		return fn
	}
}

const testFunc = () => {
	console.log("这是一个测试方法")
	return "测试方法"
}

const addTest = testFunc
	.before(() => {
		console.log("测试方法之前执行")
	})
	.after(() => {
		console.log("测试方法之后执行")
	})

console.log(testFunc())
console.log("--------------")
console.log(addTest())

// 这是一个测试方法
// 测试方法
// --------------
// 测试方法之前执行
// 这是一个测试方法
// 测试方法之后执行
// 测试方法

适配器模式

适配器不会去改变实现层、实现层不属于它的职责范围,它干涉了抽象过程,外部接口的适配能够让同一个方法适用于多种系统,也就是说它会将一个类的接口转换成客户希望的另一个接口,适配器模式让那些接口不兼容的类可以一起工作

// 腾讯地图
class TencentMap {
	TencentMapShow() {
		console.log("显示腾讯地图")
	}
}

class BaiduMap {
	BaiduMapShow() {
		console.log("显示百度地图")
	}
}

// 适配器

class TencentMapAdapter extends TencentMap {
	constructor() {
		super()
	}

	// 渲染
	render() {
		this.TencentMapShow()
	}
}

class BaiduMapAdapter extends BaiduMap {
	constructor() {
		super()
	}

	// 渲染
	render() {
		this.BaiduMapShow()
	}
}

// 使用者

const renderMap = (mapStyle) => mapStyle.render()

// 调用地图

let userP = "IQQO"

if (!userP === "IQQO") renderMap(new TencentMapAdapter())
else renderMap(new BaiduMapAdapter())

策略模式

策略模式定义了一系列算法,并将每一个算法封装起来。使他们可以相互的替换,并且算法的变化不会影响使用算法的客户,策略模式属于对象行为模式,它通过对算法进行封装把使用算法的责任和算法的实现分隔开来,并委派给不同的对象对这些算法进行管理

该模式主要用来解决多种算法相似的情况下的优化,使用 if...else 所带来的复杂和难以维护使用这种模式即可以解决,算法可以自由进行切换,同时可避免多重 if...else 判断,并具有良好的扩展性

let policy = {
	A: (multiple) => multiple * 100,
	B: (multiple) => multiple * 10,
	C: (multiple) => multiple * 2,
}

const numPolicy = (lv, multiple) => policy[lv](multiple)

console.log(numPolicy("C", 200))
console.log(numPolicy("B", 50))
console.log(numPolicy("A", 60))

// 400
// 500
// 6000

代理模式

代理模式是为其他对象提供一种代理以控制对这个对象的访问,使得代理对象控制具体对象的引用,代理几乎可以是任何对象

// 名为 airPig 的员工跳槽记
let user = {
	name: "airPig",
	deposit: 2500,
}

let proxy = new Proxy(user, {
	// 访问对象将执行 get 方法 -- 模拟去新公司面试
	get(target, key) {
		if (key === "deposit")
			console.log("新公司问你之前工资多少 -- 你回答了:" + target[key])
		if (key === "name")
			console.log("新公司问你名字 -- 你回答了:" + target[key])
		return target[key]
	},
	// 修改对象属性 执行 set 方法 -- 修改你的工资
	set(target, key, value) {
		if (key === "deposit") {
			console.log("将要修改你的工资")
			if (value > target["deposit"])
				console.log("修改成功 -- 现在工资为:" + value)
			else throw new Error("跳槽不能把工资往小修改!!!")
		}
	},
})

// proxy.name 新公司问你名字 -- 你回答了:airPig
// proxy.deposit 新公司问你之前工资多少 -- 你回答了:2500
// proxy.deposit = 1500  Error: 不能往小修改

// proxy.deposit = 24000
// 已被修改
// 修改成功 -- 现在工资为:24000


观察者模式

观察者模式包含观察目标和观察者俩类对象,一个目标可以有任意数目的与之相依赖的观察者,一旦观察目标的状态发生了变化,那么所有的观察者也将得到通知

当一个对象的状态发生变化的时候,所有的依赖与它的对象都将得到通知并被自动更新、解决了主题对象与观察者之间功能的耦合,即一个对象状态该改变,通知其他对象

这个模式的有点在于目标者与观察者功能的耦合度降低,可以专注自身功能逻辑,观察者将被动接受更新,在时间上解耦,实时目标者更新状态,不过没有一种模式是完美无缺的,什么场景都合适的,它的缺点是虽然实现了对象之间的依赖关系解耦合,但是却不能对事件通知进行细分管理,如筛选通知、指定主题事件通知等等

class Subject {
	constructor() {
		this.observer = []
	}

	// 添加观察者
	addObserver = (observer) => this.observer.push(observer)

	// 删除观察者
	removeObserver = (observer) =>
		(this.observer = this.observer.filter((item) => item !== observer))

	// 通知观察者更新
	notify = () => this.observer.forEach((item) => item.update())
}

class Observer {
	constructor(name) {
		this.name = name
	}
	update = () => console.log("update -- 观察者更新了", this.name)
}

const subject = new Subject()
const observer1 = new Observer("观察者1号")
const observer2 = new Observer("观察者2号")
const observer3 = new Observer("观察者3号")
const observer4 = new Observer("观察者4号")

subject.addObserver(observer1)
subject.addObserver(observer2)
subject.addObserver(observer3)
subject.addObserver(observer4)

subject.notify()
console.log("-------------")
subject.removeObserver(observer1)
subject.notify()

// update -- 观察者更新了 观察者1号
// update -- 观察者更新了 观察者2号
// update -- 观察者更新了 观察者3号
// update -- 观察者更新了 观察者4号
// -------------
// update -- 观察者更新了 观察者2号
// update -- 观察者更新了 观察者3号
// update -- 观察者更新了 观察者4号


发布订阅者模式

发布订阅者模式与观察者模式很是相似,不过它们却有着本质的区别

观察者和目标者需要相互知晓

发布者和订阅者不需要相互知晓,它们是通过第三方来实现调度的,属于经过解耦合的观察者模式

const SUBSCRIBER = {
	message: {},
	// 发布订阅消息
	publish(type, data) {
		if (!this.message[type]) return
		this.message[type].forEach((item) => item(data))
	},
	// 添加订阅者
	subscribe(type, fn) {
		if (!this.message[type]) this.message[type] = [fn]
		else this.message[type].push(fn)
	},
	// 删除订阅者
	unsubscribe(type, fn) {
		if (!this.message[type]) return
		// 没有指定删除订阅者就删除全部订阅者
		if (!fn) this.message[type] && (this.message[type].length = 0)
		else this.message[type] = this.message[type].filter((item) => item !== fn)
	},
}

let test_01 = (data) => console.log("test_01", data)
let test_02 = (data) => console.log("test_02", data)
let test_03 = (data) => console.log("test_03", data)
let test_04 = (data) => console.log("test_04", data)

SUBSCRIBER.subscribe("A", test_01)
SUBSCRIBER.subscribe("B", test_02)
SUBSCRIBER.subscribe("A", test_03)
SUBSCRIBER.subscribe("C", test_04)

// 发新闻
SUBSCRIBER.publish("A", "专属订阅A的早报")
SUBSCRIBER.publish("B", "这是给B订阅者的晚报")

// 取消订阅
SUBSCRIBER.unsubscribe("C", test_04)

SUBSCRIBER.publish("C", "发给C订阅者的报纸")

// 删除所有订阅
SUBSCRIBER.unsubscribe("A")

SUBSCRIBER.publish("A", "专属订阅A的早报")

// test_01 专属订阅A的早报
// test_03 专属订阅A的早报
// test_02 这是给B订阅者的晚报


模块模式

模块化模式最初被定义为在传统的软件工程中为类提供私有和公共封装的一种方法,能够使一个单独的对象拥有公共或者私有的方法和变量,从而屏蔽来自全局作用域的特殊部分,这可以减少我们的函数名与在页面中其他脚本或者作用域内定义的函数名冲突的可能性,不过在ES6提出块级作用域的时候就解决了这个问题了

并且在 ES6 中的模块化导入导出也是很好的支持了这个模块化模式,或者我们使用 ES6 类的时候也可以使用 # 来声明一个私有变量

那么 ES6 之前我们都是怎么解决的呢?没错,就是闭包,闭包会使函数作用域一直存在,其中的私有变量也不会出现被 CG

var testMoudle = (function () {
	var count = 0
	return {
		increment: function () {
			return ++count
		},
		reset: function () {
			count = 0
		},
		decrement() {
			return --count
		},
	}
})()

console.log(testMoudle.increment())
console.log(testMoudle.decrement())
testMoudle.reset()
console.log(testMoudle.decrement())
console.log(count)

// 1
// 0
// - 1
// count is not defined

ES6 模块化导入导出:

let count = 0

const increase = () => ++count
const decrease = () => --count
const reset = () => {
	count = 0
}

export default {
	increase,
	decrease,
	reset,
}

// 只导出三个方法,变量该脚本私有
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Document</title>
	</head>
	<body>
		<script type="module">
			import MyModule from "./module_export.js"
			console.log(MyModule)
		</script>
	</body>
</html>

在这里插入图片描述

第二种导出导入方法:

let count = 0

const increase = () => ++count
const decrease = () => --count
const reset = () => {
	count = 0
}

export { increase, decrease, reset }

// 只导出三个方法,变量该脚本私有

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Document</title>
	</head>
	<body>
		<script type="module">
			import { increase, decrease, reset } from "./module_export.js"
			console.log(increase(), decrease())
		</script>
	</body>
</html>

桥接模式

桥接模式就是将抽象部分与它的实现部分进行分离,使它们都可以独立的变化,一个类存在俩个或者多个独立变化的维度,并且这俩个维度都需要进行扩展,有助于独立的管理各组成部分,缺点是每使用一个桥接元素都要增加一次函数的调用,性能降低,提高系统复杂程度

// 模态框的动作
class Toast {
	constructor(ele, animation) {
		this.ele = ele
		this.animation = animation
	}
	show() {
		this.animation.show(this.ele)
	}
	hide() {
		this.animation.hide(this.ele)
	}
}

const ANIMATIONS = {
	bounce: {
		show: (ele) => console.log(`${ele}元素弹跳显示`),
		hide: (ele) => console.log(`${ele}元素弹跳隐藏`),
	},
	silde: {
		show: (ele) => console.log(`${ele}元素滑动显示`),
		hide: (ele) => console.log(`${ele}元素滑动隐藏`),
	},
}

let toast = new Toast("#box", ANIMATIONS.silde)

toast.show()
toast.hide()

let toast2 = new Toast(".div", ANIMATIONS.bounce)

toast2.show()
toast2.hide()

// #box元素滑动显示
// #box元素滑动隐藏
// .div元素弹跳显示
// .div元素弹跳隐藏

组合模式

组合模式就是在对象间形成一个树形结构,组合模式中基本对象和组合对象被一致对待,我们不需要去关心对象有多少层,调用的时候只需要在根部进行一个调用,它模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样去处理复杂元素从而使得客户程序与复杂元素的内部结构进行一个解耦

const FOLDER = function (folder) {
	this.folder = folder
	this.list = []
}

FOLDER.prototype.addFunc = function (res) {
	this.list.push(res)
}

FOLDER.prototype.scanFunc = function () {
	console.log(`扫描 ${this.folder} 文件`)
	for (let i = 0; i < this.list.length; i++) {
		this.list[i].scanFunc()
	}
}

const FILE = function (file) {
	this.file = file
}

FILE.prototype.scanFunc = function () {
	console.log(`开始扫描${this.file}文件`)
}

const Folder = new FOLDER("root")

let folder_02 = new FOLDER("src")
let folder_03 = new FOLDER("components")
let folder_04 = new FOLDER("public")
let folder_05 = new FOLDER("images")

let file_01 = new FILE("test_01.js")
let file_02 = new FILE("test_02.js")
let file_03 = new FILE("test_01.jsx")
let file_04 = new FILE("test_01.html")
let file_05 = new FILE("test_01.jpg")

Folder.addFunc(folder_02)
Folder.addFunc(folder_03)
Folder.addFunc(folder_04)
Folder.addFunc(folder_05)

folder_02.addFunc(file_01)
folder_02.addFunc(file_02)
folder_03.addFunc(file_03)
folder_04.addFunc(file_04)
folder_05.addFunc(file_05)

Folder.scanFunc()

// 扫描 root 文件
// 扫描 src 文件
// 开始扫描test_01.js文件
// 开始扫描test_02.js文件
// 扫描 components 文件
// 开始扫描test_01.jsx文件
// 扫描 public 文件
// 开始扫描test_01.html文件
// 扫描 images 文件
// 开始扫描test_01.jpg文件


命令模式

有时我们需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么,需要一种松耦合的方式来设计程序,使得发送者和接受者能够消除彼此之间的耦合

  • 发布者:发出命令,调用命令对象,不知道如何执行与谁执行
  • 接收者:提供对应接口处理请求,不知道谁发起的请求
  • 命令对象:接受命令。调用接收者对应的接口处理发布者的请求
// 接收者
class Receiver {
	execute() {
		console.log("接收者执行请求")
	}
}

// 命令对象
class Command {
	constructor(receiver) {
		this.receiver = receiver
	}
	execute() {
		console.log("命令对象 --> 接收者 --> 对应接口执行")
		this.receiver.execute()
	}
}

// 发布者
class Invoker {
	constructor(command) {
		this.command = command
	}
	invoker() {
		console.log("发布者发布请求")
		this.command.execute()
	}
}

let store_house = new Receiver()
let order = new Command(store_house)
let client = new Invoker(order)

client.invoker()

// 发布者发布请求
// 命令对象 --> 接收者 --> 对应接口执行
// 接收者执行请求


模板方法模式

模板方法模式由俩部分组成,第一部分是抽象父类,第二部分是具体实现子类,通常在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺序,子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类方法,它是一种典型的通过封装变化提高系统扩展性的设计模式,运用了模板方法模式的程度中,子类方法种类和执行顺序都是不变的,但是子类的方法具体实现则是可变的,父类只是一个模板,子类可以添加不同的功能

let Container = function (param) {
	this.render = function (list) {
		console.log("渲染列表", list)
	}
	let getData =
		param.getData ||
		function () {
			throw new Error("必须传递getData方法")
		}
	let func = new Function()
	func.prototype.init = async function () {
		let list = await getData()
		render(list)
	}
	return func
}

let Nowplaying = Container({
	getData: () => [1, 2, 3],
})
let nowplaying = new Nowplaying()
nowplaying.init()

let Commimgsoon = Container({
	getData: () => [4, 5, 6],
})
let commingsoon = new Commimgsoon()

commingsoon.init()

// 渲染列表 [ 1, 2, 3 ]
// 渲染列表 [ 4, 5, 6 ]

迭代器模式

迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示,迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按照顺序访问其中的每个元素

// 统一接口
let MyEach = function (arr, callback) {
	for (let i = 0; i < arr.length; i++) {
		callback(i, arr[i])
	}
}

// 外部调用
MyEach([1, 2, 3, 4, 5, 6, 7, 8, 9], (index, value) => {
	console.log(index, value)
})

// 0 1
// 1 2
// 2 3
// 3 4
// 4 5
// 5 6
// 6 7
// 7 8
// 8 9

ES6中,数组等等支持了迭代器,如果我们想让对象使用迭代器的话:

let obj = {
	0: "test_01",
	1: "test_02",
	2: "test_03",
	3: "test_04",
	4: "test_05",
	5: "test_06",
	6: "test_07",
	7: "test_08",
	8: "test_09",
	length: 9,
	[Symbol.iterator]: Array.prototype[Symbol.iterator],
}

for (let item of obj) {
	console.log(item)
}

// test_01
// test_02
// test_03
// test_04
// test_05
// test_06
// test_07
// test_08
// test_09

上面我们的对象使用的是数组的迭代器,让我们继续手写一个精巧的迭代器:

let obj = {
	list: [1, 2, 3, 4, 5, 6, 7, 8, 9],
	[Symbol.iterator]: function () {
		let index = 0
		return {
			next: () => {
				if (index < this.list.length) {
					return { value: this.list[index++], done: false }
				} else {
					return { value: undefined, done: true }
				}
			},
		}
	},
}
let a = obj[Symbol.iterator]()
console.log(a.next())
console.log(a.next())
console.log(a.next())
console.log(a.next())
console.log(a.next())
console.log(a.next())
console.log(a.next())
console.log(a.next())
console.log(a.next())
console.log(a.next())
console.log(a.next())
console.log(a.next())
console.log(a.next())

// { value: 1, done: false }
// { value: 2, done: false }
// { value: 3, done: false }
// { value: 4, done: false }
// { value: 5, done: false }
// { value: 6, done: false }
// { value: 7, done: false }
// { value: 8, done: false }
// { value: 9, done: false }
// { value: undefined, done: true }
// { value: undefined, done: true }
// { value: undefined, done: true }
// { value: undefined, done: true }

职责链模式

使多个对象都有机会处理请求,从而避免了请求的发送者与多个接收者直接的耦合关系,将这些接受连接成一条链,顺着这条链传递该请求,知道找到能处理该请求的对象

这种模式符合单一原则,使每个方法都只有一个职责;符合开放封闭原则,在需求增加时可以很方便的扩充新责任,使用时也无需知晓谁是真正的处理方法,减少了大量的 if 以及 switch 循环判断语句

这种模式的缺点是团队成员需要对责任链存在共识,也就是说这个模式写上别人不容易看懂,排查错误也是不容易的

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Document</title>
	</head>
	<body>
		<input type="text" placeholder="输入符合条件的密码" />

		<button>确认</button>

		<script>
			const ipt = document.querySelector("input")
			const btn = document.querySelector("button")
			btn.addEventListener("click", () => {
				checks.check()
			})

			function checkEmpty() {
				if (!ipt.value.trim()) {
					console.log("不能为空")
					return
				}
				return "next"
			}

			function checkNumber() {
				if (Number.isNaN(+ipt.value)) {
					console.log("只能为数字")
					return
				}
				return "next"
			}

			function checkLength() {
				if (ipt.value.length < 8) {
					console.log("必须大于 8 位")
					return
				}
				return "next"
			}

			///

			class Chain {
				constructor(func) {
					this.checkRule = func || (() => "next")
					this.nextRule = null
				}
				addRule(nextRule) {
					this.nextRule = new Chain(nextRule)
					return this.nextRule
				}
				end() {
					this.nextRule = {
						check: () => "end",
					}
				}
				check() {
					this.checkRule() == "next" ? this.nextRule.check() : null
				}
			}

			
			const checks = new Chain()
			checks.addRule(checkEmpty).addRule(checkNumber).addRule(checkLength).end()
		</script>
	</body>
</html>

在这里插入图片描述


结束了

这么快就结尾了,我想说我们不要为了使用设计模式而去使用设计模式,有时候根本没必要,有时候会画蛇添足,如果使用设计模式可以给你带来代码的整洁可维护或者业务上的性能提升,那么在这个时候我们可以适当考虑使用设计模式,它很棒 😎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/350163.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

72 优化算法【动手学深度学习v2】

72 优化算法【动手学深度学习v2】 深度学习学习笔记 学习视频&#xff1a;https://www.bilibili.com/video/BV1bP4y1p7Gq/?spm_id_from333.1007.top_right_bar_window_history.content.click&vd_source75dce036dc8244310435eaf03de4e330 优化问题 优化问题一般是最小化f…

怎么样可以查看系统的内存和显示您硬盘中文件和文件夹的分布情况——SpaceSniffer

一、找内存 &#xff08;1&#xff09;右击此电脑&#xff0c;点击属性&#xff0c;即可看到如下 &#xff08;2&#xff09;或者可以打开电脑桌面&#xff0c;右击任务栏&#xff0c;如下 然后打开任务管理器 二、搜索各个盘的文件 SpaceSniffer是一个可以显示您硬盘中文…

防火墙有关iptables的知识点

基本概念 什么是防火墙 在计算中&#xff0c;防火墙是基于预定安全规则来监视和控制传入和传出网络流量的网络安全系统。该计算机流入流出的所有网络通信均要经过此防火墙。防火墙对流经它的网络通信进行扫描&#xff0c;这样能够过滤掉一些攻击&#xff0c;以免其在目标计算机…

MySQL——索引视图练习题

学生表&#xff1a;Student (Sno, Sname, Ssex , Sage, Sdept) 学号&#xff0c;姓名&#xff0c;性别&#xff0c;年龄&#xff0c;所在系 Sno为主键 课程表&#xff1a;Course (Cno, Cname,) 课程号&#xff0c;课程名 Cno为主键 学生选课表&#xff1a;SC (Sno, Cno, Score)…

大白话高并发(三)

背景 高并发得第三篇&#xff0c;讲一讲压测吧&#xff0c;因为我的目的是模拟100万人同时来秒杀。 是不是真的要找100万个人 没必要 &#xff0c;你就算100万人掐着表在同一毫秒内把请求请求某一台机器&#xff0c;服务器也不可能在同一时间处理那么多请求&#xff0c;因为…

同步辐射XAFS表征方法的应用场景分析

X射线吸收精细结构XAFS表征方法是一种用于研究物质结构和化学环境的分析技术。XAFS 使用 X 射线照射到物质表面&#xff0c;并观察由此产生的 X 光吸收谱。 ​XAFS 技术通常应用于研究高分子物质、生物分子、纳米结构和其他类型的物质。例如&#xff0c;XAFS 可以用来研究高分子…

使用git上传项目到GitHub教程

文章目录一、安装Git二、上传本地文件到git上。1.创建本地版本库2.通过命令git init把这个文件夹变成Git可管理的仓库3、使用git commit -m "XXX"命令提交4、在Github上创建一个Git仓库5、执行git remote add origin xxxxxxxx.git6、使用$ git push -u origin master…

35、基于51单片机自动灭火避障智能小车 消防灭火小车系统设计

摘要 智能作为现代的新发明&#xff0c;是以后的发展方向&#xff0c;他可以按照预先设定的模式在一个环境里自动的运作&#xff0c;不需要人为的管理&#xff0c;可应用于科学勘探等等的用途。智能小车就是其中的一个体现&#xff0c;本次设计的多功能智能灭火避障小车&#…

MySQL番外篇-硬件优化概述

备注:测试数据库版本为MySQL 8.0 硬件优化概述 MySQL的硬件有: CPU内存硬盘网络资源 对于硬件的选择与调优&#xff0c;在系统上线前就需要考虑起来。 当然我们都知道: 好的CPU&#xff0c;可以让SQL语句解析得更快&#xff0c;进而加快SQL语句的执行速度。大的内存&#…

GEE学习笔记 八十七:python版GEE动态加载地图方法

在Google Earth Engine的python版API更新后&#xff0c;之前使用folium动态加载地图的代码就不能在正常运行&#xff0c;因为整个Google Earth Engine的地图加载服务的URL发生了更新&#xff0c;所以我们也需要更新相关绘制方法。下面我会讲解一种新的绘制方法&#xff0c;大家…

【深度学习】激活函数

上一章——认识神经网络 新课P54介绍了强人工智能概念&#xff0c;P55到P58解读了矩阵乘法在代码中的应用&#xff0c;P59&#xff0c;P60介绍了在Tensflow中实现神经网络的代码及细节&#xff0c;详细的内容可以自行观看2022吴恩达机器学习Deeplearning.ai课程&#xff0c;专…

NVIDIA Tesla V100部署与使用

在先前的实验过程中&#xff0c;使用了腾讯云提供的nvidia T4GPU&#xff0c;尽管其性能较博主的笔记本有了极大提升&#xff0c;但总感觉仍有些美中不足&#xff0c;因此本次博主租赁了nvidia V100 GPU&#xff0c;看看它的性能表现如何。 和先前一样&#xff0c;只需要将服务…

2023美赛A题思路数据代码分享

文章目录赛题思路2023年美国大学生数学建模竞赛选题&论文一、关于选题二、关于论文格式三、关于论文提交四、论文提交流程注意不要手滑美赛A题思路数据代码【最新】赛题思路 (赛题出来以后第一时间在CSDN分享) 最新进度在文章最下方卡片&#xff0c;加入获取一手资源 202…

医学图象分割常用损失函数(附Pytorch和Keras代码)

对损失函数没有太大的了解&#xff0c;就是知道它很重要&#xff0c;搜集了一些常用的医学图象分割损失函数&#xff0c;学习一下&#xff01; 医学图象分割常见损失函数前言1 Dice Loss2 BCE-Dice Loss3 Jaccard/Intersection over Union (IoU) Loss4 Focal Loss5 Tvesky Loss…

学生投票系统-课后程序(JAVA基础案例教程-黑马程序员编著-第三章-课后作业)

【案例3-4】学生投票系统 记得 关注&#xff0c;收藏&#xff0c;评论哦&#xff0c;作者将持续更新。。。。 【案例介绍】 案例描述 某班级投票竞选班干部&#xff0c;班级学生人数为100人&#xff0c;每个学生只能投一票。 本任务要求&#xff0c;编程实现一个投票程序&…

2023美赛赛题和数据公布啦~【附中文翻译版】

2023美赛赛题和数据公布啦~ 加2023年的美国大学生数学建模竞赛 数学建模竞赛是一项在全球范围内非常受欢迎的竞赛&#xff0c;旨在鼓励学生运用数学知识和建模技能解决实际问题。这项竞赛不仅对学生的数学能力提出了很高的要求&#xff0c;还对他们的创造性、团队协作和沟通能…

经典算法题---链表奇偶重排(好题)双指针系列

我听别人说这世界上有一种鸟是没有脚的&#xff0c;它只能够一直的飞呀飞呀&#xff0c;飞累了就在风里面睡觉&#xff0c;这种鸟一辈子只能下地一次&#xff0c;那一次就是它死亡的时候。——《阿甘正传》这一文章讲解链表的奇偶排序问题&#xff0c;这是一道不难但是挺好的链…

凹凸贴图(Bump Mapping)

凹凸贴图是什么&#xff1f; 我们首先来看low-poly&#xff08;多边形数较少&#xff09;mesh和high-poly&#xff08;多边形数量较多&#xff09;mesh之间的不同。首先&#xff0c;最明显的不同就是high-poly能够表现出更多细节&#xff0c;但high-poly有比较大的性能开销。有…

springboot下@transcation使用基本介绍

springboot下transcation基本使用的几种可能 普通常使用的几种可能&#xff08;事务的传播行为默认值Propagation.REQUIRED&#xff09;&#xff1a; transcation只在使用方法A上&#xff0c;A内无调用其他方法&#xff0c;事务正常方法A和方法B在同一个类下&#xff0c;transc…

net6中使用FluentValidation做实体验证(批量注册)

实体验证-FluentValidation 首先明白两个概念 自动验证&#xff1a;就是在请求进入到控制器前FluentValidation就自行完成实体的验证并做错误返回&#xff0c; 优点&#xff1a;简单 少一些手动调用的代码缺点&#xff1a;灵活性差&#xff0c;不好控制&#xff0c;不支持异步…