网站设计难点,asp化妆品网站 后台,狠狠做网站改成什么了,电影网站域名设计模式简介
设计模式源于GOF#xff08;四人帮#xff09;合著出版的《设计模式#xff1a;可复用的面向对象软件元素》#xff0c;该书第一次完整科普了软件开发中设计模式的概念#xff0c;他们提出的设计模式主要是基于以下的面向对象设计原则#xff…设计模式简介
设计模式源于GOF四人帮合著出版的《设计模式可复用的面向对象软件元素》该书第一次完整科普了软件开发中设计模式的概念他们提出的设计模式主要是基于以下的面向对象设计原则
对接口编程而不是对实现编程优先使用对象组合而不是继承
根据该书所提到的总共有 23 种设计模式这些模式可以分为以下三大类
1创建型模式
这些模式提供了一种在创建对象的同时隐藏创建逻辑的方式而不是使用 new 运算符直接实例化对象这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活
包括工厂模式、抽象工厂模式、单例模式、建造者模式、原型模式
2结构型模式
这些模式关注对象之间的组合和关系旨在解决如何构建灵活且可复用的类和对象结构
包括适配器模式、桥接模式、过滤器模式、组合模式、装饰器模式、外观模式、享元模式、代理模式
3行为型模式
这些模式关注对象之间的通信和交互旨在解决对象之间的责任分配和算法的封装
包括责任链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、空对象模式、策略模式、模板模式、访问者模式
下图描述设计模式之间的关系
设计模式的六大原则
1开闭原则
开闭原则是指对扩展开发对修改关闭。在程序需要进行扩展的时候不能去修改原有的代码想要达到这样的效果我们需要使用接口和抽象类
2里氏代换原则
里氏代换原则中说任何基类可以出现的地方子类一定可以出现。该原则其实是对开闭原则的补充实现开闭原则的关键步骤是抽象化而基类与子类的继承关系就是抽象化的具体实现所以里氏代换原则是对实现抽象化的具体步骤的规范
3依赖倒转原则
这个原则是开闭原则的基础具体内容针对接口编程依赖于抽象而不依赖于具体
4接口隔离原则
接口隔离原则是指使用多个隔离的接口比使用单个接口要好还有个意思是降低类之间的耦合度
5迪米特原则又称最少知道原则
最少知道原则是指一个实体尽量少地与其他实体发生相互作用使得系统功能模块相互独立
6合成复用原则
合成复用原则是指尽量使用合成/聚合的方式而不是使用继承
工厂模式
工厂模式提供了一种创建对象的方式而无需指定要创建的具体类。通过使用工厂模式可以将对象的创建逻辑封装到一个工厂类里而不是在客户端代码中直接实例化对象
工厂模式主要有以下几种类型
1简单工厂模式
简单工厂模式不是一个正式的设计模式但它是工厂模式的基础它使用一个单独的工厂类来创建不同的对象根据传入的参数决定创建哪种类型的对象
2工厂方法模式
工厂方法模式定义了一个创建对象的接口但由子类决定实例化哪个类工厂方法将对象的创建延迟到子类
3抽象工厂模式
抽象工厂模式提供一个创建一系列相关或互相依赖对象的接口而无需指定它们具体的类 当我们需要在不同条件下创建不同实例时可以使用工厂模式它的使用场景主要有以下几个
1日志记录日志可能记录到本地硬盘、远程服务器等用户可以选择记录日志的位置
2数据库访问当用户不知道最终系统使用哪种数据库或者数据库可能变化时
PS工厂模式适用于生成复杂对象的场景如果对象较为简单通过 new 即可完成创建而不必使用工厂模式使用工厂模式会引入一个工厂类增加系统复杂度 它的优缺点
1优点
调用者只需要知道对象的名称即可创建对象扩展性高如果需要增加新产品只需扩展一个工厂类即可屏蔽了产品的具体实现调用者只关心产品的接口
2缺点
每次增加一个产品时都需要增加一个具体类和对应的工厂使系统中类的数量成倍增加增加了系统的复杂度和具体类的依赖 工厂模式包含以下几个主要角色
1抽象产品
定义了产品的共同接口或抽象类它可以是具体产品类的父类或接口规定了产品对象的共同方法
2具体产品
实现了抽象产品接口定义了具体产品的特定行为和属性
3抽象工厂
声明了创建产品的抽象方法可以是接口或抽象类它可以有多个方法用于创建不同类型的产品
4具体工厂
实现了抽象工厂接口负责实际创建具体产品的对象 我们通过以下一个简单的实例来展示工厂模式的实现假设我们有不同种类的button需要创建每个按钮有不同的实现
1定义抽象产品和具体产品
Button类是一个抽象类接口它有两个方法render()用于渲染按钮onClick()用于按钮点击后的行为
WindowsButton和 MacButton分别实现了Button接口提供具体的渲染和点击行为
// 抽象产品button
class Button {render() {throw new Error(方法 render() 被实现)}onClick() {throw new Error(方法 onClick() 被实现)}
}// 具体产品Windows 按钮
class WindowsButton extends Button {render() {console.log(渲染 Windows 按钮)}onClick() {console.log(点击了 Windows 按钮)}
}// 具体产品Mac 按钮
class MacButton extends Button {render() {console.log(渲染 Mac 按钮)}onClick() {console.log(点击了 Mac 按钮)}
}2定义抽象工厂和具体工厂
ButtonFactory是工厂接口定义了createButton()方法用来创建具体的按钮
WindowsButtonFactory和MacButtonFactory分别实现了createButton()方法创建不同操作系统的按钮
// 工厂接口
class ButtonFactory {createButton() {throw new Error(方法 createButton() 被实现)}
}// 具体工厂Windows 按钮工厂
class WindowsButtonFactory extends ButtonFactory {createButton() {console.log(创建 Windows 按钮)return new WindowsButton()}
}// 具体工厂Mac 按钮工厂
class MacButtonFactory extends ButtonFactory {createButton() {console.log(创建 Mac 按钮)return new MacButton()}
}3使用工厂创建对象
renderButton() 是客户端方法通过传入工厂对象来创建并使用按钮
// 客户端代码根据需要选择不同的工厂来创建不同的按钮
function renderButton(factory) {const button factory.createButton()button.render() button.onClick()
}// 模拟渲染不同操作系统的按钮
console.log(Windows 按钮)
renderButton(new WindowsButtonFactory())console.log(Mac 按钮)
renderButton(new MacButtonFactory())执行上述代码控制台打印出 抽象工厂模式
抽象工厂模式是工厂模式的扩展它提供一个接口用于创建一系列相关或相互依赖的对象而无需指定具体的类换句话说抽象工厂模式为一组产品提供了一个接口这组产品可能有多个不同的具体实现而每一个具体工厂类都负责创建这些产品的不同实现 它的使用场景主要有以下几个
1系统需要多个产品族的产品在一起工作但不需要指定具体类时
2系统需要独立于产品的创建、组合和表示时 它的优缺点
1优点
确保同一产品族的对象一起工作客户端不需要知道每个对象的具体类简化了代码
2缺点
增加一个新的产品族需要修改抽象工厂和所有具体工厂的代码 假设我们需要在多个操作系统上创建不同风格的按钮和文本框将使用抽象工厂模式来创建这些 UI 元素以便在不同操作系统上使用相应的样式
1定义抽象产品接口
定义Button和TextBox作为抽象产品它们是各个操作系统上的 UI 元素
// 抽象产品Button
class Button {render() {throw new Error(方法 render() 被实现)}onClick() {throw new Error(方法 onClick() 被实现)}
}
// 抽象产品TextBox
class TextBox {render() {throw new Error(方法 render() 被实现)}onFocus() {throw new Error(方法 onFocus() 被实现)}
}2定义具体产品
定义具体产品类WindowsButton、MacButton、WindowsTextBox和MacTextBox它们实现了上述的抽象产品接口
// 具体产品Windows 按钮
class WindowsButton extends Button {render() {console.log(渲染 Windows 按钮)}onClick() {console.log(点击了 Windows 按钮)}
}
// 具体产品Mac 按钮
class MacButton extends Button {render() {console.log(渲染 Mac 按钮)}onClick() {console.log(点击了 Mac 按钮)}
}
// 具体产品Windows 文本框
class WindowsTextBox extends TextBox {render() {console.log(渲染 Windows 文本框)}onFocus() {console.log(Windows 文本框获得焦点)}
}
// 具体产品Mac 文本框
class MacTextBox extends TextBox {render() {console.log(渲染 Mac 文本框)}onFocus() {console.log(Mac 文本框获得焦点)}
}3定义抽象工厂接口
UIFactory是抽象工厂接口定义了两个方法createButton和createTextBox用来创建按钮和文本框
// 抽象工厂接口UIFactory
class UIFactory {createButton() {throw new Error(方法 createButton() 必须被实现)}createTextBox() {throw new Error(方法 createTextBox() 必须被实现)}
}4定义具体工厂
具体工厂WindowsFactory和MacFactory负责实例化具体的 UI 元素按钮和文本框
// 具体工厂Windows 工厂
class WindowsFactory extends UIFactory {createButton() {console.log(创建 Windows 按钮)return new WindowsButton()}createTextBox() {console.log(创建 Windows 文本框)return new WindowsTextBox()}
}
// 具体工厂Mac 工厂
class MacFactory extends UIFactory {createButton() {console.log(创建 Mac 按钮)return new MacButton()}createTextBox() {console.log(创建 Mac 文本框)return new MacTextBox()}
}5客户端使用工厂
根据需求选择不同的工厂来创建产品按钮和文本框而不关心具体的产品实现
// 客户端代码根据工厂创建 UI 元素
function renderUI(factory) {const button factory.createButton() button.render() button.onClick() const textBox factory.createTextBox() textBox.render() textBox.onFocus()
}console.log(渲染 Windows 风格的 UI:)
renderUI(new WindowsFactory())console.log(渲染 Mac 风格的 UI:)
renderUI(new MacFactory())执行上述代码控制台打印出 单例模式
单例模式确保一个类只有一个实例并提供了一个全局访问点来访问该实例需要注意的是
1单例类只能有一个实例
2单例类必须自己创建自己的唯一实例
3单例类必须给其他对象提供这一实例 当需要控制实例数量节省系统资源时可以使用单例模式它的使用场景主要有以下几个
1生成唯一序列号
2创建消耗资源过多的对象比如I/O、数据库连接等
3如果某个计算的结果是全局唯一且不希望重复计算可以使用单例模式缓存该结果确保第一次计算后复用该结果 它的缺点
1没有接口不能继承
2与单一职责原则冲突一个类应该只关心内部逻辑而不关心实例化方式 单例模式可以使用以下不同的方法来实现
1懒汉式
在首次调用时创建实例适合资源消耗较大的情况
2饿汉式
在类加载时创建实例适合在程序启动时就需要用到实例的情况
懒汉式
懒汉式的单例模式是在需要实例的时候才创建实例实例是延迟加载的只有在第一次访问时才会创建
第一次创建instance1时Singleton的构造函数会初始化实例并存储在Singleton.instance中第二次创建instance2时由于Singleton.instance已经存在构造函数直接返回已有的实例
class Singleton {constructor() {// 如果实例已经存在直接返回该实例if (Singleton.instance) {return Singleton.instance}// 否则就初始化实例this.value Math.random()Singleton.instance this}getValue() {return this.value}
}// 测试
const instance1 new Singleton()
console.log(instance1.getValue())const instance2 new Singleton()
console.log(instance2.getValue())console.log(instance1 instance2) // 输出 true说明是同一个实例在 JavaScript 中单线程模型通常不会遇到并发问题但是在多线程环境中如果多个线程同时调用new Singleton()可能会创建多个实例违背了单例模式的原则
为了避免多个线程并发访问时创建多个实例我们可以通过加锁来确保同一时刻只有一个线程能创建实例使用 synchronized 来进行加锁
在 JavaScript 中实现上锁的概念时通常使用一个标志变量如lock来模拟锁的功能
通过Singleton.lock来确保只有一个线程能执行实例创建的代码其他线程在此期间会被阻塞直到锁被释放在try...finally结构中确保即使发生异常也能释放锁
class Singleton {constructor() {// 如果实例已经存在直接返回该实例if (Singleton.instance) {return Singleton.instance}// 模拟线程安全的加锁机制if (!Singleton.lock) {Singleton.lock true // 上锁try {// 如果没有实例则创建一个this.value Math.random()Singleton.instance this} finally {Singleton.lock false // 解锁}}}getValue() {return this.value}
}// 测试
const instance1 new Singleton()
console.log(instance1.getValue())const instance2 new Singleton()
console.log(instance2.getValue())console.log(instance1 instance2) // 输出 true说明是同一个实例饿汉式
饿汉式的单例模式与懒汉式不同它在类加载时就创建好实例饿汉式实现不需要考虑线程安全问题因为实例在类加载时就创建好了只有一个线程能访问类加载阶段
在下面的实现中Singleton.instance是类加载时就创建好的静态属性因此无论多少次访问都返回相同的实例
class Singleton {static instance new Singleton()constructor() {if (Singleton.instance) {return Singleton.instance}this.value Math.random() }getValue() {return this.value}
}// 测试
const instance1 Singleton.instance
console.log(instance1.getValue())const instance2 Singleton.instance
console.log(instance2.getValue())console.log(instance1 instance2) // 输出 true说明是同一个实例建造者模式
建造者模式将复杂对象的构建与表示分离使得同样的构建过程可以创建不同的对象 它的使用场景主要有以下
1当一个对象的构建过程很复杂可能涉及多个步骤且这些步骤可以独立进行
2需要支持产品的多种表现形式不同配置、不同组合
PS与工厂模式的区别是建造者模式更加关注于零件装配的顺序 它的优缺点
1优点
分离构建过程和表示可以构建不同的表示代码复用性高可以在不同的构建过程中重复使用相同的建造者
2缺点
如果产品属性较少该模式会导致代码冗余增加了系统的类和对象数量 建造者模式包含以下几个主要角色
1产品
要构建的复杂对象产品类通常包含多个部分或属性
2抽象建造者
定义了构建产品的抽象接口包括构建产品的各个部分的方法
3具体建造者
实现抽象建造者接口具体确定如何构建产品的各个部分并负责返回最终构建的产品
4指导者
负责调用建造者的方法来构建产品指导者并不了解具体的构建过程只关心产品的构建顺序和方式 假设我们需要构建一辆汽车汽车有多个部件不同配置的汽车有不同的部件组合下面通过建造者模式来组织这个构建过程
1定义产品类
构建一个汽车产品类它有多个部件引擎、轮胎、车窗
class Car {constructor() {this.engine nullthis.tires nullthis.windows null}show() {console.log(汽车配置引擎: ${this.engine}, 轮胎: ${this.tires}, 车窗: ${this.windows})}
}2定义抽象建造者
建造者类定义了构建汽车的各个步骤但不包含具体的实现
//抽象建造者CarBuilder
class CarBuilder {buildEngine() {throw new Error(子类实现 buildEngine 方法)}buildTires() {throw new Error(子类实现 buildTires 方法)}buildWindows() {throw new Error(子类实现 buildWindows 方法)}getCar() {throw new Error(子类实现 getCar 方法)}
}3定义具体建造者
具体建造者类继承CarBuilder并实现了具体的构建过程这里定义了两种不同配置的汽车普通汽车和豪华汽车
//具体建造者普通汽车 NormalCarBuilder
class NormalCarBuilder extends CarBuilder {constructor() {super()this.car new Car()}buildEngine() {this.car.engine 普通引擎}buildTires() {this.car.tires 普通轮胎}buildWindows() {this.car.windows 普通车窗}getCar() {return this.car}
}
//具体建造者豪华汽车 LuxuryCarBuilder
class LuxuryCarBuilder extends CarBuilder {constructor() {super()this.car new Car()}buildEngine() {this.car.engine 豪华引擎}buildTires() {this.car.tires 豪华轮胎}buildWindows() {this.car.windows 豪华车窗}getCar() {return this.car}
}4定义指导者
指导者负责按照一定的顺序调用建造者类的方法来创建汽车
//指导者Director
class Director {constructor(builder) {this.builder builder}construct() {this.builder.buildEngine()this.builder.buildTires()this.builder.buildWindows()}
}5客户端
客户端通过指导者来控制建造过程最终得到一个构建好的汽车对象
// 客户端代码
const normalCarBuilder new NormalCarBuilder()
const normalDirector new Director(normalCarBuilder)normalDirector.construct()
const normalCar normalCarBuilder.getCar()
normalCar.show() // 输出: 汽车配置引擎: 普通引擎, 轮胎: 普通轮胎, 车窗: 普通车窗const luxuryCarBuilder new LuxuryCarBuilder()
const luxuryDirector new Director(luxuryCarBuilder)luxuryDirector.construct()
const luxuryCar luxuryCarBuilder.getCar()
luxuryCar.show() // 输出: 汽车配置引擎: 豪华引擎, 轮胎: 豪华轮胎, 车窗: 豪华车窗原型模式
原型模式通过复制现有的对象来创建新对象而不是通过直接构造新的对象避免了重复初始化复杂对象的开销 它的使用场景主要有以下几个
1类初始化需要消耗大量资源如数据、硬件资源
2通过 new 创建对象需要复杂的数据准备或访问权限时
3一个对象需要多个修改者
PS与直接实例化类创建新对象不同原型模式通过拷贝现有对象生成新对象 它的优缺点
1优点
性能提高避免构造函数的约束
2缺点
配置克隆方法需要全面考虑类的功能对已有类可能较难实现特别是处理不支持串行化的间接对象或含有循环结构的引用时 原型模式包含以下几个主要角色
1原型接口
定义一个用于克隆自身的接口通常包含一个 clone() 方法
2具体原型类
实现圆形接口的具体类负责实际的克隆操作通常使用浅拷贝或深拷贝来复制自身
浅拷贝复制对象时原始对象的属性值会被复制到新对象中但如果属性是引用类型则会复制引用而不是实际的对象深拷贝不仅复制对象本身的属性还会递归地复制对象中引用类型的属性从而确保新对象完全独立于原始对象
3客户端
使用原型实例来创建新的对象客户端调用原型对象的 clone() 方法来创建新的对象而不是直接使用构造函数 假设我们需要复制一个游戏角色这个角色有多个属性如名字、血量、装备等通过克隆原始角色来创建一个新的角色
1创建一个GameCharacter类
// 原型类游戏角色
class GameCharacter {constructor(name, health, weapons) {this.name name this.health health this.weapons weapons }// 显示角色信息display() {console.log(角色${this.name}, 血量${this.health}, 武器${this.weapons.join(, )})}// 克隆方法 - 浅拷贝clone() {return new GameCharacter(this.name, this.health, [...this.weapons])}
}2创建一个简单的角色对象
// 创建一个角色
const originalCharacter new GameCharacter(战士, 100, [剑, 盾])
originalCharacter.display()3克隆角色
// 克隆角色
const clonedCharacter originalCharacter.clone()
clonedCharacter.display()4修改克隆后的对象
修改克隆后的角色看看它是否独立于原始对象
// 修改克隆后的角色
clonedCharacter.name 弓箭手
clonedCharacter.health 80
clonedCharacter.weapons.push(弓)// 显示修改后的角色信息
clonedCharacter.display()// 显示原始角色信息
originalCharacter.display()从以下输出中可以看出原始角色和克隆后的角色是独立的修改克隆角色的属性不会影响原始角色 当前的 clone 方法是浅拷贝它会直接复制对象的引用类型属性如 weapons 数组如果修改了克隆角色的weapons会影响到原始角色
为了避免这个问题我们可以在 clone 方法中使用深拷贝保证即使 weapons 是引用类型也会完全复制
// 深拷贝确保复制所有引用类型的内容
clone() {return new GameCharacter(this.name, this.health, JSON.parse(JSON.stringify(this.weapons)))
}