看看用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个矩形测试:

  1. 先从工厂里分别创建出来并调用draw
  2. 打印出当前可见图元数量
  3. 移除所有
  4. 分别打印可见和不可见图元数量
  5. 从工厂里再次分别创建出5个并调用draw
  6. 再次分别打印可见和不可见图元数量
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设计模式之组合、享元的更多相关文章

  1. Java设计模式学习记录-享元模式

    前言 享元模式也是一种结构型模式,这篇是介绍结构型模式的最后一篇了(因为代理模式很早之前就已经写过了).享元模式采用一个共享来避免大量拥有相同内容对象的开销.这种开销最常见.最直观的就是内存损耗. 享 ...

  2. [设计模式] javascript 之 享元模式;

    享元模式说明 定义:用于解决一个系统大量细粒度对象的共享问题: 关健词:分离跟共享: 说明: 享元模式分单纯(共享)享元模式,以及组合(不共享)享元模式,有共享跟不共享之分:单纯享元模式,只包含共享的 ...

  3. 面向对象设计模式之Flyweight享元模式(结构型)

    动机:采用纯粹对象方案的问题在于大量细粒度的对象会很快充斥在系统中,从而带来很高的运行代价——主要指内存需求方面的代价.如何在避免大量细粒度对象问题的同 时,让外部客户程序仍然能够透明地使用面向对象的 ...

  4. Java设计模式之《享元模式》及应用场景

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6542449.html 享元模式:"享"就是分享之意,指一物被众人共享, ...

  5. 设计模式(11)--Flyweight(享元模式)--结构型

    作者QQ:1095737364    QQ群:123300273     欢迎加入! 1.模式定义: 享元模式是对象的结构模式.享元模式以共享的方式高效地支持大量的细粒度对象. 2.模式特点: 享元模 ...

  6. 重学 Java 设计模式:实战享元模式「基于Redis秒杀,提供活动与库存信息查询场景」

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 程序员‍‍的上下文是什么? 很多时候一大部分编程开发的人员都只是关注于功能的实现,只 ...

  7. 设计模式学习之享元模式(Flyweight,结构型模式)(20)

    转:http://terrylee.cnblogs.com/archive/2006/03/29/361767.html 摘要:面向对象的思想很好地解决了抽象性的问题,一般也不会出现性能上的问题.但是 ...

  8. 设计模式学习心得<享元模式 Flyweight>

    享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能.这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式. 享元模式尝 ...

  9. C#设计模式之十一享元模式(Flyweight Pattern)【结构型】

    一.引言 今天我们要讲[结构型]设计模式的第六个模式,该模式是[享元模式],英文名称是:Flyweight Pattern.还是老套路,先从名字上来看看.“享元”是不是可以这样理解,共享“单元”,单元 ...

随机推荐

  1. 编写Node.js原生扩展

    Node.js是一个强大的平台,理想状态下一切都都可以用javascript写成.然而,你可能还会用到许多遗留的库和系统,这样的话使用c++编写Node.JS扩展会是一个不错的注意. 以下所有例子的源 ...

  2. 二分查找 - vb.net

    Module Module1    Sub Main()        Dim array(999) As Integer        Dim searchValue As Integer      ...

  3. HDU1864(背包)

    最大报销额 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submi ...

  4. ArcGIS制图技巧系列(2)地形渲染

    ArcGIS制图技巧系列(2)地形渲染 by 李远祥 DEM数据是常见的地形数据,在GIS常规的制图中,DEM一直扮演着增强效果.由于带有高程值,DEM在很多情况下都在三维中显示,但这里主要介绍的是在 ...

  5. The type or namespace name '****' could not be found

    偶尔会在编译时出现“The type or namespace name '****' could not be found (are you missing a using directive or ...

  6. 调用win32 api 函数SendMessage() 实现消息直接调用

    简单的调用例子, 适合初学者学习,当然 我也是初学者. #include <windows.h> #include <stdio.h> #include <stdlib. ...

  7. C#下控制台程序窗口下启用快速编辑模式运行线程会阻止线程运行

    最近做一个小的功能,使用C#控制台程序开启一个线程进行无限循环没5秒处理一次程序,发现控制台窗口在开启快速编辑模式情况下,进行选择程序打印 出来的文字后发现线程不走了,将快速编辑模式去除后,线程就不会 ...

  8. IP分类以及特殊IP

     一.IP分类 点分十进制数表示的IPv4 地址分成几类,以适应大型.中型.小型的网络.这些类的不同之处在于用于表示网络的位数与用于表示主机的位数之间的差别.IP地址分成五类,用字母表示:       ...

  9. static的加载先后顺序

    1.静态变量的声明和赋值是分开的,静态变量会先被声明,赋值操做被放在了静态代码块中. 2.静态变量的赋值和静态代码块的执行顺序和代码的先后书写顺序相关. 3.静态代码块优先执行,其次构造方法,最后普通 ...

  10. NodeJS 框架一览

    Express 当你使用Node.js构建web应用程序时, Express通常被视为事实上的Web服务器.它的哲学(一个可以使用中间件包扩展的简约核心)是大多数Node.js开发者所熟悉的. Koa ...