Java设计模式之享元模式(Flyweight)

一、概述

举两个例子。

例子1, A找牛二做了一个轻量小型门户网站,牛二花了几天做好后,租了服务器a,完工。后来,B看到了,觉得不错,也找牛二做了一个这样的网站,牛二就复制了之前的那份代码,租了服务器b,完工。后来C、D......

例子2, 做一个围棋游戏,围棋由许许多多的黑子和白子组成,面向对象的思想,每个棋子都是一个对象,这样就需要生成许许多多的对象。A玩这个游戏,生成了n个棋子,如果这时B也玩这个游戏,又要生成n个棋子......

从上面两个例子可以看出什么问题呢?

 1. 资源过度浪费。

 2. 重复代码过多,冗余严重。

 3. 维护困难。

这个时候不禁想,如果资源可以共享就好了,反正都是一样的“东西”,就像网盘上的共享文件一样,需要的时候去下载一下就好,每个人都可以去下载,但共享文件只有一份,这样不是更好。

那么如何做到共享呢?

二、享元模式

1. 定义

享元模式,运用共享技术有效地支持大量细粒度的对象。(引自《大话设计模式》)

2. 说明

其实享元模式就是运用共享技术有效地支持大量细粒度对象的复用。对于相似或相同的享元对象我们可以重复使用,我们将这些对象存储的地方称之为享元池。这样,相同的对象在共享池中只存在一个,用的时候从共享池中取即可。避免资源的浪费。

这里面涉及到两个概念,内部状态和外部状态。

内部状态(Intrinsic State):在享元对象内部且不会随着环境的改变而改变的状态,是可以共享的部分。

外部状态(Extrinsic State):会随着环境改变而改变的状态,且不可共享的部分。

共享模式就是为了解决程序中大量生成细粒度的类实例来表示数据,当这些对象除了少数几个参数外基本上都是相同的时候,我们就可以设计成共享模式,将相同的参数设为类对象的内部状态,个别不同的参数设为类对象的外部状态,在方法调用时再传进来,这样就达到了共享的目的,可以减少生成大量的实例。

3. 基本结构图

Flyweight:享元类。可以为抽象类或接口,声明了具体享元类需要实现的方法,通过这个类可以向外界提供享元对象的内部数据(内部状态) ,也可以接受并作用外部数据(外部状态)。在享元类中,通常将内部状态(intrinsicState)和外部状态(extrinsicState)分开处理,内部状态作为享元类的内部成员,外部状态通过注入的方式添加到享元类中。

ConcreteFlyweight:具体享元类,继承Flyweight抽象类或实现Flyweight接口,为内部状态增加存储空间。

UnsharedConcreteFlyweight:非共享具体享元类,指那些不需要共享的Flyweight子类。这里并不强制共享,如果不需要共享时可通过非共享具体享元类创建对象。

FlyweightFactory:享元工厂类,就是一个工厂类,用来创建并管理Flyweight对象。结合了工厂模式的设计,主要是用来确保合理地共享Flyweight,当用户请求一个Flyweight时,FlyweightFactory对象提供一个已创建的实例或者创建一个(如果不存在的话)。

4. 基本代码

Flyweight,共享抽象类,这里包含了内部状态和外部状态,当有多个内部状态时,可以通过实体类的方式引入。

public abstract class Flyweight {
	//内部状态
	protected String intrinsicState;
	
	public Flyweight(String intrinsicState) {
		this.intrinsicState = intrinsicState;
	}
	
	//外部状态通过注入的方式传入共享类中
	public abstract void operation(String extrinsicState);
}

ConcreteFlyweight

public class ConcreteFlyweight extends Flyweight {
		
	public ConcreteFlyweight(String intrinsicState) {
		super(intrinsicState);
	}
		
	@Override
	public void operation(String extrinsicState) {
		System.out.println("具体享元类,内部状态:"+intrinsicState+",外部状态:"+extrinsicState);
	}

}

UnsharedConcreteFlyweight

public class UnsharedConcreteFlyweight extends Flyweight {
			
	public UnsharedConcreteFlyweight(String intrinsicState) {
		super(intrinsicState);
	}

	@Override
	public void operation(String extrinsicState) {
		System.out.println("不共享的具体享元类,内部状态:"+intrinsicState+",外部状态:"+extrinsicState);
	}

}

FlyweightFactory

public class FlyweightFactory {
	//通过map存储共享对象,实现享元池
	private Map flyweights = new HashMap();
	
	public Flyweight getFlyweight(String key) {
		//如果对象存在,则直接从享元池中取出返回
		if(flyweights.containsKey(key)) {
			return (Flyweight) flyweights.get(key);
		} else {//如果对象不存在,则创建新对象返回,并将新对象存储到享元池中
			Flyweight f = new ConcreteFlyweight(key);
			flyweights.put(key, f);
			return f;
		}
	}
	
