JavaScript设计模式之结构型模式

337 阅读8分钟

Github 源码地址

适配器模式

简介

  • 解决:将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的
  • 使用:系统需要使用现有的类,而此类的接口不符合系统的需要;通过接口转换,将一个类插入另一个类系中。
  • 方式:适配器继承或依赖已有的对象,实现想要的目标接口。
  • 场景:有动机地修改一个正常运行的系统的接口。

优缺点

  • 优点:
    1. 可以让任何两个没有关联的类一起运行。
    2. 提高了类的复用。
    3. 增加了类的透明度。
    4. 灵活性好。
  • 缺点:
    1. 过多地使用适配器,会让系统非常零乱,不易整体进行把握。

举例

  • 你想要audio(音频播放器)同时播放音频格式(mp3)和视频格式(mp4)的文件。
class MediaPlayer {
    play() {
        console.log('play mp4');
    }
}

// 适配器
class MediaAdapter {
    play() {
        new MediaPlayer().play();
    }
}
class AudioPlayer {
    play(type) {
        if (type === 'mp3') {
            console.log('play mp3');
        } else if (type === 'mp4') {
            new MediaAdapter().play();
        } else {
            console.log("Invalid media. " + type + " format not supported");
        }
    }
}

function demo() {
    const audio = new AudioPlayer();
    audio.play('mp3');
    audio.play('mp4');
    audio.play('avi');
}
demo();

image.png

桥接模式

简介

  • 解决:在有多种可能会变化的情况下,用继承会造成类爆炸问题,扩展起来不灵活。
  • 使用:实现系统可能有多个角度分类,每一种角度都可能变化。
  • 方式:把这种多角度分类分离出来,让它们独立变化,减少它们之间耦合。
  • 场景:抽象化角色和具体化角色之间增加更多的灵活性;可以自由组合想要的内容。

优缺点

  • 优点:
    1. 抽象和实现的分离。
    2. 优秀的扩展能力。
    3. 实现细节对客户透明。
  • 缺点:桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。

举例

  • 我们想要画一个不同颜色、大小的圆。
class DrawAPI {
    drawCircle(radius, x, y) {
        console.log('drawCircle');
    }
}

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() {
        console.log('draw shape!');
    }
}

class Circle extends Shape {
    constructor(x, y, radius, drawApi) {
        super(drawApi);
        this.x = x;
        this.y = y;
        this.radius = radius;
    }
    draw() {
        this.drawApi.drawCircle(this.radius, this.x, this.y);
    }
}

function demo() {
    const redCircle = new Circle(10, 10, 10, new RedCircle());
    const greenCircle = new Circle(20, 20, 20, new GreenCircle());
    redCircle.draw();
    greenCircle.draw();
}

demo();

image.png

组合模式

简介

  • 解决:将对象组合成树形结构以表示"部分-整体"的层次结构。
  • 使用:表示对象的部分-整体层次结构(树形结构);希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
  • 方式:树枝和叶子实现统一接口,树枝内部组合该接口。
  • 场景:部分、整体场景,如树形菜单,文件、文件夹的管理。

优缺点

  • 优点:
    1. 高层模块调用简单。
    2. 节点自由增加。
  • 缺点:使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。

举例

  • 公司有多个部门,每个部门有多个员工。
class Employee {
    constructor(dept, name, age) {
        this.dept = dept;
        this.name = name;
        this.age = age;
        this.adptEmployeeList = {};
    }
    add(employee) {
        this.adptEmployeeList[employee.name] = employee;
    }
    remove(employee) {
        delete this.adptEmployeeList[employee.name]
    }

    getList() {
        return this.adptEmployeeList
    }
    toEmployeeString() {
        return ("Employee :[ Name : " + this.name + ", dept : " + this.dept + ", age :" + this.age + " ]");
    }
}
function demo() {
    const CEO = new Employee('ceo', '小小小十七', 25);
    const headerHR = new Employee('HR', '小十七', 20);
    const headerSales = new Employee('sales', '十七', 31);
    const hr1 = new Employee('ceo', '七', 20);
    const hr2 = new Employee('ceo', '五六七', 19);
    const sales1 = new Employee('ceo', '小十', 28);
    const sales2 = new Employee('ceo', '小九', 30);
    CEO.add(headerHR);
    CEO.add(headerSales);
    headerHR.add(hr1);
    headerHR.add(hr2);
    headerHR.remove(hr1);
    headerSales.add(sales1);
    headerSales.add(sales2);
    console.log('ceo', CEO);
}
demo();

image.png

装饰器模式

简介

  • 解决:动态地给一个对象添加一些额外的职责。(为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。)
  • 使用:在不想增加很多子类的情况下扩展类。
  • 方式:将具体功能职责划分,同时继承装饰者模式。
  • 场景:扩展一个类的功能;动态增加功能,动态撤销。

优缺点

  • 优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
  • 缺点:多层装饰比较复杂。

举例

  • 画相同颜色不同形状的图形。
class Shape {
    draw() {
        console.log('draw shape.');
    }
}
class Circle extends Shape {
    draw() {
        console.log('draw circle.');
    }
}

class Rectangle extends Shape {
    draw() {
        console.log('draw rectangle.');
    }
}

