js设计模式(三)-结构型模式(桥接模式/外观模式/享元模式/适配器模式)
结构型设计模式介绍
结构型模式所描述的是如何将类和对象结合在一起来形成一个更大的结构,它描述两种不同的事物:类和对象,根据这一点,可分为类结构型和对象结构型模式。类结构型模式关心类的组合,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系;对象结构型模式关心类与对象的组合,通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法。
桥接模式
桥接模式:将抽象部分与它的实现部分分离,使它们都可以独立地变化。使用组合关系代替继承关系,降低抽象和实现两个可变维度的耦合度。
优点:
- 分离了抽象和实现部分,将实现层(DOM 元素事件触发并执行具体修改逻辑)和抽象层( 元素外观、尺寸部分的修改函数)解耦,有利于分层;
- 提高了可扩展性,多个维度的部件自由组合,避免了类继承带来的强耦合关系,也减少了部件类的数量; 使用者不用关心细节的实现,可以方便快捷地进行使用;
缺点:
- 桥接模式要求两个部件没有耦合关系,否则无法独立地变化,因此要求正确的对系统变化的维度进行识别,使用范围存在局限性;
- 桥接模式的引入增加了系统复杂度;
桥接模式的适用场景:
- 如果产品的部件有独立的变化维度,可以考虑桥接模式;
- 不希望使用继承,或因为多层次继承导致系统类的个数急剧增加的系统;
- 产品部件的粒度越细,部件复用的必要性越大,可以考虑桥接模式;
这个怎么说呢,我理解就是基于建造者模式又进行了一次精确的拆分。为什么呢,建造者模式将一个大业务拆分成了不同的步骤,而桥接模式相当于把一个大业务的步骤实例化成不同的对象。
一个简单的例子:
class Car {
constructor(instance) {
this.instance = instance;
}
buildCar() {
this.instance.show();
}
}
class Track {
constructor(instance) {
this.instance = instance;
}
buildCar() {
this.instance.show();
}
}
class Part1 {
constructor(part) {
this.part = part;
}
show() {
console.log("Part1.show : ", this.part);
}
}
class Part2 {
constructor(part) {
this.part = part;
}
show() {
console.log("Part2.show : ", this.part);
}
}
const car = new Car(new Part1("part1111!"));
car.buildCar("123123");
B站视频里面的老师说了一个场景:不同组件使用不同的动画,可以使用方式就是桥接方式。
外观模式
外观模式为一组复杂的子系统接口提供一个更高级的统一接口,通过这个接口使得对子系统接口的访问更容易。
外观模式的用途:将一些复杂操作封装起来,并创建一个简单的接口用于调用。
外观模式的适用场景:
- 维护设计粗糙和难以理解的遗留系统,或者系统非常复杂的时候,可以为这些系统设置外观模块,给外界提供清晰的接口,以后新系统只需与外观交互即可;
- 你写了若干小模块,可以完成某个大功能,但日后常用的是大功能,可以使用外观来提供大功能,因为外界也不需要了解小模块的功能;
- 团队协作时,可以给各自负责的模块建立合适的外观,以简化使用,节约沟通时间;
- 如果构建多层系统,可以使用外观模式来将系统分层,让外观模块成为每层的入口,简化层间调用,松散层间耦合;
优点:
- 访问者不需要再了解子系统内部模块的功能,而只需和外观交互即可,使得访问者对子系统的 使用变得简单 ,符合最少知识原则,增强了可移植性和可读性;
- 减少了与子系统模块的直接引用,实现了访问者与子系统中模块之间的松耦合,增加了可维护性和可扩展性;
- 通过合理使用外观模式,可以帮助我们更好地划分系统访问层次,比如把需要暴露给外部的功能集中到外观中,这样既方便访问者使用,也很好地隐藏了内部的细节,提升了安全性; 缺点:
- 不符合开闭原则,对修改关闭,对扩展开放,如果外观模块出错,那么只能通过修改的方式来解决问题,因为外观模块是子系统的唯一出口;
- 不需要或不合理的使用外观会让人迷惑,过犹不及;
外观模式与中介者模式的区别:
- 外观模式:封装子使用者对子系统内模块的直接交互,方便使用者对子系统的调用;
- 中介者模式:封装子系统间各模块之间的直接交互,松散模块间的耦合;
这个一直在用,但是一直没有意识到这居然是一个模式 ·_·!
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
);
}
setConfig(config) {
if (MessageClass.instance) {
MessageClass.instance.config = Object.assign(
MessageClass.instance.config,
config
);
}
}
}
const useMessage = (config) => {
const msg = new MessageClass(config);
return {
_setTimeOut: (num) => msg.setConfig({ timeOut: num }),
};
};
const $message = new MessageClass({ timeOut: 1111 });
console.log($message);
useMessage()._setTimeOut(9999);
console.log($message);
这个例子我粘贴了一下前面的单例模式,单例模式中我们已经知道一旦实例化 MessageClass 事件,如果想修改基础配置怎么办呢?
如代码所示,声明了一个 useMessage 的方法,返回了一个对象,并调用了一下 MessageClass 的 setConfig 方法。并执行一次,就改变了单例模式创建的对象。
享元模式
享元模式:运用共享技术来有效地支持大量细粒度对象的复用,以减少创建的对象的数量。通俗来讲,享元就是共享单元,比如现在流行的共享单车、共享充电宝等,他们的核心理念都是享元模式。
享元模式适用于以下场景:
程序中使用大量的相似对象,造成很大的内存开销 对象的大多数状态都可以变为外部状态,剥离外部状态之后,可以用相对较少的共享对象取代大量对象。
优点:
- 由于减少了系统中的对象数量,提高了程序运行效率和性能,精简了内存占用,加快运行速度;
- 外部状态相对独立,不会影响到内部状态,所以享元对象能够在不同的环境被共享;
缺点:
- 引入了共享对象,使对象结构变得复杂;
- 共享对象的创建、销毁等需要维护,带来额外的复杂度(如果需要把共享对象维护起来的话);
我感觉享元模式才把结构运用的更加合理,但是如果让我在项目中灵活使用,还真的做不到,毕竟会感觉用起来很麻烦:
// Flyweight optimized version
var Book = function ( title, author, genre, pageCount, publisherID, ISBN ) {
this.title = title;
this.author = author;
this.genre = genre;
this.pageCount = pageCount;
this.publisherID = publisherID;
this.ISBN = ISBN;
};
// Book Factory singleton
var BookFactory = (function () {
var existingBooks = {}, existingBook;
return {
createBook: function ( title, author, genre, pageCount, publisherID, ISBN ) {
// Find out if a particular book meta-data combination has been created before
// !! or (bang bang) forces a boolean to be returned
existingBook = existingBooks[ISBN];
if ( !!existingBook ) { return existingBook; }
else {
// if not, let's create a new instance of the book and store it
var book = new Book( title, author, genre, pageCount, publisherID, ISBN );
existingBooks[ISBN] = book; return book;
}
}
};
});
// BookRecordManager singleton
var BookRecordManager = (function () {
var bookRecordDatabase = {};
return {
// add a new book into the library system
addBookRecord: function ( id, title, author, genre, pageCount, publisherID, ISBN, checkoutDate, checkoutMember, dueReturnDate, availability ) {
var book = bookFactory.createBook( title, author, genre, pageCount, publisherID, ISBN );
bookRecordDatabase[id] = {
checkoutMember: checkoutMember,
checkoutDate: checkoutDate,
dueReturnDate: dueReturnDate,
availability: availability,
book: book
};
},
updateCheckoutStatus: function ( bookID, newStatus, checkoutDate, checkoutMember, newReturnDate ) {
var record = bookRecordDatabase[bookID];
record.availability = newStatus;
record.checkoutDate = checkoutDate;
record.checkoutMember = checkoutMember;
record.dueReturnDate = newReturnDate;
},
extendCheckoutPeriod: function ( bookID, newReturnDate ) {
bookRecordDatabase[bookID].dueReturnDate = newReturnDate;
},
isPastDue: function ( bookID ) {
var currentDate = new Date();
return currentDate.getTime() > Date.parse( bookRecordDatabase[bookID].dueReturnDate);
}
};
});
适配器模式
适配器模式:将一个类(对象)的接口(方法、属性)转化为用户需要的另一个接口。解决类(对象)之间接口不兼容的问题。
优点:
- 已有的功能如果只是接口不兼容,使用适配器适配已有功能,可以使原有逻辑得到更好的复用,有助于避免大规模改写现有代码;
- 可扩展性良好,在实现适配器功能的时候,可以调用自己开发的功能,从而方便地扩展系统的功能; 灵活性好,因为适配器并没有对原有对象的功能有所影响,如果不想使用适配器了,那么直接删掉即可,不会对使用原有对象的代码有影响;
缺点:
- 会让系统变得零乱,明明调用 A,却被适配到了 B,如果系统中这样的情况很多,那么对可阅读性不太友好。如果没必要使用适配器模式的话,可以考虑重构,如果使用的话,可以考虑尽量把文档完善。
适配器模式的适用场景:
- 当你想用已有对象的功能,却想修改它的接口时,一般可以考虑一下是不是可以应用适配器模式。
- 如果你想要使用一个已经存在的对象,但是它的接口不满足需求,那么可以使用适配器模式,把已有的实现转换成你需要的接口;
- 如果你想创建一个可以复用的对象,而且确定需要和一些不兼容的对象一起工作,这种情况可以使用适配器模式,然后需要什么就适配什么;
放一个最简单的例子:
[
{
"day": "周一",
"uv": 6300
},
{
"day": "周二",
"uv": 7100
}, {
"day": "周三",
"uv": 4300
}, {
"day": "周四",
"uv": 3300
}, {
"day": "周五",
"uv": 8300
}, {
"day": "周六",
"uv": 9300
}, {
"day": "周日",
"uv": 11300
}
]
["周二", "周二", "周三", "周四", "周五", "周六", "周日"] //x轴的数据
[6300. 7100, 4300, 3300, 8300, 9300, 11300] //坐标点的数据
// x轴适配器
function echartXAxisAdapter(res) {
return res.map(item => item.day);
}
// 坐标点适配器
function echartDataAdapter(res) {
return res.map(item => item.uv);
}
参考文档:
转载自:https://juejin.cn/post/7176090557347790906