zoukankan      html  css  js  c++  java
  • 07组合,模版

    组合模式

    • 将对象组合成树形结构,以表示‘部分-整体’的层次结构;遍历树形结构的操作只需要一次操作;
    • 利用对象多态性的统一对待组合对象和单个对象,不用关心他们的不同;
    • 像命令模式中宏命令就是一种组合模式;

    请求在树中传递的过程;

    • 如果子节点是叶节点,叶对象自身会处理这个请求;如果还是组合对象则继续往下传递;

    更强大的宏命令

    • 由于宏命令和一般对象的执行都是excute;所有宏命令还可以组合
     var macroCommand1 = MacroCommand();
     macroCommand1.add(openCommand);
    
     var macroCommand2 = MacroCommand();
     macroCommand2.add(closeCommand);
     
     var macroCommand = MacroCommand(); 
     macroCommand.add(macroCommand1);
     macroCommand.add(macroCommand2);
    

    透明性带来的安全性问题

    • 组合对象和叶对象还是有区别的:叶对象不能增加子节点
    var openCommand = {
      execute: function() {
        console.log('open');
      },
      add: function() {
        throw new Error('can not add')
      }
    }
    

    组合模式例子-扫描文件夹

    var Folder = function(name) {
      this.name = name;
      this.parent = null;
      this.files = [];
    };
    Folder.prototype.add = function(file) {
      file.parent = this;
      this.files.push(file);
      return this;
    };
    Folder.prototype.scan = function() {
      console.log('开始扫描文件夹:' + this.name);
      var i = 0, file, files = this.files;
      for(; file = files[i++];)
        file.scan();
    };
    Folder.prototype.remove = function() {
      var parent = this.parent;
      if(!parent)
        return;
      var files = parent.files, file;
      for(var len = files.length - 1; len >= 0; len--) {
        file = files[len];
        if(file === this)
          files.splice(len, 1);
      }
    };
    
    var File = function(name) {
      this.name = name;
      this.parent = null;
    };
    File.prototype.add = function() {
      throw new Error('文件下不能再添加文件');
    };
    File.prototype.scan = function() {
      console.log('开始扫描文件:' + this.name);
    };
    File.prototype.remove = function() {
      var parent = this.parent;
      if(!parent)
        return;
      var files = parent.files, file;
      for(var len = files.length - 1; len >= 0; len--) {
        file = files[len];
        if(file === this)
          files.splice(len, 1);
      }
    };
    
    var folder = new Folder( '学习资料' );
    var folder1 = new Folder( 'JavaScript' );
    folder1.add(new File( 'JavaScript 设计模式与开发实践' ));
    folder.add( folder1 ).add(new File( '重构与模式' ));
    folder1.remove();
    folder.scan();
    

    新闻模块

    //虚拟父类
    var News = function () {
      this.children = [];
      this.element = null;
    }
    
    News.prototype = {
      init: function () {
        throw new Error('请重写你的方法!');
      },
      add: function () {
        throw new Error('请重写你的方法!');
      },
      getElement: function () {
      	console.log(this);
        throw new Error('请重写你的方法!');
      }
    }
    //容器类构造函数
    var Container = function (id, parent) {
      News.call(this);
      this.id = id;
      this.parent = parent;
      this.init(); 
    }
    inheritPro(Container, News);
    
    Container.prototype.init = function () {
      this.element = document.createElement('ul');
      this.element.id = this.id;
      this.element.className = 'new-container';
    }
    Container.prototype.add = function (child) {
      this.children.push(child);
      this.element.appendChild(child.getElement());
      return this;
    }
    Container.prototype.getElement = function () {
      return this.element;
    }
    Container.prototype.show = function () {
      this.parent.appendChild(this.element);
    }
    //成员集合类
    var Item = function (classname) {
      News.call(this);
      this.classname = classname || '';
      this.init();
    }
    inheritPro(Item, News);
    Item.prototype.init = function () {
      this.element = document.createElement('li');
      this.element.className = this.classname;
    }
    Item.prototype.add = function (child) {...}
    Item.prototype.getElement = function () {...}
    //其它基类
    NewsGroup..
    ImageNews..
    IconNews..
    EasyNews..
    TypeNews..
    
    //构建
    var news1 = new Container('news', document.body);
    news1.add(
      new Item('normal').add(
        new IconNews('...', '#', 'video')
      )
    ).add(
      new Item('normal').add(
      	new NewsGroup('has-img', '#', 'small').add(
      	  new ImageNews('img/1.jpg', '#', 'small')
      	).add(
          new EasyNews('...', '#')
      	).add(
          new EasyNews('...', '#')
      	)
      )
    ).add(
      new Item('normal').add(
        new TypeNews('...', '#', '..', 'left')
      )
    ).show();
    

    值得注意的地方

    • 组合模式是一种聚合关系不是父子关系;
    • 要求组合对象和叶对象拥有相同的接口;同时一组叶对象的操作也必须一致;
    • 在一些复合情况下必须给父节点和子节点建立双向映射关系;最简单的方法就是互相保存对方的引用,可用引入中介者来管理这些对象;
    • 在一些结构复杂的树中,应该避免便利整棵树,可用借助职责链模式;

    组合模式的使用情况

    • 表示对象的部分-整体层次结构,特别是不确定有多少层次的时候;
    • 希望统一对象树中的所有对象的时候;

    模版方法模式

    • 是一种典型得通过封装变化提高系统扩展性的设计模式;
    • 模版方法模式由两部分组成:
      • 抽象父类
      • 实现子类
    • 模版方式方法是一种严重依赖抽象类的设计模式;JS并没有从语法层面提供对抽象类的支持;JS中不能保证子类是否重写父类方法
      • 简单的解决方法:给子类要重写的父类方法设置抛出异常
    Father.prototype.method = function() {
      throw new Error('子类必须重写这个方法');
    }
    

    使用场景

    • web开发中,比如构建一系列UI组件的一般过程:
      • 1.初始化一个div容器
      • 2.通过ajax请求相应内容
      • 3.把数据渲染到容器中
      • 4.通知用户组建渲染完毕
      • 一般第2步请求地址不同,第3步渲染方式不同;所以可以把这4步抽象到父类的模版中,父类还可以提供1,4步的具体实现;当子类基础这个父类后再重写模版方法中的2,3步;

    钩子方法

    • 一般模版方法里的步骤是确定好的,具体情况下可以加入钩子;通过其返回的结果来调整顺序或跳过步骤;

    好莱坞原则

    • 允许底层组件将自己挂钩到高层组建中,高层组件会决定什么时候以何种方式去使用底层组建;
    • 用模版方式编写时,意味着子类放弃了对自己的控制权,其只负责提供一些设计上的细节;
    • 这个原则也应用于观察者模式和回调函数;

    去处继承

    • 模版方法模式是为数不多基于基础的设计模式,但JS语言实际并没有提供真正的类式继承;
    var Father = function(param){
      var first = param.first || function() {
        console.log('first');
      };
      var second = param.second || function() {
        throw new Error('必须传递second方法');
      };
      var F = function() {};
      F.prototype.init = function(){
        first();
        second();
      }
      return F;
    };
    
    var Child = Father({
      second: function() {
        console.log('second');
      }
    });
    
    var child = new Child();
    child.init();
    
    

    弹出框例子

      //模版
      var Alert = function (data) {
        if(!data) return;
        this.content = data.content;
        this.panel = document.createElement('div');
        this.panel.className = 'alert';
        this.contentNode = document.createElement('p');
        this.contentNode.innerHTML = this.content;
        this.confirmBtn = document.createElement('span');
        this.confirmBtn.className = 'a-confirm';
        this.confirmBtn.innerHTML = data.confirm || '确认';
        this.closeBtn = document.createElement('b');
        this.closeBtn.className = 'a-close';
        this.closeBtn.innerHTML = data.close || 'x';
        this.success = data.success || function() {};
        this.fail = data.fail || function () {};
        this.init();
      }
    
      Alert.prototype = {
        init: function () {
          this.panel.appendChild(this.closeBtn);
          this.panel.appendChild(this.contentNode);
          this.panel.appendChild(this.confirmBtn);
          document.body.appendChild(this.panel);
          this.bindEvent();
          this.show();
        },
        bindEvent: function () {
          var self = this;
          this.closeBtn.onclick = function () {
            self.fail();
            self.hide();
          }
          this.confirmBtn.onclick = function () {
            self.success();
            self.hide();
          }
        },
        hide: function () {
          this.panel.style.display = 'none';
        },
        show: function () {
          this.panel.style.display = 'block';
        }
      }
      //根据模版创建类
      var RightAlert = function (data) {
        Alert.call(this, data);
        this.confirmBtn.className = this.confirmBtn.className + ' right';
      }
      RightAlert.prototype = new Alert();
    
    

    导航栏例子

      //一个格式化方法
      function formateString (str, data) {
        return str.replace(/{#(w+)#}/g, function (match, key) {
          return typeof data[key] === undefined ? '' : data[key];
        })
      }
      //基础导航
      var Nav = function (data) {
        this.item = '<a href="{#href#}" title="{#title#}">{#name#}</a>';
        this.html = '';
        for(var i = 0, l = data.length; i < l; i++) {
          this.html += formateString(this.item, data[i]);
        }
        return this.html;
      }
      //带有消息提醒的导航
      var NumNav = function (data) {
        var tpl = '<b>{#num#}</b>';
        for(var i = data.length - 1; i >= 0; i--) {
          data[i].name +=  formateString(tpl, data[i]);
        }
        return Nav.call(this, data);
      }
    
      var nav = document.getElementById('container');
      nav.innerHTML = NumNav([
        {
          href: 'http://www.baidu.com',
          title: 'baidu',
          name: '百度',
          num: '10'
        },
        {
          href: 'http://www.taobao.com',
          title: 'taobao',
          name: '淘宝',
          num: '2'
        }
      ])
    
    • 其实在JS中不一定非要用模版方法,可能选择高阶函数更合适;
  • 相关阅读:
    Combobox的使用
    章节十、7-Xpath---Xpath中绝对路径相对路径的区别
    章节十、6-CSS---用CSS 定位子节点
    章节十、5-CSS---用CSS 通配符定位元素
    章节十、4-CSS Classes---用多个CSS Classes定位元素
    章节十、3-CSS Selector---用CSS Selector
    章节十、2-用Linktext和PartialLinkText、ClassName、TagName定位元素
    章节十、1-用ID和XPath、name定位元素
    章节九、5-IE Driver
    章节九、4-ChromDriver介绍
  • 原文地址:https://www.cnblogs.com/jinkspeng/p/4582458.html
Copyright © 2011-2022 走看看