常用设计模式系列(十二)—享元模式
第一节
前言
昏昏沉沉的两天过去了,也不知道为什么,突然总觉得很困,可能之前熬夜熬的多了,所以现在可能年纪大了,需要蹦一蹦才能把自己从颓废的边缘拉扯回来,伸出你的手,让我们向于和伟老师学习,来个养生迪(斗图时刻)。
蹦完有没有觉得贼精神,精神恢复,我们继续讲解我们的结构型设计模式,今天讲解结构型设计模式第五章节—“享元模式”,“享元”和“外观”一样让人琢磨不透,就像你追你爱慕的女孩的时候,和在一起之后的对比,虽然两段关系不太一样,但是她的心思,那可不是你能一眼猜透的;如何破冰?还是要深入了解,才能更有效的掌握核心信息。那么,“享元”如何代入呢?
可以这么理解,将一个系统中共同的内容抽象出来,多个使用者共用,减少共用内容的冗余,例如:现在国家倡导绿色出行,十个人都拥有自己的私家车,但是国家提供地铁,可以运输几百个人,这样把大家的交通工具抽象出来,抽象为公共交通,减少了街上车子的数量,减少了道路上车子的冗余,这个模式就是享元模式,“将公共元素进行抽象,然后共享这些公共元素”即享元。
第二节
享元模式概念
享元模式(Flyweight
Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式,享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。是不是听起来有点像单例模式?
1.实现方式不一样,单例是一个类只有一个唯一的实例,而享元可以有多个实例,只是通过一个共享容器来存储不同的对象。
2.使用场景不一样,单例是强调减少实例化提升性能,因此一般用于一些需要频繁创建和销毁实例化对象或创建和销毁实例化对象非常消耗资源的类中,如连接池、线程池。而享元则是强调共享相同对象或对象属性,节约内存使用空间。
在系统运行时有大量对象的情况下,有可能会造成堆内存溢出,我们把其中共同的部分抽象出来,抽象为公用对象,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。我们常用的线程池、数据库连接池、常量池等等相关,防止堆内存中对象被重复多次的无限制的创建,进行高效利用,哪里需要往哪里搬。
目前在我们身边,共享的产品已经渗入我们身边,共享单车、共享汽车、共享充电宝,当共享产品的厂家提供了共享产品时,就意味着这个产品开始了它的轮班生涯,例如共享厂家提供了一个充电宝,我们从这个充电宝的角度去看,第一次用充电宝,如果没有,商家补货,有了就拿走用,这个充电宝属于共享池中,每个厂家规定的派发出去的也是有数量的,不能一直无限的扩展,就意味着池子的大小有限制,如果全部派发出去了,那你就需要等别人还回来才能使用,这些共享产品都统一管理起来在一个池子进行分配,达到共享的目的,这个模式就是享元模式。
此图中抽象充电宝属于抽象享元角色,充电宝A和B是具体享元角色,品牌属于不需要抽象的,传递给享元角色使用,则为非享元角色,充电宝池则为享元工厂。
第三节
代码实现
1.创建非享元对象-品牌
package com.yang.flyweight;
/**
* @ClassName Power
* @Description 非享元对象——品牌
* @Author IT小白架构师之路
* @Date 2020/12/24
* @Version 1.0
**/
public class Version {
//品牌名称
private String name;
public Version(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
2.创建充电宝抽象对象
package com.yang.flyweight;
/**
* @ClassName AbstractRecharge
* @Description 抽象充电宝对象
* @Author IT小白架构师之路
* @Date 2020/12/24
* @Version 1.0
**/
public abstract class AbstractRecharge {
//品牌属性
protected Version version;
//状态 0闲置 1使用中
private String status;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public AbstractRecharge(String status,Version version){
this.status = status;
this.version = version;
}
/**
* 充电方法
*/
public abstract void recharge();
}
3.创建A类充电宝
package com.yang.flyweight;
/**
* @ClassName Recharge
* @Description 充电宝类A
* @Author IT小白架构师之路
* @Date 2020/12/24
* @Version 1.0
**/
public class RechargeA extends AbstractRecharge{
public RechargeA(String status,Version version){
super(status,version);
}
/**
* 充电方法
*/
public void recharge(){
System.out.println("我是"+super.version.getName()+"充电宝,我可以充电");
}
}
4.创建B类充电宝
package com.yang.flyweight;
/**
* @ClassName RechargeB
* @Description B类
* @Author IT小白架构师之路
* @Date 2020/12/24
* @Version 1.0
**/
public class RechargeB extends AbstractRecharge{
public RechargeB(String status,Version version){
super(status,version);
}
/**
* 充电方法
*/
public void recharge(){
System.out.println("我是"+super.version.getName()+"充电宝,我可以充电");
}
}
5.创建充电宝池(类似数据库连接池的方式,自动扩容)
package com.yang.flyweight;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName RechargeFactory
* @Description 充电宝池子
* @Author IT小白架构师之路
* @Date 2020/12/24
* @Version 1.0
**/
public class RechargeFactory {
/**
* 充电宝池子最大数量
*/
private static int maxNum = 5;
/**
* 初始化数量
*/
private static int initNum = 2;
private static List<AbstractRecharge> list = new ArrayList<AbstractRecharge>();
static {
for (int i=0;i<initNum;i++){
if(i%2==0){
Version version = new Version("A品牌");
list.add(new RechargeA("0",version));
}else{
Version version = new Version("B品牌");
list.add(new RechargeB("0",version));
}
}
System.out.println("充电宝数量初始化成功");
}
//获取对象
public static AbstractRecharge getRecharge() throws Exception{
//总数大小
int size = list.size();
System.out.print("充电宝总数大小"+size+",");
//查看闲置大小
getHasNum();
//是否需要等待
boolean wait = true;
while (true){
boolean haveStatus = false;
for (AbstractRecharge recharge : list){
//有闲置的对象,直接返回
if("0".equals(recharge.getStatus())){
haveStatus = true;
wait = false;
recharge.setStatus("1");
System.out.println("有可用充电宝,请取走");
return recharge;
}
}
if(!haveStatus){
if (list.size()>=maxNum){
System.out.println("目前无充电宝可用,等待2s");
Thread.sleep(2000);
}else{
//新增对象并返回
AbstractRecharge recharge;
if (size%2==0){
Version version = new Version("A品牌");
recharge = new RechargeA("0",version);
}else{
Version version = new Version("B品牌");
recharge = new RechargeB("0",version);
}
wait = false;
recharge.setStatus("1");
list.add(recharge);
return recharge;
}
}
}
}
/**
* 归还充电宝
* @param recharge
*/
public static void backRecharge(AbstractRecharge recharge){
//置闲
recharge.setStatus("0");
System.out.println("充电结束,充电宝已归还");
}
/**
* 获取闲置数量
*/
public static void getHasNum() {
int num = 0;
for (AbstractRecharge recharge : list){
//有闲置的对象,直接返回
if("0".equals(recharge.getStatus())){
num++;
}
}
System.out.println("目前闲置数量"+num);
}
}
6.创建runbale用来测试,执行充电方法
package com.yang.flyweight;
/**
* @ClassName RechaegeRunnable
* @Description 注释
* @Author IT小白架构师之路
* @Date 2020/12/24
* @Version 1.0
**/
public class RechargeRunnable implements Runnable{
//传入充电宝对象
private AbstractRecharge recharge;
private int i;
public RechargeRunnable(AbstractRecharge recharge,int i){
this.recharge = recharge;
this.i = i;
}
@Override
public synchronized void run() {
try {
Thread.sleep(5000);
//调用充电方法
recharge.recharge();
int num = i+1;
//归还充电宝
RechargeFactory.backRecharge(recharge);
System.out.println("第"+num+"个线程执行完毕");
}catch (Exception e){
e.printStackTrace();
}
}
}
- 创建客户端测试,创建20个线程测试
package com.yang.flyweight;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName Client
* @Description 注释
* @Author IT小白架构师之路
* @Date 2020/12/24
* @Version 1.0
**/
public class Client {
public static void main(String[] args) throws Exception{
List<Thread> threadList = new ArrayList<Thread>();
//20个线程同时处理
for (int i=0;i<20;i++){
//获取对象
AbstractRecharge recharge = RechargeFactory.getRecharge();
//创建新的线程去使用
RechargeRunnable runnable = new RechargeRunnable(recharge,i);
//启动线程
Thread thread = new Thread(runnable);
thread.start();
threadList.add(thread);
}
//设置当子线程处理完再执行主线程
for (Thread temp:threadList){
temp.join();
}
System.out.print("结束测试,");
RechargeFactory.getHasNum();
}
}
8.测试结果如下,使用享元模式对公用对象进行抽象
充电宝数量初始化成功
充电宝总数大小2,目前闲置数量2
有可用充电宝,请取走
充电宝总数大小2,目前闲置数量1
有可用充电宝,请取走
充电宝总数大小2,目前闲置数量0
充电宝总数大小3,目前闲置数量0
充电宝总数大小4,目前闲置数量0
充电宝总数大小5,目前闲置数量0
目前无充电宝可用,等待2s
目前无充电宝可用,等待2s
目前无充电宝可用,等待2s
我是A品牌充电宝,我可以充电
充电结束,充电宝已归还
我是B品牌充电宝,我可以充电
充电结束,充电宝已归还
第4个线程执行完毕
我是A品牌充电宝,我可以充电
充电结束,充电宝已归还
第1个线程执行完毕
我是B品牌充电宝,我可以充电
充电结束,充电宝已归还
第2个线程执行完毕
我是A品牌充电宝,我可以充电
充电结束,充电宝已归还
第5个线程执行完毕
第3个线程执行完毕
有可用充电宝,请取走
充电宝总数大小5,目前闲置数量4
有可用充电宝,请取走
充电宝总数大小5,目前闲置数量3
有可用充电宝,请取走
充电宝总数大小5,目前闲置数量2
有可用充电宝,请取走
充电宝总数大小5,目前闲置数量1
有可用充电宝,请取走
充电宝总数大小5,目前闲置数量0
目前无充电宝可用,等待2s
目前无充电宝可用,等待2s
目前无充电宝可用,等待2s
我是A品牌充电宝,我可以充电
充电结束,充电宝已归还
第6个线程执行完毕
我是B品牌充电宝,我可以充电
充电结束,充电宝已归还
第7个线程执行完毕
我是B品牌充电宝,我可以充电
充电结束,充电宝已归还
我是A品牌充电宝,我可以充电
充电结束,充电宝已归还
我是A品牌充电宝,我可以充电
充电结束,充电宝已归还
第10个线程执行完毕
第9个线程执行完毕
第8个线程执行完毕
有可用充电宝,请取走
充电宝总数大小5,目前闲置数量4
有可用充电宝,请取走
充电宝总数大小5,目前闲置数量3
有可用充电宝,请取走
充电宝总数大小5,目前闲置数量2
有可用充电宝,请取走
充电宝总数大小5,目前闲置数量1
有可用充电宝,请取走
充电宝总数大小5,目前闲置数量0
目前无充电宝可用,等待2s
目前无充电宝可用,等待2s
目前无充电宝可用,等待2s
我是A品牌充电宝,我可以充电
充电结束,充电宝已归还
我是A品牌充电宝,我可以充电
充电结束,充电宝已归还
我是B品牌充电宝,我可以充电
充电结束,充电宝已归还
第12个线程执行完毕
我是A品牌充电宝,我可以充电
充电结束,充电宝已归还
第13个线程执行完毕
我是B品牌充电宝,我可以充电
充电结束,充电宝已归还
第11个线程执行完毕
第14个线程执行完毕
第15个线程执行完毕
有可用充电宝,请取走
充电宝总数大小5,目前闲置数量4
有可用充电宝,请取走
充电宝总数大小5,目前闲置数量3
有可用充电宝,请取走
充电宝总数大小5,目前闲置数量2
有可用充电宝,请取走
充电宝总数大小5,目前闲置数量1
有可用充电宝,请取走
我是A品牌充电宝,我可以充电
充电结束,充电宝已归还
我是B品牌充电宝,我可以充电
充电结束,充电宝已归还
第17个线程执行完毕
我是A品牌充电宝,我可以充电
充电结束,充电宝已归还
第16个线程执行完毕
我是B品牌充电宝,我可以充电
充电结束,充电宝已归还
第19个线程执行完毕
我是A品牌充电宝,我可以充电
充电结束,充电宝已归还
第18个线程执行完毕
第20个线程执行完毕
结束测试,目前闲置数量5
第四节
享元模式优缺点及适用场景
优点:
1.相同对象可以进行管理,防止创建重复的对象导致内存溢出,这种方式提高了效率。
2.对象统一管理,需要做统一处理的时候则比较方便。
缺点:
1.为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。
2.读取享元模式的外部状态会使得运行时间稍微变长。
适用场景:
1.系统中存在大量相同或者相似对象,且占用较大的内存资源,优化方案可采用享元。
2.大部分的对象可以按照内部状态进行分组,且可将不同的部分分割为外部对象的时候。
3.线程池、数据库连接池等相关场景。
扫描二维码
关注我吧
IT小白架构师之路