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中不一定非要用模版方法,可能选择高阶函数更合适;
posted @ 2015-10-19 23:36  JinksPeng  阅读(171)  评论(0编辑  收藏  举报