深入理解Java设计模式之享元模式
作者:一指流砂~ 发布时间:2023-11-10 17:20:51
一、引言
大家都知道单例模式,通过一个全局变量来避免重复创建对象而产生的消耗,若系统存在大量的相似对象时,又该如何处理?参照单例模式,可通过对象池缓存可共享的对象,避免创建多对象,尽可能减少内存的使用,提升性能,防止内存溢出。
在软件开发过程,如果我们需要重复使用某个对象的时候,如果我们重复地使用new创建这个对象的话,这样我们在内存就需要多次地去申请内存空间了,这样可能会出现内存使用越来越多的情况,这样的问题是非常严重,然而享元模式可以解决这个问题,下面具体看看享元模式是如何去解决这个问题的。
二、什么是享元模式
定义:共享元对象,运用共享技术有效地支持大量细粒度对象的复用。如果在一个系统中存在多个相同的对象,那么只需要共享一份对象的拷贝,而不必为每一次使用创建新的对象。
享元模式是为数不多的、只为提升系统性能而生的设计模式,主要作用就是复用大对象(重量级对象),以节省内存空间和对象创建时间。
面向对象可以非常方便的解决一些扩展性的问题,但是在这个过程中系统务必会产生一些类或者对象,如果系统中存在对象的个数过多时,将会导致系统的性能下降。对于这样的问题解决最简单直接的办法就是减少系统中对象的个数。享元模式提供了一种解决方案,使用共享技术实现相同或者相似对象的重用。也就是说实现相同或者相似对象的代码共享。
所谓享元模式就是运行共享技术有效地支持大量细粒度对象的复用。系统使用少量对象,而且这些都比较相似,状态变化小,可以实现对象的多次复用。
共享模式是支持大量细粒度对象的复用,所以享元模式要求能够共享的对象必须是细粒度对象。
首先了解两个概念:内部状态、外部状态。
内部状态:在享元对象内部不随外界环境改变而改变的共享部分。
外部状态:随着环境的改变而改变,不能够共享的状态就是外部状态。
由于享元模式区分了内部状态和外部状态,所以我们可以通过设置不同的外部状态使得相同的对象可以具备一些不同的特性,而内部状态设置为相同部分。
在我们的程序设计过程中,我们可能会需要大量的细粒度对象来表示对象,如果这些对象除了几个参数不同外其他部分都相同,这个时候我们就可以利用享元模式来大大减少应用程序当中的对象。
如何利用享元模式呢?这里我们只需要将他们少部分的不同的部分当做参数移动到类实例的外部,然后在方法调用的时候将他们传递过来就可以了。这里也就说明了一点:内部状态存储于享元对象内部,而外部状态则应该由客户端来考虑。
三、享元模式的结构
1.Flyweight
: 享元接口,所有具体享元类的超类或接口,通过该接口Flyweight可以接受并作用于外部状态。通过该接口可以传入外部的状态,在享元对象的方法处理中可能会使用这些外部的数据。
2.ConcreteFlyweight
: 具体的享元实现对象,指定内部状态,必须是共享的,需要封装Flyweight的内部状态。
3.UnshareConcreteFlyweight:
非共享的享元实现对象,并不是所有的Flyweight实现对象都需要共享。非共享的享元实现对象通常是对享元对象的组合对象。
4.FlyweightFactoty
: 享元工厂类,主要用来创建并管理共享的享元对象,并对外提供访问共享享元的接口。当用户请求一个Flyweight时,FlyweightFactory就会提供一个已经创建的Flyweight对象或者新建一个(如果不存在)。
5.Client
: 享元客户端,主要的工作就是维持一个对Flyweight的引用,计算或存储享元的外部状态,当然这里可访问共享和不共享的Flyweight对象。
享元模式的核心在于享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池,用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。
四、享元模式和单例模式的异同
享元模式可以再次创建对象 也可以取缓存对象
单例模式则是严格控制单个进程中只有一个实例对象
享元模式可以通过自己实现对外部的单例 也可以在需要的使用创建更多的对象
单例模式是自身控制 需要增加不属于该对象本身的逻辑
两者都可以实现节省对象创建的时间 ThreadPool 线程池 与数据库连接池 都有使用享元模式
五、享元模式的优缺点
优点:
可以极大减少内存中对象的数量,使得相同或相似对象在内存中只保存一份,从而可以节约系统资源,提高系统性能。
享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。
缺点:
享元模式使得系统变得复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化。
为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长。
六、享元模式的使用场景
一个系统有大量相同或者相似的对象,造成内存的大量耗费。
对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。
在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源,因此,应当在需要多次重复使用享元对象时才值得使用享元模式。
七、享元模式的实现
public abstract class abStudent
{
public string Name;
public string schName;
public string Sex;
public abStudent()
{
schName = "吉林大学";
Sex = "男";
}
public override string ToString()
{
return string.Format("我叫{0},性别{1},在读学校{2}", Name, Sex, schName);
}
}
public class Student:abStudent
{
public Student(string name)
{
Name = name;
}
}
public class School
{
private Dictionary<int, Student> StudentList;
public School()
{
StudentList = new Dictionary<int, Student>();
StudentList.Add(1, new Student("张三"));
StudentList.Add(2, new Student("李四"));
}
public Student GetStudent(int num)
{
return StudentList[num] as Student;
}
}
class Program
{
static void Main(string[] args)
{
School school = new School();
Student student = school.GetStudent(1);
Console.WriteLine(student.ToString());
student = school.GetStudent(2);
Console.WriteLine(student.ToString());
Console.ReadKey();
}
}
八、总结
1、享元模式可以极大地减少系统中对象的数量。但是它可能会引起系统的逻辑更加复杂化。
2、享元模式的核心在于享元工厂,它主要用来确保合理地共享享元对象。
3、内部状态为不变共享部分,存储于享元享元对象内部,而外部状态是可变部分,它应当油客户端来负责。
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注脚本之家的更多内容!
来源:https://www.cnblogs.com/xuwendong/p/10414863.html


