操持模式(结构型)-享元模式

[复制链接]
发表于 2025-9-23 05:51:09 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
择要

        在软件开发的广阔范畴中,随着体系规模的不绝膨胀,资源的有用利用徐徐成为了一个至关紧张的议题。当一个体系中存在大量相似的对象时,怎样优化这些对象的管理,镌汰内存的占用,提拔体系的团体性能,成为了开发者们亟待办理的问题。享元模式作为一种结构型操持模式,应运而生,为这一困难提供了行之有用的办理方案。
定义

        享元模式,运用共享技能有用地支持大量细粒度的对象。在这一模式中,对象的状态被清楚地划分为内部状态和外部状态。内部状态,是对象中那些可以共享的雷同内容,它存储于对象内部,且不会随着情况的改变而发生厘革。比方,在一个图形绘制体系中,圆形的半径、颜色等属性,对于所有同类型的圆形来说,若这些属性值雷同,就可视为内部状态。而外部状态,则是那些需要外部情况来设置的、无法共享的内容,其会随着情况的厘革而改变。比如,在上述图形绘制体系中,圆形在画布上的具体位置,就属于外部状态。​
        值得留意的是,外部状态和内部状态相互独立,外部状态的厘革不会对内部状态产生影响。通过这种状态的划分,我们可以为雷同的对象设置差异的外部状态,从而使它们出现出差异的特性,同时共享雷同的内部状态,极大地镌汰了对象的创建数目。​
        从本质上讲,享元模式的核心在于 “分离与共享”。它将对象的状态进行分离,区分出厘革的部分(外部状态)和稳定的部分(内部状态),并对稳定的部分进行共享。通过这种方式,到达镌汰对象数目、节流内存空间的目标,使体系在处置处罚大量细粒度对象时更加高效。
作用

节流内存空间​

        在许多实际应用场景中,体系需要创建和管理大量相似的对象。比方,在一个在线游戏中,大概存在成千上万的游戏角色,这些角色在许多方面具有相似性,如角色的根本属性(生命值、攻击力等)、技能特效等。若为每个角色都创建一个独立的对象实例,将会斲丧大量的内存资源。而运用享元模式,我们可以将这些角色的共同属性(内部状态)进行共享,仅为每个角色生存其独特的属性(外部状态,如在游戏舆图中的位置、当前装备等)。如许,体系中实际存在的对象数目将大幅镌汰,从而明显节流内存空间。​
进步体系性能​

        镌汰对象的创建数目不但可以或许低沉内存的利用,还能提拔体系的性能。创建对象是一个相对耗时的操纵,需要分配内存、初始化对象的属性等。当体系中存在大量对象创建需求时,频仍的对象创建操纵会严峻影响体系的相应速率。享元模式通过共享对象,镌汰了不须要的对象创建,使得体系在处置处罚大量对象时,可以或许更加高效地运行,进步了体系的团体性能。​
享元工厂类的核心职位​

        享元模式的核心在于享元工厂类。享元工厂类就像是一个对象的管理者,其重要职责是提供一个用于存储享元对象的享元池。当用户需要获取某个对象时,享元工厂首先会在享元池中进行查找。假如享元池中已经存在符合要求的对象,那么工厂将直接返回该对象给用户;若享元池中不存在该对象,工厂则会创建一个新的享元对象,然后将其返回给用户,并同时将这个新增对象生存到享元池中,以便后续再次利用。通过这种方式,享元工厂类有用地实现了对象的共享和复用,极大地进步了体系资源的利用服从。
类图


角色

        在享元模式的类图中,重要包罗以下几个关键角色,它们相互协作,共同实现了对象的共享和高效管理:​

  •         Flyweight(享元接口):作为享元对象的抽象接口,它定义了对象需要实现的方法,而且通过这些方法来吸取外部状态并对其进行处置处罚。享元接口为具体的享元实现对象提供了同一的规范,确保差异的具体享元对象在活动上具有划一性。​
  •         ConcreteFlyweight(具体的享元实现对象):具体实现 Flyweight 接口的类,它是可共享的对象。在 ConcreteFlyweight 类中,需要对享元对象的内部状态进行封装,实现接口中定义的业务逻辑。比方,在一个笔墨排版体系中,某个特定字体、字号的笔墨对象就可以作为一个具体的享元实现对象,它内部封装了字体、字号等内部状态。​
  •         UnsharedConcreteFlyweight(非共享的享元实现对象):并非所有的享元对象都可以或许被共享,UnsharedConcreteFlyweight 就是如许的非共享对象。它通常是享元对象的组合对象,包罗了一些独特的、无法共享的状态。比如,在一个复杂的图形绘制体系中,一个由多个差异形状组合而成的复杂图形对象,大概就无法作为共享的享元对象,而属于非共享的享元实现对象。​
  •         FlyweightFactory(享元工厂):享元工厂类在整个享元模式中起着至关紧张的作用。它重要负责创建并管理共享的享元对象,维护一个享元池来存储已经创建的享元对象。当外部体系哀求一个享元对象时,享元工厂首先在享元池中查找是否存在符合要求的对象,假如存在则直接返回;若不存在,则创建一个新的享元对象,将其参加享元池后再返回给哀求者。享元工厂通过这种方式,有用地实现了享元对象的共享和复用
