js设计模式[es6]
先说说名字
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
这么多枯燥的名字 在我们JS中使用的我先介绍个10种吧。
再说说原则
总原则:开闭原则(Open Close Principle)
大家都用过USB外设吧,外设有很多种比如鼠标,充电器,小风扇,U盘,那么对于USB内部的实现其实我们是不知道的,但是我们只要按照他提供的抽象的USB接口就可以去生产我们不同的设备。这就是对外您随便,对内不好意思不让您看。
1、单一职责原则
这个和现在的函数式编程 和模块化编程的思想有些像,也就是说一个函数 现在ES6中可以说是Class 只做单独的一类事情 ,这个力度可以自己把控,只要不影响其他模块那就是对的。
2、里氏替换原则(Liskov Substitution Principle)
JS中没有方法重载这一说(也就是方法名称一样 参数不同可以视为不同方法)JS中实现呢基本是更具参数的类型不同 然后 走不同的逻辑实现分支。所以JS中这个原则其实不是那么的强
这里我也不会描述了,引用别人的一句话 历史替换原则中,子类对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。
总结一句话 —— 就是尽量不要从可实例化的父类中继承,而是要使用基于抽象类和接口的继承。
其他原则
写到这里其实大家还有总结了一些 依赖倒转原则(Dependence Inversion Principle),接口隔离原则(Interface Segregation Principle),迪米特法则(最少知道原则)(Demeter Principle),合成复用原则(Composite Reuse Principle),总结下这些原则其实还是总原则的一些完善,主要就是面向接口编程和面向抽象编程的思想。这里就不引导大家学习这些了,有兴趣的同学可以百度一下这些原则。
💐闲话到这里为止,我们来看看实际的代码吧。
创建型模式
简单工厂模式(Simple Factory)4
抽象工厂模式(Abstract Factory)5
建造者模式(Builder)2
单例模式(Singleton)3
1 单例模式
var single = (function() {
var temp;
function getInstance(name) {
if (temp === undefined) {
temp = new Temp(name);
}
return temp;
}
function Temp(name) {
this.name = name;
}
return {
getInstance: getInstance
}
})();
single.getInstance("kobe")
//输出Temp {name: "kobe"}
single.getInstance("jay")
//输出Temp {name: "kobe"}
单例模式也就是说一个对象的实例在系统中只能存在一个,这样做的好处就是可以控制核心对象在系统中的唯一性,但是由于闭包的特性,他的GC回收不及时。
1 简单工厂模式
让我们先看看一段代码
class ButtonFactory{
constructor(name,color){
this.name = name;
this.color = color;
}
getButton(){
let btn = document.createElement("button");
btn.innerText = this.name;
btn.style.backgroundColor=this.color;
btn.style.minWidth="100px"
return btn;
}
}
然后在看看进阶版本
class Buttons {
constructor(name, color, cf) {
this.name = name;
this.color = color;
}
createButton(cf) {
let btn = document.createElement("button");
btn.style.backgroundColor = this.color;
btn.style.minWidth = "100px"
btn.onclick=cf;
btn.innerText=this.name;
return btn;
}
}
class ButtonBlue extends Buttons {
constructor(cf) {
super();
this.name="我是蓝色按钮";
this.color="blue";
}
}
class ButtonRed extends Buttons {
constructor(cf) {
super();
this.name="我是红色按钮";
this.color="red";
}
}
class ButtonFactory {
constructor(type) {
this.type = type;
}
getButton(cf) {
let btn;
switch(this.type) {
case "blue":
btn= new ButtonBlue().createButton(cf);
break;
case "red":
btn= new ButtonRed().createButton(cf);
break;
}
return btn;
}
}
第一种写法就是单纯的产生一个按钮的实例,既然是工厂就应该生产多元化的按钮,那么我们经过改进以后的代码灵活性和实用性就要高与第一种写法,但是这种生产按钮的工厂还是不能满足我们多元的需求,那么我们就需要下一种模式
抽象工厂模式
还是老样子 先撸一段代码
const CXMONKEY = {
MONKEY: function(name) {
return new Monkey(name)
}
}
const CXSHIP = {
MONKEY: function(name) {
return new Ship(name)
}
}
const FA = {
MF: function() {
return new MonkeyFactory()
},
SP: function() {
return new ShipFactory()
}
}
//抽象方法
class IAnmial {
say() {
throw "我是抽象方法"
}
}
//抽象类
class Anmial extends IAnmial {
constructor(name) {
super();
this.name = name;
}
eat() {
console.log(this.name + "吃东西")
}
anmial(type, name, factory) {
return FA[factory]().getAnmial(type, name)
}
}
//抽象工厂
class AbsAnmialFactory {
cerateAnmial(type, name, factory) {
return new Anmial().anmial(type, name, factory);
}
}
//实例工厂
class MonkeyFactory {
getAnmial(type, name) {
return CXMONKEY[type](name);
}
}
//实例工厂
class ShipFactory {
getAnmial(type, name) {
return CXSHIP[type](name);
}
}
//实例产品
class Monkey extends Anmial {
constructor(name) {
super(name);
}
say() {
console.log("我是CX猴工厂出来的:" + this.name)
}
}
//实例产品
class Ship extends Anmial {
constructor(name) {
super(name);
}
say() {
console.log("我是CX羊工厂出来的:" + this.name)
}
}
//调用方式
new AbsAnmialFactory().cerateAnmial('MONKEY', "猴子", "SP").say();
这段代码解释起来还是有点绕口,简单说抽象工厂就是生产抽象的产品的 在实例化的过程中可以指定 是有哪个实例工厂 生产那种产品。那么同样是汽车,可能是德国制造也可以是 中国制造。这样的好处就是可以多种组合但是不用改变代码。
看看这个实际的定义 结合代码理解一下
多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。
一个抽象工厂类,可以派生出多个具体工厂类。
每个具体工厂类可以创建多个具体产品类的实例,也就是创建的是一个产品线下的多个产品。
建造者模式
来瞅瞅代码
class Car {
constructor(engine,tyre){
this.engine = engine;
this.tyre = tyre;
}
}
class Engineer {
makeCar(factory){
return new Car(factory.getEngine(), factory.getTyre());
}
}
class Factory { //这个工厂不是工厂模式
getEngine() {
console.log("引擎组装完毕");
return "我是动力"
}
getTyre() {
console.log("轮胎组装完毕");
return "我是轮胎"
}
};
var engineer = new Engineer();
var car = engineer.makeCar(new Factory())
console.log(car)
建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。建造者模式属于对象创建型模式。根据中文翻译的不同,建造者模式又可以称为生成器模式
结合代码我们看看,我们的汽车由轮胎和引擎组成但是呢我们需要一个工程师来组装他 ,但是他怎么组装的我们并不知道,最后返回一个汽车出来就好了。
结构型模式
适配器模式
桥接模式
装饰模式
外观模式
享元模式
代理模式
适配器模式
将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。
class MediaPlayer{
play(audioType,fileName){
throw "我是抽象的播放方法 没有具体实现"
}
}
class AdvancedMediaPlayer{
playVlc(fileName){}
playMp4(fileName){}
}
class VlcPlayer extends AdvancedMediaPlayer{
playVlc(fileName){
console.log(`我是Vlc播放器文件名称是${fileName}`)
}
playMp4(fileName){}
}
class Mp4Player extends AdvancedMediaPlayer{
playVlc(fileName){}
playMp4(fileName){
console.log(`我是Mp4播放器文件名称是${fileName}`)
}
}
class MediaAdapter extends MediaPlayer{
play(audioType,fileName){
if(audioType=="vlc")new VlcPlayer().playVlc(fileName);
if(audioType=="mp4")new Mp4Player().playMp4(fileName);
}
}
class AudioPlayer extends MediaPlayer{
play(audioType,fileName){
if(audioType==="mp3"){
console.log(`我是mp3播放器文件名称是${fileName}`)
}else{
return new MediaAdapter().play(audioType,fileName);
}
}
}
var audioPlayer = new AudioPlayer();
audioPlayer.play("mp3", "beyond the horizon.mp3");
audioPlayer.play("mp4", "alone.mp4");
audioPlayer.play("vlc", "far far away.vlc");
我们有一个播放器,他有基本的播放功能。我们不想他只能播放MP3 所以我们可以用一个适配器转换一下,让他拥有更多的功能。
桥接模式(Bridge Pattern)
将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式
class DrawAPI {
drawCircle(radius,x, y){
throw "抽象方法"
}
}
class RedCircle extends DrawAPI {
drawCircle( radius, x, y) {
console.log("Drawing Circle[ color: red, radius: " + radius + ", x: " + x + ", " + y + "]");
}
}
class GreenCircle extends DrawAPI {
drawCircle( radius, x, y) {
console.log("Drawing Circle[ color: green, radius: " + radius + ", x: " + x + ", " + y + "]");
}
}
class Shape {
constructor(drawAPI) {
this.drawAPI = drawAPI;
}
draw(){
this.drawAPI.drawCircle(this.x,this.y,this.radius)
}
}
class Circle extends Shape{
constructor(x, y, radius,drawAPI){
super(drawAPI);
this.x = x;
this.y = y;
this.radius = radius;
}
}
var redCircle = new Circle(100,1000, 10, new RedCircle());
var greenCircle = new Circle(100,100, 10, new GreenCircle());
redCircle.draw();
greenCircle.draw();
我们现在有一个画图形的类这个类可以生成一个圈,为了可以画不同种类的圈我们可以吧画圈的实现类传递进去,这样就实现了我们的多样化可扩展了。如果我们想画一个其他类型的圈我们可以继承抽象方法,然后去实现他 这样就可以在实例化图形类的时候 把我们新的实现类传递进去了,这有点LSP的味道哦。
装饰模式 Decorator Pattern
动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比生成子类实现更为灵活。其别名也可以称为包装器(Wrapper),与适配器模式的别名相同,但它们适用于不同的场合。
function decorateArmour(target, key, descriptor) {
const method = descriptor.value;
let moreDef = 100;
let ret;
descriptor.value = (...args)=>{
args[0] += moreDef;
ret = method.apply(target, args);
return ret;
}
return descriptor;
}
class Man{
constructor(def = 2,atk = 3,hp = 3){
this.init(def,atk,hp);
}
//ES7的装饰器
@decorateArmour
init(def,atk,hp){
this.def = def; // 防御值
this.atk = atk; // 攻击力
this.hp = hp; // 血量
}
toString(){
return `防御力:${this.def},攻击力:${this.atk},血量:${this.hp}`;
}
}
var tony = new Man();
console.log(`当前状态 ===> ${tony}`);
// 输出:当前状态 ===> 防御力:102,攻击力:3,血量:3
看看我们的超人,本来的基础防御力是2 我们可以通过装饰器方法让他防御变成102
下面我们来说说ES7的装饰器
它的主要作用与装饰者模式类似,是给一个已有的方法或类扩展一些新的行为,而不是去直接修改它本身
外观模式(Facade Pattern)
外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用
class FaceShape {
draw() {
throw "定义抽象方法"
}
}
class Rectangle extends FaceShape {
draw() {
console.log("Rectangle::draw()")
}
}
class Square extends FaceShape {
draw() {
console.log("Square::draw()")
}
}
class FCircle extends FaceShape {
draw() {
console.log("Circle::draw()")
}
}
class ShapeMaker {
constructor() {
this.circle = new FCircle();
this.rectangle = new Rectangle();
this.square = new Square();
}
drawCircle() {
this.circle.draw();
}
drawRectangle() {
this.rectangle.draw();
}
drawSquare() {
this.square.draw();
}
}
var shapeMaker = new ShapeMaker();
shapeMaker.drawCircle();
shapeMaker.drawRectangle();
shapeMaker.drawSquare();
首先是有一个抽象接口,所有的实现类都需要实现这个抽象接口,然后通过一个大对象暴露出来,让开发人员也很容易记住通过某个对象就可以完成很多事情。
享元模式(Flyweight Pattern)
运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。
class Monster{
constructor(name) {
this.name = name;
}
sayName(){
console.log(`我的名字是:${this.name}`)
return this;
}
}
class MonsterFactory extends Monster{
createMonster(name){
return new Monster(name)
}
}
class GetMonster{
getM(num){
let monsters=[];
let names=["a","b","2","1","e","f","g"];
let name="";
for (let i = num; i>=0;i--) {
name=names[parseInt(Math.random()*7,10)].repeat(2)+names[parseInt(Math.random()*7,10)];
monsters.push(new MonsterFactory().createMonster(name).sayName() )
}
return monsters;
}
}
console.log(new GetMonster().getM(10))
现在需要做一个小游戏,游戏里面有10个或者100个怪物,我们可以在使用的时候去生成怪物,也可以在游戏加载的初始化中实例化100个怪物,并且把怪物保存在数组中,但是其实屏幕上只存在3个怪物,总计有100个怪物那么 我们产生100个怪物就有些浪费了,所以我们修改下上述代码 让他变成享元模式
class GetMonster{
getM(num){
let monsters={};
let names=["a","b","2","1","e","f","g"];
let name="";
for (let i = num; i>=0;i--) {
name=names[parseInt(Math.random()*7,10)].repeat(2)+names[parseInt(Math.random()*7,10)];
if(!monsters[name]){
monsters[name]=new MonsterFactory().createMonster(name).sayName()
}
}
return monsters;
}
}
console.log(new GetMonster().getM(20))
我们通过对象的key来确定已经有的key不用添加使用的时候就可以复用这个对象了
代理模式(Proxy Pattern)
通过引入一个新的对象(如小图片和远程代理 对象)来实现对真实对象的操作或者将新的对 象作为真实对象的一个替身,这种实现机制即 为代理模式,通过引入代理对象来间接访问一 个对象,这就是代理模式的模式动机
class Images{
addendImage(src){
let imgNode = document.createElement('img');
document.body.appendChild(imgNode);
imgNode.src = src;
return imgNode;
}
}
class proxyImage {
addendImage(src){
let imgNode = new Images().addendImage("./img/test.gif");
let image = new Image();
image.src = src;
image.onload=function(){
imgNode.src =src;
}
}
}
new proxyImage().addendImage('http://ww3.sinaimg.cn/mw690/0064cTs2jw1ey2ptjq6aoj30sg0lc12j.jpg');
new proxyImage().addendImage('http://ww1.sinaimg.cn/mw690/0064cTs2jw1ey2pthhfiyj30p00dwgtt.jpg');
new proxyImage().addendImage('http://ww4.sinaimg.cn/mw690/0064cTs2jw1ey2ptmcfa6j30sg0lcan2.jpg');
我们可以先展示本地的一个默认图片,等远程图片返回了在显示真实的图片。
先写到这里 下一期接着写观察者模式和中介者模式 以及一些源码分析