• 设计模式-享元模式


    它使用物件用来尽可能减少内存使用量;于相似物件中分享尽可能多的资讯。当大量物件近乎重复方式存在,因而使用大量内存时,此法适用。通常物件中的部分状态(state)能够共享。常见做法是把它们放在数据结构外部,当需要使用时再将它们传递给享元。

    享元模式又称为轻量级模式,是对象池的一种实现。类似于线程池可以避免不停的创建和销毁多个对象,消耗性能。

    它的本质是通过共享对象(将多个同一对象的访问集中起来)来降低内存消耗,是结构型模式。

    这里需要注意的是享元模式把一个对象的状态分为内部状态和外部状态,内部状态是不变的可以共享的相同内容,外部状态是变化的是需要外部环境来设置的不能共享的内容,需要注意内部状态和外部状态的区分

    模式的动机

    主要是为了解决当对象数量太多时,将导致运行代价过高,带来性能下降等问题,通常境况下底层可能会用的更多些,比如我们的源码底层,String、Integer、Long等下文会说到

    模式的结构

    享元模式的通用UML类图

    通用代码:

    1. public interface Flyweight {
    2. void operation(String extrinsicState);
    3. }
    4. 复制代码
    1. public class ConcreteFlyweight implements Flyweight{
    2. private String intrinsicState;
    3. public ConcreteFlyweight(String intrinsicState){
    4. this.intrinsicState = intrinsicState;
    5. }
    6. @Override
    7. public void operation(String extrinsicState) {
    8. System.out.println("extrinsicState = " + extrinsicState);
    9. System.out.println("intrinsicState = " + this.intrinsicState);
    10. }
    11. }
    12. 复制代码

    这里需要注意的点事可能有的人会觉得intrinsicState这个属性是外部传过来的所以他就是外部对象,其实不然,工厂就是通过intrinsicState来区分实例的,而且也只有构造方法会用到其他是没有方法能够改变这个属性的,所以把这个属性定义为内部属性也就是可以共享的

    下面的operation方法就会接受一个外部属性,这个是不共享的所以定义为外部属性

    一定一定要区分外部属性和内部属性

    1. public class FlyweightFactory {
    2. private static Map<String,Flyweight> pool = new HashMap<>();
    3. // 因为内部状态具备不变性,因此作为缓存的键
    4. public static Flyweight getFlyweight(String intrinsicState) {
    5. if (!pool.containsKey(intrinsicState)) {
    6. Flyweight flyweight = new ConcreteFlyweight(intrinsicState);
    7. pool.put(intrinsicState, flyweight);
    8. }
    9. return pool.get(intrinsicState);
    10. }
    11. }
    12. 复制代码
    1. public class Test {
    2. public static void main(String[] args) {
    3. Flyweight flyweight1 = FlyweightFactory.getFlyweight("aa");
    4. Flyweight flyweight2 = FlyweightFactory.getFlyweight("bb");
    5. flyweight1.operation("a");
    6. flyweight2.operation("b");
    7. }
    8. }
    9. 复制代码

    代码示例

    通过上文的解说可能有些人对内部状态和外部状态还是很模糊,也不知道到底什么时候去使用享元模式,这里我们再举一个例子,比如我们下五子棋,所有棋大小形状肯定都是一样的,我们可以作为内部状态共享,其实我们还可以把颜色作为内部状态共享,有的人可能角色颜色有两种怎么共享。这里我们可以将颜色通过构造方法传入赋值给颜色属性,在工厂创建实例的时候就把白色和黑色划分成了两种对象,每一种对象的颜色就统一了。外部状态就是棋的坐标。

    具体实现如下:

    1. public abstract class BaseChess {
    2. //这两个属性是所有棋子共有的属性所以我们一颗直接放到抽象类中
    3. protected final String shape = "原型";
    4. protected final Integer radius = 5;
    5. protected String color;
    6. public BaseChess(String color){
    7. this.color = color;
    8. }
    9. //移动棋子
    10. public abstract void moveChess(int x, int y);
    11. }
    12. 复制代码
    1. public class Chess extends BaseChess {
    2. public Chess(String color) {
    3. super(color);
    4. }
    5. @Override
    6. public void moveChess(int x, int y) {
    7. String string = "棋子形状:" +
    8. super.shape +
    9. "棋子半径:" +
    10. super.shape +
    11. "----" +
    12. super.color +
    13. "棋子移动位置" +
    14. "x:" +
    15. x +
    16. "y:" +
    17. y;
    18. System.out.println(string);
    19. }
    20. }
    21. 复制代码
    1. public class ChessFactory {
    2. private static Map<String,BaseChess> pool = new HashMap<>();
    3. public static BaseChess getChess(String color){
    4. //这里以棋子颜色作为键值来区分示例
    5. if(pool.containsKey(color)){
    6. return pool.get(color);
    7. }else{
    8. Chess chess = new Chess(color);
    9. pool.put(color,chess);
    10. return chess;
    11. }
    12. }
    13. }
    14. 复制代码
    1. public class Test {
    2. public static void main(String[] args) {
    3. BaseChess chess = ChessFactory.getChess("白色");
    4. BaseChess chess1 = ChessFactory.getChess("黑色");
    5. BaseChess chess2 = ChessFactory.getChess("白色");
    6. BaseChess chess3 = ChessFactory.getChess("黑色");
    7. System.out.println(chess);
    8. chess.moveChess(100,102);
    9. System.out.println(chess1);
    10. chess1.moveChess(101,102);
    11. System.out.println(chess2);
    12. chess2.moveChess(102,102);
    13. System.out.println(chess3);
    14. chess3.moveChess(103,102);
    15. }
    16. }
    17. 复制代码

    这里有一点是如果有人觉得这个内部对象还要通过构造方法传递很不爽,可以创建两个对象分别是白色棋子对象和黑色棋子对象,在工厂方法中就定义号白色对应的是哪个对象黑色是哪个,这样就可以将颜色这个内部属性写死在对象内部了

    源码中的提现

    String

    Java 中将 String 类定义为 final(不可改变的),JVM 中字符串一般保存在字符串常量池中,java 会确保一个宁符串在常量池中只有一个拷贝,这个宁符串常量池在JDK6.0 以前是位于常量池中,位于 永久代,而在JDK7.0中,JVM将其从永久代拿出来放置于堆中。

    可以看一下下面代码:

    1. String s1 = "hello";
    2. String s2 = "hello";
    3. String s3 = new String("hello");
    4. String s4 = s3.intern();
    5. System.out.println(s1 == s2);
    6. System.out.println(s1 == s3);
    7. System.out.println(s2 == s3);
    8. System.out.println(s4 == s3);
    9. System.out.println(s4 == s2);
    10. 复制代码

    运行结果如下:

    1. s1和s2一样是应为"hello"这个字面量已经存到常量池中
    2. s2再赋值的时候会到常量池中找找到了直接赋值不会再新创建对象s2和s3不一样就是s3自己new了一个对象所以肯定是不一样的
    3. s4和s3不一样又和s2一样的原因是 intern方法会去查常量池查如果查到了直接返回常量池的对象,如果没查到会将s3放到常量池(通过在创建一个新对象)再返回对应常量池的引用

    Integer

    可以先看一下 valueOf方法源码:

    可以看到会先判断范围如果是在达到一定的范围就直接冲缓存中取否则才会创建对象
    再看这个IntegeCache是个什么东西:

    1. private static class IntegerCache {
    2. static final int low = -128;
    3. static final int high;
    4. static final Integer[] cache;
    5. static Integer[] archivedCache;
    6. static {
    7. // high value may be configured by property
    8. int h = 127;
    9. String integerCacheHighPropValue =
    10. VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
    11. if (integerCacheHighPropValue != null) {
    12. try {
    13. h = Math.max(parseInt(integerCacheHighPropValue), 127);
    14. // Maximum array size is Integer.MAX_VALUE
    15. h = Math.min(h, Integer.MAX_VALUE - (-low) -1);
    16. } catch( NumberFormatException nfe) {
    17. // If the property cannot be parsed into an int, ignore it.
    18. }
    19. }
    20. high = h;
    21. // Load IntegerCache.archivedCache from archive, if possible
    22. CDS.initializeFromArchive(IntegerCache.class);
    23. int size = (high - low) + 1;
    24. // Use the archived cache if it exists and is large enough
    25. if (archivedCache == null || size > archivedCache.length) {
    26. Integer[] c = new Integer[size];
    27. int j = low;
    28. for(int i = 0; i < c.length; i++) {
    29. c[i] = new Integer(j++);
    30. }
    31. archivedCache = c;
    32. }
    33. cache = archivedCache;
    34. // range [-128, 127] must be interned (JLS7 5.1.7)
    35. assert IntegerCache.high >= 127;
    36. }
    37. private IntegerCache() {}
    38. }
    39. 复制代码

    发现是Integer的一个内部类,在第一次调用它的时候就会创建值为-128~127的对象。

    享元模式优缺点

    优点:

    1. 如果有大量相同对象要创建该模式可以提高性能节省内存

    缺点

    1. 享元模式使得系统更加复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化。
    2. 为了使对象可以共享,享元模式需要将享元对象的状态外部化,而读取外部状态使得运行时间变
  • 相关阅读:
    vue3 状态管理pinia
    HTML如何设置引文与特色元素
    Request请求转发与Respones请求重定向有什么区别?
    顺丰面试,第二个问题把我劝退了!
    Qt之sendEvent
    Appium和Android常用9种自动化测试框架对比有哪些优势?
    C#(C Sharp)学习笔记_变量常量与作用域【十二】
    测试C#图像文本识别模块Tesseract的基本用法
    Python毕业论文题目计算机毕业论文django微信小程序校园导航系统
    DOS学习-目录与文件应用操作经典案例-type
  • 原文地址:https://blog.csdn.net/BASK2311/article/details/127977684