具体实现


  •         Flyweight(享元接口):在 Java 代码实现中,享元接口可以通过接口大概抽象类来定义。比方,在一个图形绘制的享元模式实现中,我们可以定义如下享元接口:
  1. public interface Shape {​
  2.     void draw(int x, int y);​
  3. }
复制代码
        这里的draw方法吸取外部状态(坐标x和y),用于在特定位置绘制图形。通过定义如许的接口,具体的图形享元实现对象(如圆形、矩形等)都需要实现该接口,确保了它们在处置处罚外部状态和绘制活动上的划一性。

  •         ConcreteFlyweight(具体的享元实现对象):以圆形为例,它实现了上述Shape接口:
  1. public class Circle implements Shape {​
  2.     private String color; // 内部状态,颜色​
  3. ​
  4.     public Circle(String color) {​
  5.         this.color = color;​
  6.     }​
  7. ​
  8.     @Override​
  9.     public void draw(int x, int y) {​
  10.         System.out.println("绘制颜色为 " + color + " 的圆形,坐标为 (" + x + ", " + y + ")");​
  11.     }​
  12. }
复制代码
        在Circle类中,color属性作为内部状态被封装在对象内部,而且在draw方法中,联合传入的外部状态(坐标x和y)进行图形的绘制操纵。

  •         UnsharedConcreteFlyweight(非共享的享元实现对象):假设在图形绘制体系中,存在一种特殊的组合图形,它由多个差异形状的图形组合而成,而且每个组合图形都有其独特的属性和活动,无法进行共享。我们可以定义如下非共享的享元实现对象类:
  1. public class ComplexShape {​
  2.     private List shapes = new ArrayList<>();​
  3.     private String uniqueProperty; // 独特的属性​
  4. ​
  5.     public ComplexShape(String uniqueProperty) {​
  6.         this.uniqueProperty = uniqueProperty;​
  7.     }​
  8. ​
  9.     public void addShape(Shape shape) {​
  10.         shapes.add(shape);​
  11.     }​
  12. ​
  13.     public void draw() {​
  14.         System.out.println("绘制具有独特属性 " + uniqueProperty + " 的复杂图形:");​
  15.         for (Shape shape : shapes) {​
  16.             shape.draw(0, 0); // 简单示例,实际可能需要更复杂的坐标计算​
  17.         }​
  18.     }​
  19. }
复制代码
        在这个ComplexShape类中,uniqueProperty属性表现其独特的、无法共享的状态,而且它可以包罗多个共享的享元对象(通过addShape方法添加),在draw方法中实现了其独特的绘制逻辑。

  •         FlyweightFactory(享元工厂):以下是一个简单的享元工厂类实现:
  1. import java.util.HashMap;​
  2. import java.util.Map;​
  3. ​
  4. public class ShapeFactory {​
  5.     private static final Map circleMap = new HashMap<>();​
  6. ​
  7.     public static Shape getCircle(String color) {​
  8.         Circle circle = circleMap.get(color);​
  9.         if (circle == null) {​
  10.             circle = new Circle(color);​
  11.             circleMap.put(color, circle);​
  12.             System.out.println("创建颜色为 " + color + " 的圆形");​
  13.         }​
  14.         return circle;​
  15.     }​
  16. }
复制代码
        在这个ShapeFactory类中,维护了一个circleMap享元池来存储已经创建的圆形对象。当外部哀求获取某个颜色的圆形时,首先在享元池中查找,假如不存在则创建一个新的圆形对象并放入享元池,末了返回该圆形对象,实现了圆形对象的共享和复用。
优缺点

优点


  •         镌汰内存中对象数目:享元模式最明显的优点之一就是可以或许极大地镌汰内存中对象的数目。通过共享雷同的内部状态,体系中只需要生存一份雷同状态的对象实例,而对于差异的外部状态,通过传入差异的参数来进行区分。比方,在一个包罗大量文本字符的文档处置处罚体系中,每个字符的字体、字号等内部状态可以共享,仅需为每个字符的位置等外部状态单独存储,从而使得雷同或相似对象在内存中只生存一份,有用地低沉了内存的占用。​
  •         外部状态独立性:享元模式中,外部状态相对独立于内部状态,而且不会影响其内部状态。这意味着享元对象可以在差异的情况中被共享利用,由于它们的核心内部状态不会由于外部情况的厘革而受到影响。比方,在一个游戏场景中,游戏角色的根本属性(如生命值、攻击力等内部状态)可以被共享,而角色在差异舆图中的位置(外部状态)可以根据游戏的进行随时改变,不会对角色的根本属性产生影响,使得雷同的角色享元对象可以在差异的游戏场景中复用。
