专栏首页AlbertYang的编程之路设计模式(10)[JS版]-JavaScript如何实现组合模式???

设计模式(10)[JS版]-JavaScript如何实现组合模式???

1 什么是组合模式

组合模式允许创建具有属性的对象,这些对象是原始项目或对象集合。集合中的每个项目本身可以容纳其他集合,创建深度嵌套结构。

树型控件是复合模式的一个完美例子。树的节点要么包含一个单独的对象(叶子节点),要么包含一组对象(节点的子树)。组合模式用于简单化,一致化对单组件和复合组件的使用,其实它就是一棵树。组合模式能对于工作能起到简化作用,组合对象实现某一操作时,通过递归,向下传递到所有的组成对象,在存在大批对象时,假如页面的包含许多拥有同样功能的对象,只需要操作组合对象即可达到目标。在存在着某种的层次结构,并且其中的一部分要实现某些操作,即可使用组合模式。

组合模式中的所有节点都共享一组通用的属性和方法,它既支持单个对象,也支持对象集合。这种共同的接口极大地促进了递归算法的设计和构建,这种算法可以对复合集合中的每个对象进行迭代。

实例场景:

1 自然界中的各种树,树长在大地上,树头(树根),即是入口点,这棵树头向上生长,即有自己的叶子,又有自己的子树枝,某树枝还有自己的叶子,跟子树枝。

2 操作系统目录结构、公司部门组织架构、国家省市县等,像这么看起来复杂的现象,都可以使用组合模式,即部分-整体模式来操作。

2 主要参与者

参与该模式的对象有:

Component :声明组成中对象的接口。

Leaf :代表构图中的叶子对象,一个叶子没有子对象。

Composite :表示组成中的分支(或子树),维护一个子组件的集合。

3 代码实现

在下边的代码中,Node(节点)对象创建了一个树状结构。每个节点都有一个名字和4个方法:add、remove、getChild和hasChildren。这些方法被添加到Node的原型中。这减少了对内存的要求,因为这些方法现在被所有节点共享。Node是完全递归的,不需要单独的Component或Leaf对象。

通过向父节点添加节点来构建一个小型的复合树。一旦完成,我们调用traverse,它将遍历树中的每个节点,并显示其名称和深度(通过缩进显示)。日志函数用来记录和显示结果。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>组合模式:公众号AlbertYang</title>
  </head>
  <body>
  </body>
  <script>
    var Node = function(name) {
      this.children = [];
      this.name = name;
    }

    Node.prototype = {
      add: function(child) {
        this.children.push(child);
      },

      remove: function(child) {
        var length = this.children.length;
        for (var i = 0; i < length; i++) {
          if (this.children[i] === child) {
            this.children.splice(i, 1);
            return;
          }
        }
      },

      getChild: function(i) {
        return this.children[i];
      },

      hasChildren: function() {
        return this.children.length > 0;
      }
    }

    // 使用递归遍历一个树
    function traverse(indent, node) {
      log.add(Array(indent++).join("--") + node.name);

      for (var i = 0, len = node.children.length; i < len; i++) {
        traverse(indent, node.getChild(i));
      }
    }

    // 日志函数记录和打印结果
    var log = (function() {
      var log = "";

      return {
        add: function(msg) {
          log += msg + "\n";
        },
        show: function() {
          console.info("%c%s", "color:red; font-size:18px", log);
          log = "";
        }
      }
    })();

    function run() {
      var tree = new Node("root");
      var left = new Node("left")
      var right = new Node("right");
      var leftleft = new Node("leftleft");
      var leftright = new Node("leftright");
      var rightleft = new Node("rightleft");
      var rightright = new Node("rightright");

      tree.add(left);
      tree.add(right);
      tree.remove(right); // 删除节点
      tree.add(right);

      left.add(leftleft);
      left.add(leftright);

      right.add(rightleft);
      right.add(rightright);

      traverse(1, tree);

      log.show();
    }
    run();
</script>
</html>

4 应用实例

4.1 表单验证

演示地址:https://www.albertyy.com/2020/8/Component1.html