class ShapeDecorator {
    constructor(shape) {
        this.shape = shape
    }
    draw() {
        this.shape.draw();
    }
}
class RedShapeDecorator extends ShapeDecorator {
    constructor(shape) {
        super(shape)
    }
    draw() {
        this.shape.draw();
        this.setRedColor(this.shape)
    }
    setRedColor() {
        console.log('add red color');
    }
}
function demo() {
    const circle = new Circle();
    const redCircle = new RedShapeDecorator(new Circle());
    const redRectangle = new RedShapeDecorator(new Rectangle());
    circle.draw();
    console.log('--------red circle--------');
    redCircle.draw();
    console.log('--------red rectangle--------');
    redRectangle.draw();
}

demo();

image.png

外观模式

简介

  • 解决:降低访问复杂系统的内部子系统时的复杂度,简化客户端与之的接口。
  • 使用:定义系统的入口。
  • 方式:客户端不与系统耦合,外观类与系统耦合。
  • 场景:复杂的模块或子系统提供外界访问的模块;子系统相对独立;预防低水平人员带来的风险。

优缺点

  • 优点:
    1. 减少系统相互依赖。
    2. 提高灵活性。
    3. 提高了安全性。
  • 缺点:不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。

举例

  • 画不同的形状
class Shape {
    draw() {
        console.log('draw shape.');
    }
}
class Circle extends Shape {
    draw() {
        console.log('draw circle.');
    }
}

class Rectangle extends Shape {
    draw() {
        console.log('draw rectangle.');
    }
}

class ShapeMaker {
    constructor() {
        this.circle = new Circle();
        this.rectangle = new Rectangle();
    }
    drawCircle() {
        this.circle.draw();
    }
    drawRectangle() {
        this.rectangle.draw();
    }
}
function demo() {
    const shapeMaker = new ShapeMaker();
    shapeMaker.drawCircle();
    shapeMaker.drawRectangle();
}

demo();

image.png

享元模式

简介

  • 解决:运用共享技术有效地支持大量细粒度的对象。
  • 使用:系统中有大量对象;这些对象消耗大量内存;这些对象的状态大部分可以外部化...
  • 方式:用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象。
  • 场景:系统有大量相似对象;需要缓冲池的场景。

优缺点

  • 优点:大大减少对象的创建,降低系统的内存,使效率提高。
  • 缺点:提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。

举例

  • 需要不同颜色、坐标的圆。
class Shape {
    draw() {
        console.log('draw shape.');
    }
}
class Circle extends Shape {
    draw() {
        console.log('draw');
    }
}
class ShapeFactory {
    constructor() {
        this.circleMap = new Map();
    }

    getCircle(color) {
        this.color = color;
        let circle = this.circleMap.get(color);
        if (!circle) {
            const circle = new Circle(color);
            this.circleMap.set(color, circle);
            console.log("Creating circle of color : " + color);
        }
        return circle;
    }
    setX(x) {
        this.x = x;
    }

    setY(y) {
        this.y = y;
    }

    setRadius(radius) {
        this.radius = radius;
    }
    draw() {
        console.log("Circle: Draw() [Color : " + this.color + ", x : " + this.x + ", y :" + this.y + ", radius :" + this.radius);
    }

}
const colors = ["Red", "Green", "Blue", "White", "Black"];
function getRandomColor() {
    return colors[parseInt(Math.random() * colors.length)];
}
function getRandomX() {
    return Math.random() * 100;
}
function getRandomY() {
    return Math.random() * 100;
}
function demo() {
    for (let i = 0; i < 5; ++i) {
        const circle = new ShapeFactory()
        circle.getCircle(getRandomColor());
        circle.setX(getRandomX());
        circle.setY(getRandomY());
        circle.setRadius(100);
        circle.draw();
    }
}

demo();

image.png

代理模式

简介

  • 解决:为其他对象提供一种代理以控制对这个对象的访问。
  • 使用:想在访问一个类时做一些控制。
  • 方式:增加中间层。
  • 场景:远程、虚拟、Copy-on-Write 、保护(Protect or Access)、Cache、防火墙(Firewall)...代理。

优缺点

  • 优点:
    1. 职责清晰。
    2. 高扩展性。
    3. 智能化。
  • 缺点:
    1. 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
    2. 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

举例

  • 我们需要从本地加载一张图片。
class Images {
    constructor(filename) {
        this.filename = filename;
    }
    display() {
        console.log('display.');
    }
}
class RealImage extends Images {
    constructor(filename) {
        super(filename);
        this.loadFromDisk(filename);
    }
    display() {
        console.log('display ', this.filename);
    }
    loadFromDisk(filename) {
        console.log("Loading " + filename);
    }
}
class ProxyImage extends Images {
    constructor(filename) {
        super(filename);
        this.realImage = null;
    }

    display() {
        if (this.realImage === null) {
            this.realImage = new RealImage(this.filename);
        }
        this.realImage.display();
    }
}
function demo() {
    const images = new ProxyImage("test_10mb.jpg");
    // 图像从磁盘加载
    images.display();
    console.log('----------');
    // 图像不需要从磁盘加载
    images.display();
}

demo();

image.png

总结

  • 结构型模式是如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。
  • 在日常开发中,有些方法我们可以通过结构型的设计模式将两种或以上的方法组合到一起,避免再去实现一个完整的新的放法,从而减少代码。
  • 结构型模式会增加代码的耦合度,但组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。,我们还是应该根据开发实际情况有针对的选择。

更多优质文章

「点赞、收藏和评论」

❤️关注+点赞+收藏+评论+转发❤️,原创不易,鼓励笔者创作更好的文章,谢谢🙏大家。