缺点


  •         体系复杂度增长:引入享元模式会使体系变得更加复杂。为了实现对象状态的分离和共享,需要仔细地分析和操持,将对象的状态准确地划分为内部状态和外部状态。这一过程需要开发者具备较强的抽象头脑和操持本事,同时也增长了代码的明白和维护难度。比方,在一个复杂的企业级应用体系中,对于业务对象状态的划分大概需要深入相识业务逻辑和体系架构,否则大概导致状态划分不合理,影响体系的正常运行。​
  •         运行时间变长:为了实现对象的共享,享元模式需要将享元对象的状态外部化,这就意味着在利用享元对象时,需要从外部读取这些状态信息。相比于直接访问对象内部的所有状态信息,读取外部状态的操纵会增长肯定的运行时间开销。特殊是在对性能要求极高的场景中,这种额外的运行时间开销大概会对体系的团体性能产生肯定的影响。比方,在一个对实时性要求很高的金融生意业务体系中,频仍读取外部状态大概会导致生意业务相应时间变长,影响用户体验。
利用场景

大量雷同或相似对象的场景​

        当一个体系中有大量雷同或相似的对象存在,而且这些对象的大量利用导致内存大量耗费时,享元模式是一个非常合适的办理方案。比方,在一个在线舆图应用中,舆图上大概存在成千上万的标注点,这些标注点在许多属性上(如标注点的图标样式、巨细等)大概是雷同的,只有其在舆图上的位置差异。通过利用享元模式,将标注点的共同属性作为内部状态进行共享,仅为每个标注点生存其独特的位置信息(外部状态),可以明显镌汰内存的占用,进步体系的运行服从。​
对象状态可外部化的场景​

        假如对象的大部分状态可以被外部化,即这些状态可以从对象中分离出来,并在需要时通过外部情况传入对象中,那么享元模式就可以发挥作用。比方,在一个图形渲染体系中,图形的颜色、形状等属性可以作为内部状态进行共享,而图形在屏幕上的显示位置、旋转角度等属性可以作为外部状态,根据用户的操纵动态地传入图形对象中。如许,通过共享内部状态,镌汰了对象的创建数目,同时通过机动设置外部状态,满足了差异的显示需求。​
多次重复利用享元对象的场景​

        由于利用享元模式需要维护一个存储享元对象的享元池,这自己需要耗费肯定的资源,包罗内存和时间。因此,只有在多次重复利用享元对象的情况下,利用享元模式才是值得的。比方,在一个数据库毗连池的实现中,数据库毗连对象的创建和烧毁是比力耗时的操纵。通过利用享元模式,将数据库毗连对象作为享元对象,维护一个毗连池来存储和管理这些毗连对象。当应用步伐需要数据库毗连时,从毗连池中获取已有的毗连对象,利用完毕后再放回毗连池。如许,在大量的数据库操纵中,通过重复利用毗连对象,有用地进步了体系的性能,而且增补了维护毗连池所带来的资源开销。
利用案例

JDK 类库中的 String 类

        在 JDK 类库中,String类是利用享元模式的范例代表。当我们创建字符串对象时,假如字符串的值雷同,Java 会实验从字符串常量池中获取已经存在的字符串对象,而不是创建一个新的对象。比方:
  1. String str1 = "hello";​
  2. String str2 = "hello";​
  3. System.out.println(str1 == str2); // 输出 true
复制代码
        在这个例子中,str1和str2指向的是字符串常量池中的同一个对象,由于它们的值都是"hello"。通过这种方式,Java 有用地镌汰了字符串对象的创建数目,节流了内存空间。
Integer 类中的享元模式

        在Integer类中,也应用了享元模式。当我们通过Integer.valueOf(int i)方法获取Integer对象时,假如目标值在-128到127之间,Integer类会从缓存中获取已经存在的对象,而不是创建新的对象。比方:
  1. Integer num1 = Integer.valueOf(10);​
  2. Integer num2 = Integer.valueOf(10);​
  3. System.out.println(num1 == num2); // 输出 true
复制代码
        这是由于在Integer类的内部维护了一个缓存数组,用于存储-128到127之间的整数对象。当哀求的整数在这个范围内时,直接从缓存中返回对应的对象,实现了对象的共享,进步了体系的性能和内存利用率。
总结

        元模式作为一种强盛的结构型操持模式,在优化体系性能、镌汰内存占用方面具有明显的上风。尽管它增长了体系的复杂度,而且在某些场景下大概会带来肯定的运行时间开销,但在合适的应用场景中,其带来的好处远远超过了这些毛病。通过深入明白享元模式的原理、结构和应用场景,开发者可以或许更加高效地操持和构建软件体系,使其在面临大量细粒度对象时,依然可以或许保持精良的性能和资源利用率。
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表