组合模式(Composite)
又称
部分-整体模式
,将对象组合成树形结构
以表示“部分整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
需求:新闻列表组件
- 新闻列表组件要求:
- 文字新闻,例:
80岁老翁一夜暴富
- 图片文字新闻,例:
(img)90后父母给儿子取名为“张总”
- 图标文字新闻-类似图片文字新闻,例:
(icon)100岁老头儿半夜上吊
- 图标新闻 和 文字新闻 在一行中,例:
(icon)已逝之人30年后再现 | 面对黑暗得用火
- 分类文字新闻,例:
[type]他也曾拿着手术刀在夜半凝视着我的脖颈
- 文字新闻,例:
实现:
- 定义虚拟类,用于标记开发者需要创建怎样的类作为树上的节点(
节点模板
)/** * 新闻虚拟类 */ const News = function () { // 子组件容器 this.children = []; // 当前组件元素 this.element = null; }; News.prototype = { // 初始化DOM init: function () { throw new Error("请重写你的方法"); }, // 添加子DOM add: function () { throw new Error("请重写你的方法"); }, // 获取当前DOM getElement: function () { throw new Error("请重写你的方法"); } };
定义继承模式(
这里使用的是寄生组合设计模式
)/** * 寄生组合式继承 - 寄生式继承,继承原型 */ // 原型式继承 function inheritObject(o) { // 声明一个 过渡函数对象 function F() { } // 过渡对象 的 原型 继承 父对象 F.prototype = o; // 返回过度对象的一个实例,该实例的原型继承了父对象 return new F(); } /** * 寄生组合式继承 - 寄生式继承,继承原型 * @param {class} subClass 子类 * @param {class} superClass 父类 */ function inheritPrototype(subClass, superClass) { // 赋值一份父类的原型副本保存在变量中 let p = inheritObject(superClass.prototype); // 修正因为重写子类原型导致子类的constructor属性被修改 p.constructor = subClass; // 设置子类的原型 subClass.prototype = p; }
- 定义一个新闻容器(ul)存在新闻列表(
继承自虚拟类
)/** * 创建一个新闻列表组件容器(ul) * @param {string} id DOM id * @param {HTMLElement} parent 父容器(要把ul放到哪) */ const Container = function (id, parent) { // 构造函数继承父类 News.call(this); // 模块id this.id = id; // 模块的父容器 this.parent = parent; // 构建方法 this.init(); }; // 寄生式继承父类原型方法 inheritPrototype(Container, News); // 构建方法 Container.prototype.init = function () { this.element = document.createElement('ul'); this.element.id = this.id; this.element.className = 'news-container'; }; // 添加子元素方法(li) 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); return this; };
let newsContainer = new Container('news-id', document.body); newsContainer.show(); // <ul id="news-id" class="news-container"></ul>
- 定义新闻内容的父容器(
li
)/** * 创建新闻条目(li) * @param {string} className class样式选择器 */ const NewsRow = function (className) { News.call(this); this.className = className; this.init(); }; inheritPrototype(NewsRow, News); // 初始化条目DOM NewsRow.prototype.init = function () { this.element = document.createElement('li'); this.element.className = this.className; } NewsRow.prototype.add = function (child) { // 在子元素容器中插入子元素 this.children.push(child); // 插入当前组件元素树中 this.element.appendChild(child.getElement()); return this; } NewsRow.prototype.getElement = function () { return this.element; }
- 定义各类型的新闻
文字新闻
/** * 文字新闻 * @param {string} text 新闻标题 * @param {string} href 新闻链接 */ const TextNews = function (text, href) { News.call(this); this.text = text || ''; this.href = href || ''; this.init(); }; inheritPrototype(TextNews, News); TextNews.prototype.init = function () { this.element = document.createElement('a'); this.element.innerText = this.text; this.element.href = this.href; }; TextNews.prototype.add = function (child) { // 在子元素容器中插入子元素 this.children.push(child); // 插入当前组件元素树中 this.element.appendChild(child.getElement()); return this; }; TextNews.prototype.getElement = function () { return this.element; };
图片/图标 文字新闻
/** * 图片/图标 文字新闻 * @param {string} url 图片地址 * @param {string} text 新闻标题 * @param {string} href 新闻链接 */ const ImageNews = function (url, text, href) { News.call(this); this.url = url || '默认图片地址'; this.text = text || ''; this.href = href || ''; this.init(); }; inheritPrototype(ImageNews, News); ImageNews.prototype.init = function () { // 创建a标签 this.element = document.createElement('a'); this.element.href = this.href; // 创建图片标签 let img = new Image(); img.src = this.url; this.element.appendChild(img); // 创建文字标签 let span = document.createElement('span'); span.innerText = this.text; this.element.appendChild(span); }; ImageNews.prototype.add = function (child) { // 在子元素容器中插入子元素 this.children.push(child); // 插入当前组件元素树中 this.element.appendChild(child.getElement()); return this; }; ImageNews.prototype.getElement = function () { return this.element; };
分类文字新闻
/** * 分类文字新闻 * @param {string} type 新闻分类 * @param {string} text 新闻标题 * @param {string} href 新闻链接 */ const TypeNews = function (type, text, href) { News.call(this); this.type = type || '[未分类] '; this.text = text || ''; this.href = href || ''; this.init(); }; inheritPrototype(TypeNews, News); TypeNews.prototype.init = function () { this.element = document.createElement('a'); this.element.href = this.href; let iEle = document.createElement('i'); iEle.innerText = `[${ this.type}] `; this.element.appendChild(iEle); let span = document.createElement('span'); span.innerText = this.text; this.element.appendChild(span); }; TypeNews.prototype.add = function (child) { // 在子元素容器中插入子元素 this.children.push(child); // 插入当前组件元素树中 this.element.appendChild(child.getElement()); return this; }; TypeNews.prototype.getElement = function () { return this.element; };
实现新闻列表组件:
let newsContainer = new Container('news-id', document.body); newsContainer.add( new NewsRow('news-text').add( // 文字新闻,例:`80岁老翁一夜暴富` new TextNews('80岁老翁一夜暴富', '#') ) ).add( new NewsRow('news-img').add( // 图片文字新闻,例:`(img)90后父母给儿子取名为“张总”` new ImageNews('https://dummyimage.com/200x100/000/fff', '90后父母给儿子取名为“张总”', '#') ) ).add( new NewsRow('news-icon').add( // 图标文字新闻,例:`(icon)100岁老头儿半夜上吊` new ImageNews('https://dummyimage.com/16x16/00f/fff', '100岁老头儿半夜上吊', '#') ) ).add( new NewsRow('news-text').add( // 图标新闻和文字新闻在一行中,例:`(icon)已逝之人30年后再现 | 面对黑暗得用火` new ImageNews('https://dummyimage.com/16x16/00f/fff', '已逝之人30年后再现 | ', '#') ).add( // 图标新闻和文字新闻在一行中,例:`(icon)已逝之人30年后再现 | 面对黑暗得用火` new TextNews('面对黑暗得用火', '#') ) ).add( new NewsRow('news-type').add( // 带分类新闻,例:`[type]他也曾拿着手术刀在夜半凝视着我的脖颈` new TypeNews('深夜惊悚', '他也曾拿着手术刀在夜半凝视着我的脖颈', '#') ) ).show(); console.log(newsContainer);
最终的树型数据结构
特点:
组合模式能够给我们提供一个清晰的组成结构(整体-部分,树型-节点)。