表单验证中,需要做的工作是表单的保存、恢复和验证表单中的值,然而表单的数量是未知数,类型是未知数,只有功能能确定,在这种情况下,使用组合模式无疑最好,通过给每个表单添加功能,然后一个表单对象组合起来,通过操作表单对象即可达到操作表单。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>组合模式实例应用:公众号AlbertYang</title>
  </head>
  <body>
    <input type="button" value="存储" onclick="a()" />
    <input type="button" value="取出" onclick="b()" />
  </body>
  <script type="text/javascript">
    //存储的值
    var value_content = {};

    function setCookie(name, value) {
      value_content[name] = value;
    }

    function getCookie(name) {
      return value_content[name];
    }
    //表单组合对象
    var CompositeForm = function(id, method, action) {
      this.formComponents = [];

      this.element = document.createElement('form');
      this.element.id = id;
      this.element.method = method || 'POST';
      this.element.action = action || '#';
    }

    CompositeForm.prototype.add = function(child) {
      this.formComponents.push(child);
      this.element.appendChild(child.getElement());
    }
    CompositeForm.prototype.remove = function(child) {
      for (var i = 0, len = this.formComponents.length; i < len; i++) {
        if (child == this.formComponents[i]) {
          this.formComponents.splice(i, 1);
          break;
        }
      }
    }
    CompositeForm.prototype.getChild = function(i) {
      return this.formComponents[i];
    }

    CompositeForm.prototype.save = function() {
      for (var i = 0, len = this.formComponents.length; i < len; i++) {
        this.formComponents[i].save();
      }
    }
    CompositeForm.prototype.restore = function() {
      for (var i = 0, len = this.formComponents.length; i < len; i++) {
        this.formComponents[i].restore();
      }
    }
    CompositeForm.prototype.getElement = function() {
      return this.element;
    }
    //接口方法
    var Field = function(id) {
      this.id = id;
      this.element;
      this.content;
    };
    Field.prototype.add = function() {};
    Field.prototype.remove = function() {};
    Field.prototype.getChild = function() {};

    Field.prototype.save = function() {
      setCookie(this.id, this.getValue());
    };
    Field.prototype.getElement = function() {
      return this.element;
    }
    Field.prototype.getValue = function() {
      throw new Error('错误');
    }
    Field.prototype.restore = function() {
      this.content.value = getCookie(this.id);
    };
    //继承方法
    function extend(subClass, superClass) {
      var F = function() {};
      F.prototype = superClass.prototype;
      subClass.prototype = new F();
      subClass.prototype.constructor = subClass;

      subClass.superclass = superClass.prototype;
      if (superClass.prototype.constructor == Object.prototype.constructor) {
        superClass.prototype.constructor = superClass;
      }
    }
    //输入框
    var InputField = function(id, label) {
      Field.call(this, id);

      this.input = document.createElement('input');
      this.content = this.input;

      this.label = document.createElement('label');
      var labelTextNode = document.createTextNode(label);
      this.label.appendChild(labelTextNode);

      this.element = document.createElement('div');
      this.element.id = id;
      this.element.className = 'input-field';
      this.element.appendChild(this.label);
      this.element.appendChild(this.input);
    }
    extend(InputField, Field);
    InputField.prototype.getValue = function() {
      return this.input.value;
    };
    //文本框
    var TextareaField = function(id, label) {
      Field.call(this, id);

      this.textarea = document.createElement('textarea');
      this.content = this.textarea;

      this.label = document.createElement('label');
      var labelTextNode = document.createTextNode(label);
      this.label.appendChild(labelTextNode);

      this.element = document.createElement('div');
      this.element.id = id;
      this.element.className = 'input-field';
      this.element.appendChild(this.label);
      this.element.appendChild(this.textarea);
    };
    extend(TextareaField, Field);
    TextareaField.prototype.getValue = function() {
      return this.textarea.value;
    };
    //选择框
    var SelectField = function(id, label) {
      Field.call(this, id);

      this.select = document.createElement('select');
      this.select.options.add(new Option("sfs", "sfs"));
      this.select.options.add(new Option("111", "2222222222")); //这边value会改变
      this.content = this.select;

      this.label = document.createElement('label');
      var labelTextNode = document.createTextNode(label);
      this.label.appendChild(labelTextNode);

      this.element = document.createElement('div');
      this.element.id = id;
      this.element.className = 'input-field';
      this.element.appendChild(this.label);
      this.element.appendChild(this.select);
    };
    extend(SelectField, Field);
    SelectField.prototype.getValue = function() {
      return this.select.options[this.select.options.selectedIndex].value;
    };
    //表单域
    var CompositeFieldset = function(id, legendText) {
      this.components = {};

      this.element = document.createElement('fieldset');
      this.element.id = id;

      if (legendText) {
        this.legend = document.createElement('legend');
        this.legend.appendChild(document.createTextNode(legendText));
        this.element.appendChild(this.legend);
      }
    };

    CompositeFieldset.prototype.add = function(child) {
      this.components[child.getElement().id] = child;
      this.element.appendChild(child.getElement());
    };

    CompositeFieldset.prototype.remove = function(child) {
      delete this.components[child.getElement().id];
    };

    CompositeFieldset.prototype.getChild = function(id) {
      if (this.components[id] != undefined) {
        return this.components[id];
      } else {
        return null;
      }
    };

    CompositeFieldset.prototype.save = function() {
      for (var id in this.components) {
        if (!this.components.hasOwnProperty(id))
          continue;
        this.components[id].save();
      }
    };

    CompositeFieldset.prototype.restore = function() {
      for (var id in this.components) {
        if (!this.components.hasOwnProperty(id))
          continue;
        this.components[id].restore();
      }
    };

    CompositeFieldset.prototype.getElement = function() {
      return this.element;
    };


    //用组合模式汇合起来
    var contactForm = new CompositeForm('contact-form', 'POST', 'test');

    var nameFieldset = new CompositeFieldset('name-fieldset');
    nameFieldset.add(new InputField('first-name', 'First Name'));
    nameFieldset.add(new InputField('last-name', 'Last Name'));
    contactForm.add(nameFieldset);

    var addressFieldset = new CompositeFieldset('address-fieldset');
    addressFieldset.add(new InputField('address', 'Address'));
    addressFieldset.add(new InputField('city', 'City'));
    addressFieldset.add(new SelectField('state', 'State'));
    addressFieldset.add(new InputField('zip', 'Zip'));
    contactForm.add(addressFieldset);

    contactForm.add(new TextareaField('comments', 'Comments'));

    document.body.appendChild(contactForm.getElement());

    function a() {
      contactForm.save();
    }

    function b() {
      contactForm.restore();
    }
