TypeScript设计模式之组合、享元
看看用TypeScript怎样实现常见的设计模式,顺便复习一下。
学模式最重要的不是记UML,而是知道什么模式可以解决什么样的问题,在做项目时碰到问题可以想到用哪个模式可以解决,UML忘了可以查,思想记住就好。
这里尽量用原创的,实际中能碰到的例子来说明模式的特点和用处。
组合模式 Composite
特点:以树的形式展示对象的组合,并且可以以类似的方式处理每个枝点。
用处:当对象组合以树状存在,有父有子,并且对象的行为差不多时可以考虑组合模式,如菜单,游戏里的技能树。
注意:遍历组合的性能要求。
下面用TypeScript简单实现一下组合模式:
技能树麻烦了点,技能激活要引入观察者模式,就以菜单为例吧。
菜单可以包括子菜单,点击菜单项时有子菜单则显示子菜单,没有时触发点击事件。
先声明一个抽象,包含菜单的名字,点击事件和添加Child,一般情况下Menu会维护一个childs集合,通过这个集合来添加子菜单,不过这里没有用这种方式,采用的是继承一个集合来让本身拥有集合的能力,这样更方便,是父还是子可以通过字段来控制,也可以通过是否有child来表示。
abstract class MenuBase extends Array<MenuBase>{
name: string;
abstract click();
addChild(...childs: Array<MenuBase>){
childs.forEach(o=>this.push(o));
}
}
实现具体MenuItem类,一般情况下可以用两个类,一个代表菜单,一个代表菜单项,不过这里就不需要区别是枝还是叶了,简单一点,只用一个MenuItem代表所有。
可以传click处理事件进来,click时会触发click事件,另外如果有子则显示所有子。
class MenuItem extends MenuBase{
constructor(public name: string, private clickFunc: () => string = undefined){
super();
}
click(){
console.log(`click ${this.name}`);
if(this.clickFunc){
console.log(this.clickFunc());
}
if(this.length > 0){
let childs = this.reduce((p, c)=><MenuBase>{name:`${p.name},${c.name}`}).name;
console.log(`${this.name}'s childs: ${childs}`);
}
}
}
现在来运行一下:
let root = new MenuItem('root');
let A1 = new MenuItem('A1');
let A2 = new MenuItem('A2', ()=>{return 'my name is A2'});
let B1 = new MenuItem('B1');
let B2 = new MenuItem('B2', ()=>{return 'my name is B2'});
let B3 = new MenuItem('B3', ()=>{return 'my name is B3'});
root.push(A1, A2);
A1.push(B1, B2, B3);
root.click();
A1.click();
A2.click();
B1.click();
结果:
click root
root's childs: A1,A2
click A1
A1's childs: B1,B2,B3
click A2
my name is A2
click B1
符合预期行为,这种组合就是非常简单的,但如果组合得非常深且枝非常多时就需要考虑查找枝时的效率问题了,通常的办法是采用缓存来把一些常用的查找结果缓存起来,避免频繁遍历。
享元模式 FlyWeight
特点:通过缓存来实现大量细粒度的对象的复用,从而提升性能。
用处:当有很多类似的对象要频繁创建、销毁时可以考虑享元模式,如线程池。
注意:对象的内部状态和外部状态。
下面用TypeScript简单实现一下享元模式:
假设一个画图场景,里面有很多图元,如圆,矩形等,由于量非常多,且经常刷新,每次刷新new出一批图元,浪费内存是小事,临时小对象太多导致频繁GC才是麻烦事,UI会表现出卡顿。
现在用享元模式来解决这个问题,先定义一个图形接口,可以画图,可以被删掉,同时有个isHidden状态来控制该图元是否显示,删除也就是把isHidden设为true,并不真的删除。
name是个标记,方便看记录信息。
interface Element{
isHidden: boolean;
name: string;
draw();
remove();
}
实现两个图元:圆和矩形,draw只会画不是hidden的,remove则是把isHidden设为true
class Circle implements Element{
isHidden: boolean = false;
constructor(public name: string){
}
draw(){
if(!this.isHidden){
console.log(`draw circle: ${this.name}`);
}
}
remove(){
this.isHidden = true;
console.log(`remove circle: ${this.name}`)
}
}
class Rectangle implements Element{
isHidden: boolean = false;
constructor(public name: string){
}
draw(){
if(!this.isHidden){
console.log(`draw rectangle: ${this.name}`);
}
}
remove(){
this.isHidden = true;
console.log(`remove rectangle: ${this.name}`)
}
}
下面建个图元工厂来创建、维护图元,工厂里维护了一个图元池,包括圆和矩形,工厂除了创建图元外,还可以删掉所有图元,得到图元的数量(可见或不可见的)
class GraphFactory{
static readonly instance: GraphFactory = new GraphFactory();
private graphPool: {[key: string]: Array<Element>} = {}; // 图元池
getCircle(newName: string): Circle{
return <Circle>this.getElement(newName, Rectangle);
}
getRectangle(newName: string): Rectangle{
return <Rectangle>this.getElement(newName, Rectangle);
}
getCount(isHidden: boolean){ // 图元数据
let count = 0;
for(let item in this.graphPool){
count += this.graphPool[item].filter(o=>o.isHidden == isHidden).length;
}
return count;
}
removeAll(){ //删除所有
console.log('remove all');
for(let item in this.graphPool){
this.graphPool[item].forEach(o=>o.isHidden = true);
}
}
//获取图元,如果池子里有hidden的图元,就拿出来复用,没有就new一个并Push到池子里
private getElement(newName: string, eleConstructor: typeof Circle | typeof Rectangle): Element{
if(this.graphPool[eleConstructor.name]){
let element = this.graphPool[eleConstructor.name].find(o=>o.isHidden);
if(element){
element.isHidden = false;
element.name = newName;
return element;
}
}
let element = new eleConstructor(newName);
this.graphPool[eleConstructor.name] = this.graphPool[eleConstructor.name] || [];
this.graphPool[eleConstructor.name].push(element);
return element;
}
}
代码写完了,分别拿5个圆和5个矩形测试:
- 先从工厂里分别创建出来并调用draw
- 打印出当前可见图元数量
- 移除所有
- 分别打印可见和不可见图元数量
- 从工厂里再次分别创建出5个并调用draw
- 再次分别打印可见和不可见图元数量
const num = 5;
console.log('create elements');
for(let i = 1;i<=num;i++){
let circle = GraphFactory.instance.getCircle(`circle ${i}`);
let rectangle = GraphFactory.instance.getRectangle(`rectangle ${i}`);
circle.draw();
rectangle.draw();
}
console.log(`element count: ${GraphFactory.instance.getCount(false)}`);
GraphFactory.instance.removeAll();
console.log(`visible element count: ${GraphFactory.instance.getCount(false)}`);
console.log(`hidden element count: ${GraphFactory.instance.getCount(true)}`);
console.log('create elements again');
for(let i = 1;i<=num;i++){
let circle = GraphFactory.instance.getCircle(`new circle ${i}`);
let rectangle = GraphFactory.instance.getRectangle(`new rectangle ${i}`);
circle.draw();
rectangle.draw();
}
console.log(`visible element count: ${GraphFactory.instance.getCount(false)}`);
console.log(`hidden element count: ${GraphFactory.instance.getCount(true)}`);
结果:
create elements
draw rectangle: circle 1
draw rectangle: rectangle 1
draw rectangle: circle 2
draw rectangle: rectangle 2
draw rectangle: circle 3
draw rectangle: rectangle 3
draw rectangle: circle 4
draw rectangle: rectangle 4
draw rectangle: circle 5
draw rectangle: rectangle 5
element count: 10
remove all
visible element count: 0
hidden element count: 10
create elements again
draw rectangle: new circle 1
draw rectangle: new rectangle 1
draw rectangle: new circle 2
draw rectangle: new rectangle 2
draw rectangle: new circle 3
draw rectangle: new rectangle 3
draw rectangle: new circle 4
draw rectangle: new rectangle 4
draw rectangle: new circle 5
draw rectangle: new rectangle 5
visible element count: 10
hidden element count: 0
可以看到remove all后图元全部hidden,再次创建时并不new出新的,而是从池子里拿出hidden的来复用,也就是利用享元模式,小对象的数量就可以限制在单次显示最多的那次的数量,少于这个数量的都会从池子里拿,小对象也没有频繁创建销毁,对内存,对GC都是有利的。
TypeScript设计模式之组合、享元的更多相关文章
- Java设计模式学习记录-享元模式
前言 享元模式也是一种结构型模式,这篇是介绍结构型模式的最后一篇了(因为代理模式很早之前就已经写过了).享元模式采用一个共享来避免大量拥有相同内容对象的开销.这种开销最常见.最直观的就是内存损耗. 享 ...
- [设计模式] javascript 之 享元模式;
享元模式说明 定义:用于解决一个系统大量细粒度对象的共享问题: 关健词:分离跟共享: 说明: 享元模式分单纯(共享)享元模式,以及组合(不共享)享元模式,有共享跟不共享之分:单纯享元模式,只包含共享的 ...
- 面向对象设计模式之Flyweight享元模式(结构型)
动机:采用纯粹对象方案的问题在于大量细粒度的对象会很快充斥在系统中,从而带来很高的运行代价——主要指内存需求方面的代价.如何在避免大量细粒度对象问题的同 时,让外部客户程序仍然能够透明地使用面向对象的 ...
- Java设计模式之《享元模式》及应用场景
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6542449.html 享元模式:"享"就是分享之意,指一物被众人共享, ...
- 设计模式(11)--Flyweight(享元模式)--结构型
作者QQ:1095737364 QQ群:123300273 欢迎加入! 1.模式定义: 享元模式是对象的结构模式.享元模式以共享的方式高效地支持大量的细粒度对象. 2.模式特点: 享元模 ...
- 重学 Java 设计模式:实战享元模式「基于Redis秒杀,提供活动与库存信息查询场景」
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 程序员的上下文是什么? 很多时候一大部分编程开发的人员都只是关注于功能的实现,只 ...
- 设计模式学习之享元模式(Flyweight,结构型模式)(20)
转:http://terrylee.cnblogs.com/archive/2006/03/29/361767.html 摘要:面向对象的思想很好地解决了抽象性的问题,一般也不会出现性能上的问题.但是 ...
- 设计模式学习心得<享元模式 Flyweight>
享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能.这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式. 享元模式尝 ...
- C#设计模式之十一享元模式(Flyweight Pattern)【结构型】
一.引言 今天我们要讲[结构型]设计模式的第六个模式,该模式是[享元模式],英文名称是:Flyweight Pattern.还是老套路,先从名字上来看看.“享元”是不是可以这样理解,共享“单元”,单元 ...
随机推荐
- 使用AIR进行移动APP开发常见功能和问题(下)
1. Air如何判断android.ios 平台网络连接状态? Android,使用as3原生api: if(NetworkInfo.isSupported)//只有android支持 Networ ...
- Linux笔记(十一) - 文件系统管理
(1)文件系统查看命令:df [选项] [挂载点]-a 显示所有文件系统信息,包括特殊文件系统,如/proc /sysfs-h 使用习惯单位显示容量,如KB,MB或GB-T 显示文件系统类型-m 以M ...
- Ubuntu 修改时区
1. 使用命令行 sudo tzselect 根据提示完成修改 2.修改~/.profile文件 添加: TZ='Asia/Shanghai'; export TZ 注销后重新登陆生效
- MyBatis中多对多关系的映射和查询
先说一下需求: 在页面上显示数据库中的所有图书,显示图书的同时,显示出该图书所属的类别(这里一本书可能同时属于多个类别) 创建表: 笔者这里使用 中间表 连接 图书表 和 图书类别表,图书表中 没有使 ...
- 常用的JQ函数
/// <reference path="jquery-1.8.0.min.js"> /* * DIV或元素居中 * @return */ jQuery.fn.mCen ...
- 双击startup.bat启动tomcat时闪退原因及解决方案
在启动免安装tomcat时,很容易遇到双击startup.bat,启动窗口闪退而tomcat服务未启动的状况. 具体原因:tomcat在启动时,需要读取环境变量和配置信息,如果缺少Java环境,即缺少 ...
- 从你的全世界切过(胡说八道支持向量机SVM小故事)
背景 据说很久很久以前, 澳门有一家"胡说八道大赌场", 专门提供各种奇奇怪怪的玩法. 其中有一个赌博叫"从你的全世界切过"(连名字也这么奇怪). 玩法是在一张 ...
- Webappbuilder自定义widget模板
Webappbuilder自定义widget模板 by 李远祥 到\\widgets\samplewidgets目录下拷贝 CustomWidgetTemplate 文件并重命名为MyWidget 设 ...
- java 多线程之卖票两种方式
1.通过extends Thread /* 需求:简单的卖票,多个窗口同时买票 (共用资源) 创建线程的第二种方式:实现Runnable接口 步骤: 1,定义类实现Runnable接口 2,覆盖/重写 ...
- 使用批处理根据项目工程文件生成Nuget包并发布(支持.NET Core)
最近在使用之前自己编写的批处理给.NET Core项目打包时出问题了,发现之前的脚本根本不适用了,折腾了半天,总算解决了.因此在这里分享下经验,并且奉上整理好的脚本. Nuget包这里就不多介绍了,需 ...