继续把我们的设计模式捡起,希望我能坚持完这个系列吧,下面我们就进入正题吧。
在软件开发过程中,我们需要重复使用某个对象的时候,如果重复地new这个对象,不停地申请内存空间,会造成内存空间的极大浪费,在之后程序运行过程中也可能会产生大量的垃圾对象,给服务器的垃圾回收带来极大压力,那么我们从软件设计的角度该如何解决这个问题呢?单例模式就可以解决这个问题了。在之前的单例模式中我们提到“单例模式提供了一个全局访问点,来访问其唯一的实例对象”,单例模式强调系统中有且仅有唯一的实例对象。
更进一步,假如系统中就是需要创建多个(并不是无限制)相同或者相似(也有可能相同)的对象,那我们该如何处理呢?比如数据库连接,使用的时候不可能每次都创建和销毁,当然也不能使用单例只创建一个连接,负责处理所有的客户端请求,我们可以使用数据连接池技术,创建一定数量的连接的缓存,使用的时直接拿出来使用就可以了,这种模式从创建对象的角度来看也算是“享元模式”的一种典型应用,下面我们就来学习一下该模式。
定义:享元模式(FlyWeight Pattern)主要用来减少创建对象的数量,以减少内存占用,达到提高性能目的,这种模式也属于结构型设计模式,享元模式尝试复用现有的同类对象,如果未找到匹配对象,则创建新对象,此模式是一种专门为提升系统性能而生的设计模式。
要理解享元模式,先来了解两个概念,内部状态和外部状态:
内部状态:在享元对象内部不随外界环境改变而改变的共享部分;
外部状态:随着环境的改变而改变,不能功能构想的状态就是外部状态;
享元模式区分了内部状态和外部状态,所以我们可以通过设置不同的外部状态使得相同的对象可以具备一些不同的特性,而内部状态则设置为相同的共享部分。
享元模式结构图:
角色分析:
1、Flyweight:抽象的享元角色,通常是一个接口或者抽象类,在抽象享元角色中声明了具体享元角色中的公共方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)
2、ConcreteFlyweight:具体享元角色,继承或实现Flyweight接口,称为享元对象,通常结合单例模式来设计具体享元类,为每一个享元类提供唯一的享元对象;
3、UnsharedConcreteFlyweight:指那些不需要共享的Flyweight子类,它并不强制共享;
4、FlyweightFactory:用来创建并管理Flyweight对象,主要用来确保合理第共享Flyweight,当用户请求一个Flyweight时,FlyweightFactory工厂提供一个已经创建的实例或者新创建一个(如果不存在的话);
举例分析:
例如我们小时经常俄罗斯方块游戏,它每次落下来的图形都不一定相同,假如我们每次都new一个图形的话那么会占用大量内存,体验并不好;其实玩久了我们会发现,它每次落下来的图形就那么 几种,包括“L”型、“M”型、“Z”型、“S”型、“I”型等有限的几种类型,那么我们就可以将这有限的几种类型抽象出来,用享元模式来实现,为了更好地说明享元模式,再高级一点我们给这些图形还带上颜色,下面我们就来具体分析吧:
示例代码:
package cn.com.pep.model.flyweight;
/**
*
* @Title: AbstaractBox
* @Description: Flyweight:抽象享元角色,声明了具体享元角色中的方法,向外界提供享元对象的内部状态,同时也可以通过这些方法来设置对象的外部状态
* @author wwh
* @date 2022-9-13 14:14:22
*/
public abstract class AbstaractBox{
/**
* @Title: getShape
* @Description: 向外界提供享元对象的内部状态,即形状。
* @return
*/
public abstract String getShape();
/**
* @Title: display
* @Description: 通过此方法来设置对象的外部状态
* @param color
*/
public void display(String color) {
System.err.println("本次落下来的图形是:" + this.getShape() + ",颜色是:" + color);
}
}
package cn.com.pep.model.flyweight;
/**
*
* @Title: IBox
* @Description: 具体享元角色,为每一个享元类提供唯一的实例
* @author wwh
* @date 2022-9-13 14:23:28
*/
public class IBox extends AbstaractBox{
@Override
public String getShape() {
return "IBox";
}
}
package cn.com.pep.model.flyweight;
/**
*
* @Title: MBox
* @Description: 具体享元角色,为每一个享元类提供唯一的实例
* @author wwh
* @date 2022-9-13 14:25:04
*/
public class MBox extends AbstaractBox{
@Override
public String getShape() {
return "MBox";
}
}
package cn.com.pep.model.flyweight;
/**
*
* @Title: ZBox
* @Description: 具体享元角色,为每一个享元类提供唯一的实例
* @author wwh
* @date 2022-9-13 14:25:52
*/
public class ZBox extends AbstaractBox{
@Override
public String getShape() {
return "ZBox";
}
}
package cn.com.pep.model.flyweight;
import java.util.HashMap;
/**
*
* @Title: BoxFactory
* @Description:享元工厂,用来创建并管理Flyweight对象,当用户请求一个Flyweight对象时,FlyweightFactory工厂提供一个已经创建的实例或者新创建一个实例;
* @author wwh
* @date 2022-9-13 14:26:27
*/
public class BoxFactory {
/**
* 创建一个池,用来缓存需要共享的享元对象
*/
private static HashMap<String, AbstaractBox> map = new HashMap<>();
public BoxFactory() {
map.put("I", new IBox());
map.put("M", new MBox());
map.put("Z", new ZBox());
}
private static class SingtonHolder{
private static final BoxFactory INSTANCE = new BoxFactory();
}
/**
* @Title: getFactory
* @Description:
* @return
*/
public static final BoxFactory getFactory() {
return SingtonHolder.INSTANCE;
}
/**
* @Title: getBox
* @Description:
* @param box
* @return
*/
public AbstaractBox getBox(String box) {
if (map.containsKey(box)) {
return map.get(box);
}
return null;
}
}
package cn.com.pep.model.flyweight;
/**
*
* @Title: FlyweightPatternDemo
* @Description: 测试类
* @author wwh
* @date 2022-9-13 14:36:49
*/
public class FlyweightPatternDemo {
public static void main(String[] args) {
BoxFactory factory = BoxFactory.getFactory();
AbstaractBox box = factory.getBox("I");
box.display("红色");//传入外部状态--颜色
box.display("白色");
System.err.println(box);//打印“内部状态”
factory.getBox("I");
System.err.println(box);//再次打印“内部状态”
}
}
测试结果:
UML类图:
在上面这个例子中,图形的形状就是内部状态,而颜色我们就可以认为是外部状态。外部状态是相互独立的,而且不影响内部状态。
享元模式的优缺点和使用场景:
优点:极大地减少了内存中相似或者相同对象的数量,节约系统资源、提高系统性能;外部状态相互独立,不影响内部状态;
缺点:为了使对象可以共享,需要分离外部状态和内部状态,是程序逻辑复杂;
使用场景:
1、一个系统中有大量相同或者相似的对象,造成内存的大量耗费;
2、对象的大部分状态都可以外部化,可以将这些外部状态传入到对象中;
享元模式和单例模式比较:
单例模式和享元模式都可以减少系统中对象的创建数量,但是两者还有一些区别,主要包括以下方面的内容:
1、享元模式可以再次创建对象,也可以获取缓存的对象,单例模式严格控制单个进程中只有一个实例对象;
2、享元模式可以通过享元工厂实现对外部的单例,也可以在需要的时候创建更多的实例,单例模式是自身控制,需要增加不属于改对象本身的逻辑;
3、两者都可以实现节省对象的创建;
在JDK中的应用:
ThreadPool线程池、第三方提供的数据库连接池、JDK中的字符串常量池等都使用了享元模式、Integer中也有类似的代码;
public static Integer valueOf(int i) {
if (!$assertionsDisabled && IntegerCache.high < 127)
throw new AssertionError();
if (i >= -128 && i <= IntegerCache.high)
return IntegerCache.cache[i + 128];
else
return new Integer(i);
}
从这个例子我们可以看出当i>= -128 && i<=127的时候直接取缓冲池中缓存的对象,否则就直接new一个Integer对象返回。好了,本期也到了和大家说拜拜的时候了,小弟水平有限,还请各位大佬批评指正,共同进步!
本文来自博客园,作者:一只烤鸭朝北走,仅用于技术学习,所有资源都来源于网络,部分是转发,部分是个人总结。欢迎共同学习和转载,转载请在醒目位置标明原文。如有侵权,请留言告知,及时撤除。转载请注明原文链接:https://www.cnblogs.com/wha6239/p/16684572.html