</script>
</html>

4.1 图片阅读器

演示地址:https://www.albertyy.com/2020/8/Component2.html

图片阅读器与表单验证一样,通过汇合操作图片。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>组合模式实例应用:公众号AlbertYang</title>
  </head>
  <body>
    <input value="隐藏" type="button" onclick="a()" />
    <input value="显示" type="button" onclick="b()" />
  </body>
  <script type="text/javascript">
    //图片库
    var DynamicGallery = function(id) {
      this.children = [];

      this.element = document.createElement('div');
      this.element.id = id;
      this.element.className = 'dynamic-gallery';
    };
    DynamicGallery.prototype = {
      add: function(child) {
        this.children.push(child);
        this.element.appendChild(child.getElement());
      },
      remove: function(child) {
        for (var node, i = 0; node = this.getChild(i); i++) {
          if (node == child) {
            this.children.splice(i, 1);
            break;
          }
        }
        this.element.removeChild(chld.getElement());
      },
      getChild: function(i) {
        return this.children[i];
      },
      hide: function() {
        for (var i = 0, node; node = this.getChild(i); i++) {
          node.hide();
        }
        this.element.style.display = 'none';
      },
      show: function() {
        this.element.style.display = 'block';
        for (var i = 0, node; node = this.getChild(i); i++) {
          node.show();
        }
      },
      getElement: function() {
        return this.element;
      }
    };

    //单个图片
    var GalleryImage = function(src) {
      this.element = document.createElement('img');
      this.element.className = 'gallery-image';
      this.element.src = src;
    };
    GalleryImage.prototype = {
      add: function() {},
      remove: function() {},
      getChild: function() {},
      hide: function() {
        this.element.style.display = 'none';
      },
      show: function() {
        this.element.style.display = '';
      },
      getElement: function() {
        return this.element;
      }
    };
    //汇合起来
    var topGallery = new DynamicGallery('top-gallery');
    topGallery.add(new GalleryImage('img/1.jpg'));
    topGallery.add(new GalleryImage('img/2.jpg'));
    topGallery.add(new GalleryImage('img/3.jpg'));

    var vacationPhotos = new DynamicGallery('vacation-photos');
    for (var p = 0; p < 30; p++) {
      vacationPhotos.add(new GalleryImage('img/3.jpg'));
    }
    topGallery.add(vacationPhotos);
    document.body.appendChild(topGallery.getElement());

    function a() {
      topGallery.hide();
    }

    function b() {
      topGallery.show();
    }
</script>
</html>

5 总结

组合模式通过简单的操作就能达到复杂的效果,一个操作通过遍历递归传递这个操作。不过组合模式的弱点也在于此,如果层次过多,则性能将受到影响。组合模式应用需要符合两个条件,一是产生递归,二是具有相同的动作。

今天的学习就到这里,你可以使用今天学习的技巧来改善一下你曾经的代码,如果想继续提高,欢迎关注我,每天学习进步一点点,就是领先的开始。