	public void count() {
		System.out.println("享元池的大小:"+flyweights.size());
	}
}

测试类

public class FlyweightTest {

	public static void main(String[] args) {
		//创建工厂
		FlyweightFactory factory = new FlyweightFactory();
		
		//生成一个内部状态为inside、外部状态为outside的共享对象
		Flyweight fw = factory.getFlyweight("inside");
		fw.operation("outside");
		
		//生成一个内部状态为inside、外部状态为outside2的共享对象
		Flyweight fw2 = factory.getFlyweight("inside");
		fw2.operation("outside2");
		
		//生成一个内部状态为inside2、外部状态为outside的共享对象
		Flyweight fw3 = factory.getFlyweight("inside2");
		fw3.operation("outside2");
		
		//共享池中共享对象的个数
		factory.count();
	}

}

结果

通过这个结果可以很清晰地看出,享元对象的个数与内部状态有关,而与外部状态无关。也就是说,当内部状态相同时,我们不会重复实例化对象,直接从共享池中取出。

三、实战应用

就用上面的网站的例子吧。

先分析一下内部状态和外部状态,网站的类型是内部状态,如oa系统、博客等,因为所有的oa系统可以用同一个模板,所有的博客网站可以用同一个模板。网站的账户是外部状态,这个很好理解,满足不同的用户嘛。

外部状态类User,这里将外部状态独立出来,方便设置多个外部状态属性,这里只包含“网站的账户名”这一个属性。

/**
 * 外部状态类
 */
public class User {
	private String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

网站抽象类Website,内部状态为抽象类的属性,外部状态从外部注入。

public abstract class Website {
	//内部状态
	protected String type;
	
	public Website(String type) {
		this.type = type;
	}
	
	//外部状态通过注入的方式传入共享类中
	public abstract void operation(User user);
}

具体网站实现类

public class ConcreteWebsite extends Website {
		
	public ConcreteWebsite(String type) {
		super(type);
	}
		
	@Override
	public void operation(User user) {
		System.out.println("具体享元类,内部状态:"+type+",外部状态:"+user.getName());
	}

}

网站工厂类

public class WebsiteFactory {
	//通过map存储共享对象,实现享元池
	private Map flyweights = new HashMap();
	
	public Website getWebsite(String key) {
		//如果对象存在,则直接从享元池中取出返回
		if(flyweights.containsKey(key)) {
			return (Website) flyweights.get(key);
		} else {//如果对象不存在,则创建新对象返回,并将新对象存储到享元池中
			Website website = new ConcreteWebsite(key);
			flyweights.put(key, website);
			return website;
		}
	}
	
	public void count() {
		System.out.println("享元池的大小:"+flyweights.size());
	}
}

测试类

public class FlyweightTest {

	public static void main(String[] args) {
		//创建工厂
		WebsiteFactory factory = new WebsiteFactory();
		User user = new User();
		
		//获取类型(内部状态)为"oa系统"的共享对象
		Website oa1 = factory.getWebsite("oa系统");
		user.setName("小A");//设置用户名(外部状态)为小A
		oa1.operation(user);
		
		//获取类型(内部状态)为"oa系统"的共享对象
		Website oa2 = factory.getWebsite("oa系统");
		user.setName("小B");//设置用户名(外部状态)为小B
		oa2.operation(user);
		
		//获取类型(内部状态)为"oa系统"的共享对象
		Website oa3 = factory.getWebsite("oa系统");
		user.setName("小C");//设置用户名(外部状态)为小C
		oa3.operation(user);		
		
		//同理,获取类型为"博客"的共享对象
		Website c1 = factory.getWebsite("博客");
		user.setName("大a");//设置用户名(外部状态)为小a
		c1.operation(user);
		
		Website c2 = factory.getWebsite("博客");
		user.setName("大b");
		c2.operation(user);
		
		Website c3 = factory.getWebsite("博客");
		user.setName("大c");
		c3.operation(user);
	
		
		//共享池中共享对象的个数
		factory.count();
	}

}

结果

从结果可以看出,相同内部状态的享元对象只有一个,即使外部状态不同也不会多次创建。

四、总结

1. 使用场景

  • 当一个系统中存在大量相似或相同的对象,并且这些大量的对象可以对系统造成很大的开销。
  • 当对象的大多数状态为外部状态,且如果删除这些外部状态后,大多数对象变得相同,可以用一个共享对象取代时,可以考虑享元模式。

2. 总结

