大家好,我是老田,今天给大家分享一下设计模式中的享元模式。用恰当的生活故事和真实的项目场景来描述设计模式,最后用一句话概括这个设计模式。以下为本文内容:背景享元模式(FlyweightPattern),又称轻量级模式,是对象池的一种实现。与线程池类似,线程池可以避免连续创建和销毁多个对象而消耗性能。Flyweight模式提供了一种方法来减少对象的数量,从而改进应用程序所需的对象结构。英文解释:使用共享高效支持大量细粒度对象。一个单独的对象,主要用于减少创建对象的数量,以减少内存使用,提高性能。属于结构设计模式,结构设计模式包括:代理、门面、装饰器、享元、桥、适配器、组合。注意:享元模式将对象的状态分为内部状态和外部状态。内在状态不变,外在状态在变化;然后通过共享常量部分,达到减少对象数量和节省内存的目的。生活案例房屋中介只要是一座城市,就少不了房屋中介。房屋中介机构存储了大量的出租房屋信息,一个房屋中介机构往往有多个门店,但所有门店都共享这些房屋信息(共享房屋信息)。个人身份证信息每个中国公民都有一张身份证,这个身份证信息在公安系统共享,全国所有派出所都会共享你的身份证信息(共享个人身份信息)。高考志愿每个大学在每个省都有明确的招生名额,这些名额由全省所有高考考生共享(共享录取名额)。图书馆可以借到的书,有很多读者共享。大家可以看看有没有借出去,基本还有书可以借(书是共享的)。...简单代码实现下面我们用一个案例来演示享元模式(以库为例)。publicinterfaceBook{voidborrow();}/***@authorjava后端技术全栈*/publicclassConcreteBookimplementsBook{//借书名privateStringname;publicConcreteBook(Stringname){this.name=name;}@Overridepublicvoidborrow(){System.out.println("图书馆借了一本书,书名:"+this.name);}}importjava.util.HashMap;importjava.util.Map;/**Library*@authorjava后端技术fullStack*/publicclassLlibrary{privateMapbookMap=newHashMap<>();privateLlibrary(){}//只能有一个librarypublicstaticLlibrarygetInstance(){returnLazyHolder.LAZY_STATIC_SINGLETON;}//按名字借书publicBooklibToBorrow(Stringname){Bookbook;//图书馆有的话直接借书if(bookMap.containsKey(name)){book=bookMap.get(name);}else{//图书馆没有,再入一本书,再借书book=newConcreteBook(name);bookMap.put(name,book);}returnbook;}//返回还剩多少本书publicintbookSize(){returnbookMap.size();}privatestaticclassLazyHolder{privatestaticfinalLlibraryLAZY_STATIC_SINGLETON=newLlibrary();}}importjava.util.ArrayList;importjava.util.List;publicclassStudent{privatestaticLi圣<书>bookList=newArrayList<>();privatestaticBookFactorybookFactory;publicstaticvoidmain(String[]args){bookFactory=BookFactory.getInstance();studenBorrow("java从入门到精通");studenBorrow("java从入门到放弃");studenBorrow("JVMjava虚拟机");studenBorrow("java编程思想");//归还后再借.studenBorrow("java从入门到精通");studenBorrow("java从入门到放弃");studenBorrow("JVMjava虚拟机");studenBorrow("java编程思想");//还清之后再借studenBorrow("java从入门到精通");studenBorrow("java从入门到放弃");studenBorrow("JVMjava虚拟机");studenBorrow("java编程思想");//借出每本书for(Bookbook:bookList){book.borrow();}System.out.println("同学共借了"+bookList.size()+"book");System.out.println("学生共借了"+bookFactory.bookSize()+"book");}privatestaticvoidstudenBorrow(Stringname){bookList.add(bookFactory.libToBorrow(name));}}运行结果是图书馆借了一本书,书名:java,从入门到精通,图书馆借了一本书,书名:java,从入门到放弃,图书馆借了一本书,书名:JVMjava虚拟机库出一本书,书名:java编程思维库借书,书名:java从入门到精通库借书,书名:java从入门到弃书库借书,书名:JVMjava虚拟机库借书,title:java编程思想库借了一本书,title:java从入门到精通图书馆借了一本书,title:java从入门到放弃图书馆借了一本书,title:JVMjava虚拟机图书馆借了一本书,title:JavaProgrammingThoughts学生一共借了12本书学生一共借了4本书其实图书馆只有四本书,但是多人借了。A借阅,B再借,B归还,C再借,大家共享。享元模式的UML类图如下:从上图可以看出,享元模式主要包括3个角色。抽象享元角色(Book):享元对象抽象基类或接口,同时定义了对象的外部状态和内部状态的接口或实现。具体享元角色(ConcreteBook):实现抽象角色定义的业务。这个角色的内部状态处理应该和环境无关,不会出现一个操作改变内部状态同时修改外部状态的情况。享元工厂(BookFactory):负责管理享元对象池,创建享元对象。可能你对这个例子还不是很理解,我们用一个工作中常见的场景来解释一下。大佬们是怎么使用享元模式的,JDK中使用比较广泛的,比如String,Integer,Long等类。下面这段代码在Flyweight模式下Integer输出什么?/***欢迎关注公众号:java后端技术全栈**@作者田威常*@date2021/06/0219:30*/publicclassIntegerDemo{publicstaticvoidmain(String[]args){Integera=100;Integerb=Integer.valueOf(100);System.out.println(a==b);Integerc=newInteger(1000);Integerd=Integer.valueOf(1000);System.out。println(c==d);}}很多人可能会认为输出的truetrue其实是,不对,这里最后输出的是:truefalse为什么?100可以比较,1000不能比较?其实就是在使用Integer享元模式的时候,缓存-128到127范围内的数据(在一个Integer类型的数组中)。staticfinalintlow=-128;publicstaticIntegervalueOf(inti){//high默认为127if(i>=IntegerCache.low&&i<=IntegerCache.high)returnIntegerCache.cache[i+(-IntegerCache.low)];returnnewInteger(i);}下面进行简单分析:关于Integer缓存,推荐阅读这篇文章:这里,Integer中的IntegerCache使用享元模式。关于Integer推荐:面试官:谈谈享元模式在Integer缓存范围StringJava中String类定义为final不能继承,属性值也定义为final不可变。JVM中的字符串一般都存放在字符型的字符串常量池中,Java会保证一个字符串在常量池中只有一份。这个字符串常量池在JDK1中位于方法区(永久代)。方法区被移动到堆中。下面这段代码输出什么?/***欢迎关注公众号:java后端技术全栈**@作者田威常*@date2021/06/03*/publicclassStringDemo{publicstaticvoidmain(String[]args)throwsException{Strings1="abcd";Strings2="abcd";Strings3="ab"+"cd";Strings4="ab"+newString("cd");Strings5=newString("abcd");Strings6=s5.intern();Strings7="a";Strings8="bcd";Strings9=s7+s8;System.out.println("s1==s2"+(s1==s2));System.out.println("s1==s3"+(s1==s3));System.out.println("s1==s4"+(s1==s4));System.out.println("s1==s6"+(s1==s6));System.out.println("s1==s9"+(s1==s9));System.out.println("s4==s5"+(s4==s5));}}String类的值是最终修改的。当创建一个字面量形式的String变量时,JVM会在编译时将字面量“abcd”放入字符串常量池中,在Java程序启动时加载到内存中。.这个字符串常量的特点是只有一个相同的字面量。如果还有其他相同的文字,JVM将返回对该文字的引用。如果没有相同的字面量,则在字符串常量池中创建这个字面量。并返回对它的引用。由于s2指向常量池中已经存在的字面量“abcd”(s1先于s2),JVM返回字面量绑定的引用,所以s1==s2。s3中字面量的拼接实际上已经在JVM层进行了优化,而s3的拼接在JVM编译的时候就进行了优化,所以s1、s2、s3都可以理解为一样的,即s1==s3。s4中的newString("cd"),此时生成了两个对象,"cd"和newString("cd"),"cd"存在于字符串常量池中,newString("cd")存在于堆堆,Strings4="ab"+newString("cd");本质上是两个对象相加,编译器不会优化,相加的结果存在heap堆中,s2存在字符串常量池中,当然不相等,即s1!=s4.s4和s5最终的结果都在堆中,所以此时s4!=s5s5.intern()方法可以做一个维度,在运行时动态的把总字符串加入到字符串常量池中(stringconstantpool的content是程序启动时加载alcohol,如果字符串常量池中有对象对应的字面量,则返回字面量在字符串常量池中的引用,否则,创建字面量的副本就会在字符串常量池中被并发引用),所以s1==s6。s9是由s7和s8拼接而成,但是jvm并没有优化,所以s1!=s9最后上面的代码输出:s1==s2trues1==s3trues1==s4falses1==s6trues1==s9falses4==s5falseJVM常量poolin也是享元模式的经典实现之一。关于String的扩展:美团面试题:Strings=newString("111")会创建多少个对象?Long中的Flyweight模式与Integer类似,最多缓存-128到127个数,请看Long中valueOf()方法源码:publicstaticLongvalueOf(longl){finalintoffset=128;if(l>=-128&&l<=127){//willcachereturnLongCache.cache[(int)l+offset];}returnnewLong(l);}这个不用演示了。和Integer一样使用cache,也就是享元模式。ApacheCommonsPool中享元模式对象池的基本思想是将使用过的对象保存下来,下次需要对象时再使用,从而在一定程度上减少频繁创建对象造成的消耗。用来充当存储对象“容器”的对象称为对象池(ObjectPool,简称Pool)。ApachePool实现了对象池的功能,定义了对象的生成、销毁、激活、钝化和状态转换等操作,并提供了几种默认的对象池实现,具有以下重要作用:PooledObject(池对象):用于封装对象(例如,线程、数据库连接和TCP连接),并将它们包装成可以由对象池管理的对象。PooledObjectFactory(池化对象工厂):定义了一些操作PooledObject实例生命周期的方法。PooledObjectFactory必须实现线程安全。ObjectPool(对象池):ObjectPool负责管理PooledObjects,例如借出对象,归还对象,验证对象,有多少活跃对象,有多少空闲对象。在ObjectPool类的子类org.apache.commons.pool2.impl.GenericObjectPool中,有一个属性:privatefinalMap,PooledObject>allObjects;这个Map是用来缓存对象的,所以这里也是享元模式的实现。在享元模式的扩展中定义享元模式,提出了两个需求:细粒度和共享对象。因为要求细粒度,难免会出现大量具有相似属性的对象。这时,我们将这些对象的信息分为内部状态和外部状态两部分。内部状态是指对象共享的信息,存储在享元对象内部,不会随环境变化;外部状态是指对象可以依赖的标志,它随环境而变化,不能共享。例如:连接池中的连接对象,连接对象中存储的用户名、密码、连接URL等信息是在创建对象时设置的,不会随着环境的变化而改变。这些是内部状态。而当每个连接要被回收时,我们需要将其标记为可用,这些是外部状态。优缺点优点减少对象的创建,减少内存中的对象数量,减少系统内存,提高效率。减少内存以外的资源使用。缺点要注意内部和外部状态,注意线程安全问题。使系统和程序的逻辑复杂化。总结享元模式,估计很多人光从概念上不是很理解,但是从Integer和String的结合,以及生活中的场景,就很容易理解享元模式了。Flyweight模式的实现基本上伴随着一个Collections,用来存放这些对象。一句话总结:优化资源配置,减少资源浪费参考:Tom的设计模式课程结束,今天的分享到此结束,希望大家能够了解享元模型是什么,开发中是否可以使用享元模型,供大家参考,不要在面试的时候说你不会设计模式。本文转载自微信公众号《Java后端技术全栈》,可通过以下二维码关注。转载本文请联系Java后端技术全栈公众号。