本文分享自微信公众号 - AlbertYang(AlbertYang666),作者:AlbertYang

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-08-05

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 10.设计模式--组合模式(Composite模式)

    组合模式是一种结构型模型,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模...

    大猫的Java笔记
  • 设计模式(7)[JS版]-JavaScript设计模式之原型模式如何实现???

    原型模式(prototype)是指用原型实例指向创建对象的种类,并且通过拷贝这些原型创建新的对象。 原型模式不单是一种设计模式,也被称为一种编程泛型。 从设计模...

    AlbertYang
  • 设计模式(9)[JS版]-JavaScript设计模式之如何实现桥接模式???

    Bridge模式允许两个组件,即客户端和服务一起工作,每个组件都有自己的接口。Bridge是一种高级架构模式,它的主要目标是通过两级抽象来编写更好的代码。它有利...

    AlbertYang
  • JavaScript设计模式之组合模式

    一个公司,可能分为很多个事业部,然后事业部又分为不同的部门。每个部门可能又分为不同的方向,每个方向又由不同的项目组组成。在程序设计中,也有一些和“事物是由相似的...

    一粒小麦
  • 设计模式(2)[JS版]---JavaScript如何实现单例模式?

    单例就是保证一个类只有一个实例,实现的方法一般是先判断实例存在与否,如果存在直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。

    AlbertYang
  • 设计模式(8)[JS版]-JavaScript设计模式之如何实现适配器模式???

    适配器模式是将一个接口(对象的属性和方法)转换为另一个接口。适配器允许编程组件协同工作,否则由于接口不匹配而无法协同工作。适配器模式也称为包装器模式。

    AlbertYang
  • 设计模式(4)[JS版]-JavaScript如何实现建造者模式?

    建造者模式(Builder)可以将一个复杂对象的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。也就是说如果我们用了建造者模式,那么用户只需要指定需要...

    AlbertYang
  • Java描述设计模式(10):组合模式

    知了一笑
  • 设计模式(5)[JS版]-JavaScript如何实现工厂方法模式?

    在基于类的编程中,工厂方法模式是一种创建模式,该模式使用工厂方法来处理创建对象的问题,而不必指定将要创建的对象的确切类。这是通过调用工厂方法来创建对象的,而不是...

    AlbertYang
  • 设计模式(6)[JS版]-JavaScript如何实现抽象工厂模式?

    抽象工厂模式提供了一种封装一组具有相同主题的单个工厂而无需指定其具体类的方法。即工厂的工厂;一个将单个相关/从属工厂分组在一起的工厂,但未指定其具体类别。

    AlbertYang
  • 设计模式-组合实体模式

    通常我们收快递,有时候上班期间真的没有空去收啊,所有就会委托其他人,或者说让房管代收一下,然后房管会统一放到一个地方,下再我们再去取,而这个统一管理,我们就不用...

    逍遥壮士
  • 设计模式实战-组合模式

    这节我们将介绍一种全新的设计模式——组合模式。想起“组合”二字,自然联想到了很多,比如:文件和文件夹、容器和组件、火车和车厢、大树的枝干和叶子等等,大自然中组合...

    架构师修炼
  • JS设计模式之基于组合模式的code review

    版权声明:本文为博主原创文章,未经博主允许不得转载。 ...

    j_bleach
  • JavaScript 设计模式学习第十六篇-组合模式

    组合模式(Composite Pattern)又叫整体-部分模式,它允许你将对象组合成树形结构来表现整体-部分层次结构,让使用者可以以一致的方式处理组合对象以及...

    越陌度阡
  • python3 简单实现组合设计模式

    组合模式是把一个类别归为一个整体,并且组织多个整体之间的关系,使用通过树形结构来描述所有整体。

    砸漏
  • 重学 Java 设计模式:实战组合模式

    头几年只要群里一问我该学哪个开发语言,哪个语言最好。群里肯定聊的特别火热,有人支持PHP、有人喊号Java、也有C++和C#。但这几年开始好像大家并不会真的刀枪...

    小傅哥
  • go语言实现设计模式(三):模版模式

    模版模式真的是一个好东西。所谓模版模式,就是说,某几个类中相同的操作和代码提取到父类的一个函数中,并定义相同的操作为抽象函数。由子类来实现。估计我也没表达清楚,...

    超级大猪
  • 深入理解JavaScript系列(40):设计模式之组合模式

    组合模式(Composite)将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。

    用户4962466
  • 设计模式之简单的单例模式如何实现

    单例模式的特点:在同一时期,某个类的对象一定最多只有1个!也许会尝试多次的获取对象,但是,获取到的一定是同一个对象!

    海拥

扫码关注云+社区

领取腾讯云代金券