  • 享元模式可以减少系统中对象的数量,节省资源和空间。
  • 享元模式区分内部状态和外部状态,需要仔细区分哪些是外部状态,哪些是内部状态,具有一定难度和复杂度。
  • 外部状态相对独立,即使更改了也不会影响到内部状态。
  • 内部状态相对稳定,属于共享部分,存储于享元对象内部。外部状态属于变化部分,由外部注入。
  • 享元模式的核心在于共享工厂,由工厂来进行合理地创建共享对象。

 

 

写在最后,

本文主要是小猫看了《大话设计模式》的一些记录笔记,再加之自己的一些理解整理出此文,方便以后查阅,仅供参考。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
项目号 版本 OA系统 概要设计说明书 2017年 5 月 20 日 版本历史 "日期 "版本 "说明 "作者 " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " 目 录 1. 引言 4 1.1. 编写目的 4 1.2. 项目背景 4 1.3. 适用范围 4 1.4. 术语和缩略语 4 1.5. 参考资料 5 2. 总体设计 5 2.1. 运行环境 5 2.1.1. 硬件环境 5 2.1.2. 软件环境 5 2.2. 开发环境 5 2.2.1. 硬件环境 5 2.2.2. 软件环境 6 2.3. 总体结构设计 6 2.3.1. 系统体系架构 6 2.3.2. 系统功能架构 7 2.3.3. 系统部署架构 7 2.3.4. 系统软件架构 8 3. 系统功能设计 8 3.1. 功能模块列表 8 3.2. 系统菜单 9 3.3. 个人事项 10 3.3.1. 待办公文 10 3.3.2. 已办公文 12 3.3.3. 待阅公文 12 3.4. 公文起草 12 3.4.1. 外部来文登记 12 3.4.2. 司内申请 12 3.4.3. 司内请示 13 3.4.4. 报送办领导 13 3.4.5. 草稿箱 13 3.5. 基础管理 13 3.5.1. 类型设置 13 3.5.2. 流程设置 15 3.5.3. 文单设置 15 3.5.4. 文号设置 16 3.5.5. 批示设置 16 3.6. 系统管理 17 3.6.1. 功能需求用例 17 3.6.2. 用例描述 17 3.6.2.1. 用户 17 3.6.2.2. 组织机构 18 3.6.2.3. 部门 18 3.6.2.4. 用户 19 3.6.2.5. 组 19 3.6.2.6. 权限 19 3.6.2.7. 角色 19 3.6.2.8. 功能模块 20 3.6.2.9. 功能权限 20 3.6.2.10. 页面元素权限 21 3.6.3. 用户管理 21 3.6.4. 角色管理 21 3.6.5. 权限管理 21 3.6.6. 组织管理 21 3.6.7. 菜单管理 21 3.6.8. 字典管理 21 3.6.9. 自定义列表 22 3.6.10. 自定义菜单 22 4. 系统接口设计 22 4.1.1. 接口1 22 4.1.2. 接口2 22 5. 数据结构设计 22 6. 系统出错处理 24 6.1. 出错信息 24 6.2. 补救措施 25 6.3. 系统维护设计 25 7. 性能与安全等设计 26 7.1. 系统性能 26 7.2. 系统安全 27 引言 1 编写目的 OA系统概要设计说明书是在客户提出的需求基础上,经过对OA系统需求规格说明书和 系统原型的分析和系统设计编写而成。用于将系统相关需求转换为未来系统的设计,提 交软件研发部门相关实现团队作为系统研发的依据和指南。 2 项目背景 根据电子公文管理总体要求,在现有信息化OA系统建设的基础上,要求完善电子公 文办理系统和交换系统,建设支持大并发和多级管理模式的协同办公平台,实现高效的 业务协作和信息共享,增强电子文件的完整性、可靠性、可用性和安全。基于此现状, 万达公司根据自动化系统总体要求,拟建设"统一规范、稳定安全、协同共享"的OA管理 系统,实现电子文件管理规范与信息化的全面融合,本文是基于对于新版OA管理信息系 统的基本设想和总体需求的理解基础上,形成的设计文档 3 适用范围 本文档适用于所有与本项目有关的软件设计、开发阶段相关人员,主要包括项目组成 员、研发经理、开发人员,项目管理人员,测试人员以及在以后想对系统进行扩展和维 护的相关人员等。 4 术语和缩略语 "术语、缩略语 "说明 " " " " " " " 5 参考资料 《软件需求规格说明书》 《OA系统原型》 总体设计 1 运行环境 1 硬件环境 1) 应用服务器: 2) 数据库服务器: 2 软件环境 1) 操作系统: Radhat Linux 6.0 数据库系统: ORACLE10I 中间件:Tomcat、Jboss 通信协议:http 客户端浏览器:IE9 2 开发环境 1 硬件环境 1) 应用服务器: 2) 数据库服务器: 2 软件环境 1) 操作系统:Win2003、Win7 2) 数据库系统:ORACLE10I 3) 开发平台及工具:炎黄Eclipse 4) 通信协议:http: 5) 客户端浏览器:IE9以上、Chrome 3 总体结构设计 1 系统体系架构 图2-1 OA系统体系架构图 2 系统功能架构 图2-2 OA系统功能架构图 3 系统部署架构 4 系统软件架构 5 核心业务对象 6 核心业

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值