这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战
Creational Design Patterns 创建型设计模式
开始总结设计模式的自省(5W1H)
why:让代码更具健壮性、可维护性、可重用性、可靠性、可理解性
what:设计模式是一套被反复使用的、资深coding玩家知晓的、代码设计经验的总结,是软件开发人员在软件开发过程中面临的一般问题的解决方案
where:这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的,设计模式无处不在
when:当你发现自己的项目十分臃肿,或是在项目的不同地方出现很多相同的代码,它们实现了相同或是有交集的功能点时,我们就需要考虑用设计模式了
who:当然由资深的开发工程师来带领我们这些小弟来进行完成,在不断的代码积累中学习更多的设计模式和设计思想
how:从现有项目中学习现有设计模式、请教资深程序员、不断学习设计模式相关书籍并加以实践
讲述设计模式的原则(SOLID原则)
- S —— Single Responsibility Principle 单一职责原则
- 很好理解:一个程序只负责做好一件事
- 如果一个方法过于复杂,那么是肯定可以拆分的
- O —— OpenClosed Principle 开放/封闭原则
- 对扩展开放,对修改封闭
- 即可扩展,不可修改;增加需求时是扩展新代码,而不是修改已有代码
- L —— Liskov Substitution Principle 里氏替换原则
- 一个对象在其出现的任何地方,都可以用子类实例做替换
- 父类能出现的地方子类就能出现,子类能覆盖父类
- I —— Interface Segregation Principle 接口隔离原则
- 保持接口的单一独立
- 客户端程序不应该依赖它不需要的接口方法
- D —— Dependency Inversion Principle 依赖倒置原则
- 抽象不应该依赖于细节,细节应当依赖于抽象
- 针对抽象编程,而不是针对实现的细节编程
什么是创建型设计模式
定义:专注于如何实例化一个对象或者一组相关的对象
程序设计遇到的问题:对象创建的基本形式可能会导致设计上的问题或者是增加设计的复杂性,此时我们通过使用合适的创建型设计模式可以解决这个问题
分类:主要涉及到以下几种设计模式
- 简单工厂模式
- 工厂模式
- 抽象工厂模式
- 构造器模式
- 单例模式
简单工厂模式
真实的案例
想象一下,你准备盖房子,然后你需要门,很多的门,如果说你每次需要门的时候,都需要穿上木匠的衣服然后做出一扇门,诚然,不得不夸奖你很刻苦,但做了很多重复性的工作,那这个在程序中将会是十分糟糕的。相反,我们可以从工厂来获得门
简而言之
简单工厂只是给用户生成了一个实例,而且还不向用户暴露任何实例化的逻辑
维基百科
In object-oriented programming (OOP), a factory is an object for creating other objects – formally a factory is a function or method that returns objects of a varying prototype or class from some method call, which is assumed to be "new".
在面向对象的编程中,工厂是用于创建其他对象的对象——从形式上讲,工厂是一个函数或方法,从一些方法的调用中返回不同原型或类的对象,这被认为是**”new“**
编程案例
首先我们有一个门的接口和相关的实现
// 木门,有长和宽两个属性,原型上有获取长和宽的方法
class WoodenDoor {
constructor(width, height) {
this.width = width;
this.height = height;
}
getWidth() {
return this.width;
}
getHeight() {
return this.height;
}
}
然后就是实现门工厂,它可以制造门且返回给你一个门的实例
const DoorFactory = {
makeDoor: (width, height) => new WoodenDoor(width, height)
};
最后我们可以这样使用门工厂
const door = DoorFactory.makeDoor(100, 200);
console.log('Width:', door.getWidth()); // 100
console.log('Height:', door.getHeight()); // 200
什么时候使用
当我们创建一个对象不仅仅是几个赋值、而且还涉及到一些逻辑上的处理时,可以讲它放在一个专门的工厂中,而不是到处写重复的相同的代码,这是没有意义的
工厂模式
真实的案例
想象一下,你开了家挺大的公司,有很多岗位都是空缺的,所以你一个人去同时面试很多求职者是很不科学的,且很费力,所以根据职位的空缺,我应该是要分配对应部分的人去做面试的这步操作
简而言之
它提供了一种方法:将实例化逻辑委派给子类
维基百科
In class-based programming, the factory method pattern is a creational pattern that uses factory methods to deal with the problem of creating objects without having to specify the exact class of the object that will be created. This is done by creating objects by calling a factory method—either specified in an interface and implemented by child classes, or implemented in a base class and optionally overridden by derived classes—rather than by calling a constructor.
工厂方法模式是一种创建模式,它使用工厂方法来处理创建对象的问题,而不必指定将要创建的对象的确切的类,通过调用工厂方法创建对象来完成,要么在接口中指定并由子类实现,要么在基类中实现并可选择由派生类进行覆盖,而不是通过调用构造函数
编程案例
以我们上面的招聘为例
class Developer {
askQuestions () {
console.log('Asking about design patterns!');
}
}
class CommunityExecutive {
askQuestions() {
console.log('Asking about community building');
}
}
现在我们创建一下面试官
class HiringManager {
takeInterview() {
const interviewer = this.makeInterviewer();
interviewer.askQuestions();
}
}
现在任何子类可以继承它并且提供所需要的面试官
class DevelopmentManager extends HiringManager {
makeInterviewer() {
return new Developer();
}
}
class MarketingManager extends HiringManager {
makeInterviewer() {
return new CommunityExecutive();
}
}
最后我们可以这样使用
const devManager = new DevelopmentManager();
devManager.takeInterview(); // Asking about design patterns!
const marketingManager = new MarketingManager();
marketingManager.takeInterview(); // Asking about community buildng.
什么时候使用
当类中有一些通用处理,但所需的子类是在运行时动态决定的时候是有用的,换句话说,当你不知道它可能需要什么确切的子类的时候可以使用
抽象工厂模式
真实的案例
想象一下,我们在简单工厂的基础上,根据你的需要,你可能会从木门店购买木门,从铁门店购买铁门,或者从其他门店购买指纹门等,另外你还可能需要不同的专业人事来帮你安装门,所以,门之间还存在依赖关系
简而言之
将单个相关但之间互相依赖的工厂组合在一起而不是指定其具体的工厂
维基百科
The abstract factory pattern provides a way to encapsulate a group of individual factories that have a common theme without specifying their concrete classes
抽象工厂模式提供了一种封装一组具有共同主题的独立工厂的方法,而无需指定它们的具体类
编程案例
首先我们有两个门的类和相关的实现
class WoodenDoor {
getDescription() {
console.log('I am a wooden door');
}
}
class IronDoor {
getDescription() {
console.log('I am an iron door');
}
}
然后我们有几种适合安装门的专家,铁门需要铁门师傅安装,木门需要木门师傅安装
class Carpenter {
getDescription() {
console.log('I can only fit wooden doors');
}
}
class Welder {
getDescription() {
console.log('I can only fit iron doors');
}
}
现在我们有了我们的抽象工厂,可以让我们制作相关对象的组合,即木门工厂将创建木门和木门配件专家,铁门工厂将创建铁门和铁门配件专家
class WoodenDoorFactory {
makeDoor() {
return new WoodenDoor();
}
makeFittingExpert() {
return new Carpenter();
}
}
class IronDoorFactory {
makeDoor() {
return new IronDoor();
}
makeFittingExpert() {
return new Welder();
}
}
现在我们来使用
const woodenFactory = new WoodenDoorFactory();
let door = woodenFactory.makeDoor();
let expert = woodenFactory.makeFittingExpert();
door.getDescription(); // I am a wooden door
expert.getDescription(); // I can only fit wooden doors
const ironFactory = new IronDoorFactory();
door = ironFactory.makeDoor();
expert = ironFactory.makeFittingExpert();
door.getDescription(); // I am an iron door
expert.getDescription(); // I can only fit iron doors
我们现在看到木门厂封装了木门和木工,铁门厂封装了铁门和电焊工,这保证我们在从工厂创建对象时不会调用错误的装门专家
什么时候使用
当存在相互关联的依赖关系时,涉及的创建逻辑并不那么简单
构造器模式
真实的案例
想象一下,你去肯德基买了一个汉堡,然后服务员把汉堡做出来给到你,没有任何问题,这就是简单工厂的实例。这个汉堡其实就是服务员 new 出来的一个实例,但是你在 new 的过程中,可能还会涉及到其他的流程或者步骤,比如,你想要多大的汉堡,你想要什么味道的汉堡,你想要往汉堡里面放些什么等等,这个时候,构造器模式就能够派上用场了
简而言之
允许您创建不同风格的对象,同时避免构造函数污染。 当一个对象可能有多种风格时很有用。 或者当创建对象涉及很多步骤时有用
维基百科
The builder pattern is an object creation software design pattern with the intentions of finding a solution to the telescoping constructor anti-pattern.
构建器模式是一种对象创建软件设计模式,旨在寻找伸缩构造器反模式的解决方案。
什么是伸缩构造器反模式
constructor(size, cheese = true, pepperoni = true, tomato = false, lettuce = true) {
// ...
}
正如您所看到的,构造函数参数的数量很快就会失控,并且可能难以理解参数的排列。 此外,如果您希望将来添加更多选项,此参数列表可以继续增长。 这称为伸缩构造函数反模式。
编程案例
首先我们要做一个
class Burger {
constructor(builder) {
this.size = builder.size; // 规格
this.cheeze = builder.cheeze || false; // 奶酪
this.pepperoni = builder.pepperoni || false; // 香肠
this.lettuce = builder.lettuce || false; // 生菜
this.tomato = builder.tomato || false; // 土豆
}
}
然后我们有一个 builder 构造器
class BurgerBuilder {
constructor(size) {
this.size = size;
}
addPepperoni() {
this.pepperoni = true;
return this;
}
addLettuce() {
this.lettuce = true;
return this;
}
addCheeze() {
this.cheeze = true;
return this;
}
addTomato() {
this.tomato = true;
return this;
}
build() {
return new Burger(this);
}
}
最后我们可以这样使用
const burger = (new BurgerBuilder(14))
.addPepperoni()
.addLettuce()
.addTomato()
.build();
什么时候使用
当一个对象可能有多种风格并避免构造函数伸缩时。 与工厂模式的主要区别在于,当创建是一步过程时使用工厂模式,而当创建是多步骤过程时使用构建器模式。
TIPS
我们在写程序代码的时候,需要去注意函数的传参不宜过多,它有两个好处
- 它会使你的代码看上去不会那么混乱,因为参数只有1个
- 你不需要去管参数的顺序问题,因为我们可以从对象中解构出相应的参数
const burger = new Burger({
size : 14,
pepperoni : true,
cheeze : false,
lettuce : true,
tomato : true
})
// 而不是使用
const burger = new Burger(14, true, false, true, true)
单例模式
真实的案例
想象一下,在我们经常的程序设计中,是不是会经常在一个文件中,对于方法的取名相当头疼,因为我们要避免方法的重名,所以我们需要提供一个命名空间,主要目的就是为了避免重复的变量名定义。而单例模式常用来定义命名空间
简而言之
确保只创建特定类的一个对象
维基百科
In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system.
在软件工程中,单例模式是一种软件设计模式,它将类的实例化限制为一个对象。 当只需要一个对象来协调整个系统的操作时,这很有用。
编程案例
首先我们有 Friend 构造函数
class Friend {
constructor() {
this.girlFriend = false;
}
haveGirlFriend() {
if (this.girlFriend) {
return 'have girlFriend';
}
this.girlFriend = true;
return 'have girlFriend';
}
noGirlFriend() {
if (!this.girlFriend) {
return 'no girlFriend';
}
this.girlFriend = false;
return 'no girlFriend';
}
}
然后就是实现一个实例,如果有就返回已有实例,否则返回新创建的 Friend 实例
Friend.getInstance = (function () {
let instance;
return function () {
if (!instance) {
instance = new Friend();
}
return instance;
};
})();
最后我们可以这样使用
const friend1 = Friend.getInstance();
friend1.haveGirlFriend();
const friend2 = Friend.getInstance();
friend2.noGirlFriend();
console.log(friend1 === friend2); // true
什么时候使用
定义命名空间和减少全局变量的时候可以进行使用,但是不利于单元测试,因为都糅合在了一起
参考资料
- Javascript 设计模式
- 《Javascript设计模式——张容铭》