js设计模式(二)-创建型模式
创建型设计模式介绍
在软件工程中,创建型模式是处理对象创建的设计模式,试图根据实际情况使用合适的方式创建对象。基本的对象创建方式可能会导致设计上的问题,或增加设计的复杂度。创建型模式通过以某种方式控制对象的创建来解决问题。
创建型模式由两个主导思想构成。一是将系统使用的具体类封装起来,二是隐藏这些具体类的实例创建和结合的方式。
创建型模式又分为对象创建型模式和类创建型模式。对象创建型模式处理对象的创建,类创建型模式处理类的创建。详细地说,对象创建型模式把对象创建的一部分推迟到另一个对象中,而类创建型模式将它对象的创建推迟到子类中。
原型模式
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
如果使用原型模式,我们只需要调用负责克隆的方法,便能完成同样的功能。 原型模式的实现关键,是语言本身是否提供了clone方法。ECMAScript 5提供了Object.create方法,可以用来克隆对象。
const Car = function () {
this.color = "red";
this.brand = "大众";
this.mileage = 10000;
};
const car = new Car();
console.log("car:before", car);
// car:before Car { color: 'red', brand: '大众', mileage: 10000 }
car.color = "blue";
car.brand = "BMW";
car.mileage = 999;
car.sayHello = () => console.log("sayHello Func => ", "hello,world!");
console.log("car:after", car);
// car:after Car { color: 'blue', brand: 'BWM', mileage: 999 }
const bentch = Object.create(car);
console.log("bentch:", bentch);
console.log("bentch.color:", bentch.color);
console.log("bentch.brand:", bentch.brand);
console.log("bentch.mileage:", bentch.mileage);
// bentch: Car {}
// bentch.color: blue
// bentch.brand: BWM
// bentch.mileage: 999
bentch.sayHello();
// sayHello Func => hello,world!
说实在的,原型模式看了好多人在介绍,并没有一个很清晰的认知。只是认识到了这两点:
- 可以这么理解,所谓原型是依赖于使用场景的,它 使用到了 Object.create 方法去克隆了已经存在的对象,并对原来的数据进行改动的时候不会对原来的数据造成影响。
- 感觉这种方法非常简单,适用于比较简单的场景,能迅速的“实例化”一个新的对象出来。
单例模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
class MessageClass {
constructor(config) {
if (!MessageClass.instance) {
const baseConfig = {
color: "red",
timeOut: 3000,
};
this.config = Object.assign(baseConfig, config);
MessageClass.instance = this;
}
return MessageClass.instance;
}
success(msg) {
this.config.color = "green";
this.msg = msg;
this.print("success", msg);
}
error(msg) {
this.config.color = "red";
this.msg = msg;
this.print("error", msg);
}
print(type) {
console.log(
type,
"::",
"color=",
this.config.color,
"msg=",
this.msg,
"timeOut=",
this.config.timeOut
);
}
}
const Message = new MessageClass({ timeOut: 5000 });
const Message2 = new MessageClass({ timeOut: 8000 });
Message.success("成功消息!");
Message.error("错误消息!");
Message2.success("成功消息!");
Message2.error("错误消息!");
console.log("Message === Message2 : ", Message === Message2);
如上所示,我实现了一个 Message 组件,类似 ElementUI 里面的 message 组件的简化版本。
这个无疑是最能诠释单例模式的一个常见的场景了,如打印结果所示,第二次实例化 MessageClass 的时候,入参config并没有生效,这是因为,第一次已经将 intence 创建出来了,以后再也不会走 constroctor 里面的逻辑了,想要修改config,就需要再暴露一个方法去 setConfig 了。
为什么要 Message 要用单例模式呢?因为要保证 每个 Message 不会互相影响,这就要在同一个地址把所有的打印信息放在一起,然后统一去实现动画效果。
其他的使用场景主要是 single-spa 的场景下,例如 vue-router,vuex,dialog,confirm ...
工厂模式
工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
class User {
constructor(role, pages) {
this.role = role;
this.pages = pages;
}
static UserFactory(role) {
switch (role) {
case "superadmin":
return new User(role, ["home", "user-manage", "news-manage"]);
break;
case "admin":
return new User(role, ["home", "news-manage"]);
break;
case "user":
return new User(role, ["home"]);
break;
default:
throw "参数错误!";
break;
}
}
}
const sadmin = User.UserFactory("superadmin");
const admin = User.UserFactory("admin");
const user = User.UserFactory("user");
console.log(sadmin);
console.log(admin);
console.log(user);
const err = User.UserFactory("error");
如上所示,是从 B 站视频上的一个例子,用来鉴权所使用的。其实这个逻辑用在这里有点牵强。
但是用来理解套路是够了,就是根据实例化的时候的参数不同,返回不同的对象实例。
抽象工厂模式
抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。
这个就不粘代码了,工厂模式中可以看到,最终返回的是一个实例后的对象,而抽象工厂模式是使用一个公共的factory方法返回class原型。使用方法也多了一步:
const UserClass = userClassFactory('superadmin')
let user = new UserClass('张三')
建造者模式(生成器模式)
建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。
我看了一下相关的介绍,这个模式的使用场景不是很常见,但是有个例子还是很容易理解的:
// 类的链式调用
class CarBuilder {
constructor(name) {
this.name = name;
}
buildPart1(params) {
this.part1 = params;
return this;
}
buildPart2(params) {
this.part2 = params;
}
}
const bentch = new CarBuilder("bentch");
bentch.buildPart1({ color: "red" }).buildPart2({ color: "blue" });
console.log(bentch);
如上所示,如果有一个按顺序实现的比较复杂的需求,例如一个多层循环的tab结构,就需要先实现头部,然后绑定头部事件,按照头部信息触发点击事件切换内容。
同样,链式调用是最直观的方法,其实正常的逻辑会有一个组装方法,把各个模块分开后由 组装方法 build 在一起。
后记
其实,本文的几段代码都是为了演示设计模式而专门杜撰的,说句不好听的,就是为了用而用。
而实际开发中,一般都是多个模式混合着使用,学习这些模式必须学习到其核心原理,而不是要背会这些代码。
简单总结一下:
- 原型模式:基于语言的原生语法,进行新的对象的创建和构建,适用于比较灵活的场景;
- 单例模式:整个页面中涉及到的所有实例,都来自于同一个,适用于来自于同一个模型的多个不同实例,但是需要统一管理所有的实例的场景下;
- 工厂模式:由一个入口,返回多个同类型的实例对象,适用于同属大类但是又有些许区别的的场景;
- 抽象工厂模式:在工厂模式的基础上,同一个入口返回差别比较大的实例对象原型,适用于比较复杂的一对多的创建场景;
- 建造者模式:将整个大业务进行拆解,进行依次组装,适用于流程比较复杂且有前后依赖关系的场景。
参考文档
转载自:https://juejin.cn/post/7175817588323123256