猜你喜欢
- android获取ibeacon列表,供大家参考,具体内容如下最近公司有需要做ibeacon需求。因为涉及扫码的时间。特意写一个servic
- 本文实例主要进行java Timer(定时调用、固定时间执行)测试,具体实现代码如下。测试1当任务执行时间小于重复执行的间隔时间代码:pub
- 1、SDK下载很慢。配置SDK代理,速度像飞一样。建议先把20-24下完,不然后面遇到很多问题。2、support-v7的问题例如res\v
- 这篇文章主要介绍了Java类加载器ClassLoader用法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值
- Settings是WebView提供给上层App的一个配置Webview的接口,每个WebView都有一个WebSettings,要控制We
- 1.注解方式,yml文件配置上以下就可以直接使用mybatis-plus: mapper-locations: classpath:mapp
- 介绍MVC(Model-View-Controller)是一种软件架构模式,其中应用程序被划分为三个部分:模型(Model)、视图(View
- AsyncTask是一个很常用的API,尤其异步处理数据并将数据应用到视图的操作场合。其实AsyncTask并不是那么好,甚至有些糟糕。本文
- progressDialog, 它有两个方法dialog.cancel() 和 dialog.dimiss()1. public void
- 折半查找法仅适用于对已有顺序的数组、数据进行操作!!!(从小到大)自我总结:折半查找法就是相当于(通过改变low或high的大小)把中间位置
- 前言随着微软对C#不断发展和更新,C#中对于数组操作的方式也变得越来越多样化。以往要实现过滤数组中的空字符串,都是需要实行循环的方式来排除和
- 在使用WPF开发的时候就不免会遇到需要两个窗口间进行传值操作,当然多窗口间传值的方法有很多种,本文介绍的是使用委托实现多窗口间的传值。在上代
- 新建线程新建线程很简单。只需要使用new关键字创建一个线程对象,然后调用它的start()启动线程即可。Thread thread1 = n
- 英文设置加粗可以在xml里面设置: <SPAN style="FONT-SIZE: 18px">androi
- 项目里使用了Feign进行远程调用,有时为了问题排查,需要开启请求和响应日志下面简介一下如何开启Feign日志:注:本文基于spring-b
- 本文实例为大家分享了C#实现文字转语音的具体代码,供大家参考,具体内容如下客户提出要求,将文字内容转为语音,因为内网环境,没办法采用联网,在
- Maven打包时指定启动类使用Maven打包的时候, 有时候需要指定启动类, 可如下操作!测试项目(结构如下):代码: com.xxx.Ma
- 一、概述并查集:一种树型数据结构,用于解决一些不相交集合的合并及查询问题。例如:有n个村庄,查询2个村庄之间是否有连接的路,连接2个村庄两大
- Overview在今天的开发学习中,我遇到了一个需求是在App的flash页面添加bing每日一图。这些都简单,但是当我获取到了图片的Url
- 1、什么是GOCW 为了解决在Csharp下编写OpenCV程序的问题,我做过比