zoukankan      html  css  js  c++  java
  • Flyweight

    设计模式目录

    享元模式是一种结构型设计模式, 它摒弃了在每个对象中保存所有数据的方式, 通过共享多个对象所共有的相同状态, 让你能在有限的内存容量中载入更多对象。

    亦称: 缓存、Cache、Flyweight

    对象的常量数据通常被称为内在状态, 其位于对象中, 其他对象只能读取但不能修改其数值。 而对象的其他状态常常能被其他对象 “从外部” 改变, 因此被称为外在状态

    享元模式建议不在对象中存储外在状态, 而是将其传递给依赖于它的一个特殊方法。 程序只在对象中保存内在状态, 以方便在不同情景下重用。 这些对象的区别仅在于其内在状态 (与外在状态相比, 内在状态的变体要少很多), 因此你所需的对象数量会大大削减。

    享元与不可变性

    由于享元对象可在不同的情景中使用, 你必须确保其状态不能被修改。 享元类的状态只能由构造函数的参数进行一次性初始化, 它不能对其他对象公开其设置器或公有成员变量。

    享元工厂

    为了能更方便地访问各种享元, 你可以创建一个工厂方法来管理已有享元对象的缓存池。 工厂方法从客户端处接收目标享元对象的内在状态作为参数, 如果它能在缓存池中找到所需享元, 则将其返回给客户端; 如果没有找到, 它就会新建一个享元, 并将其添加到缓存池中。

    你可以选择在程序的不同地方放入该函数。 最简单的选择就是将其放置在享元容器中。 除此之外, 你还可以新建一个工厂类, 或者创建一个静态的工厂方法并将其放入实际的享元类中。

    享元模式在核心 Java 程序库中的示例:

    java.lang.Integer#valueOf(int) (以及 BooleanByteCharacterShortLongBig­Decimal

    识别方法: 享元可以通过构建方法来识别, 它会返回缓存对象而不是创建新的对象。

    享元模式结构

    样例

    本例中, 我们将渲染一片森林 (1,000,000 棵树)! 每棵树都由包含一些状态的对象来表示(坐标和纹理等)。 尽管程序能够完成其主要工作, 但很显然它需要消耗大量内存。

    原因很简单: 太多树对象包含重复数据 (名称、 纹理和颜色)。 因此我们可用享元模式来将这些数值存储在单独的享元对象中 ( Tree­Type类)。 现在我们不再将相同数据存储在数千个 Tree对象中, 而是使用一组特殊的数值来引用其中一个享元对象。

    客户端代码不会知道任何事情, 因为重用享元对象的复杂机制隐藏在了享元工厂中。

    tree

    package structural.flyweight;
    
    import java.awt.*;
    
    /**
     * 包含每棵树的独特状态
     */
    public class Tree {
    
        private int x;
        private int y;
        private TreeType type;
    
        public Tree(int x, int y, TreeType type) {
            this.x = x;
            this.y = y;
            this.type = type;
        }
    
        public void draw(Graphics g) {
            type.draw(g, x, y);
        }
    }
    
    
    
    
    package structural2.flyweight;
    
    import java.awt.*;
    
    /**
     * 包含多棵树共享的状态
     */
    public class TreeType {
    
        private String name;
        private Color color;
        private String otherTreeData;
    
        public TreeType(String name, Color color, String otherTreeData) {
            this.name = name;
            this.color = color;
            this.otherTreeData = otherTreeData;
        }
    
        public void draw(Graphics g, int x, int y) {
            g.setColor(Color.BLACK);
            g.fillRect(x - 1, y, 3, 5);
            g.setColor(color);
            g.fillOval(x - 5, y - 10, 10, 10);
        }
    }
    
    

    享元工厂

    package structural.flyweight;
    
    import java.awt.*;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * 封装创建享元的复杂机制
     */
    public class TreeFactory {
        static Map<String, TreeType> treeTypeMap = new HashMap<>();
    
        public static TreeType getTreeType(String name, Color color, String otherTreeData) {
            TreeType result = treeTypeMap.get(name);
            if (result == null) {
                result = new TreeType(name, color, otherTreeData);
                treeTypeMap.put(name, result);
            }
            return result;
        }
    }
    

    森林

    package structural.flyweight;
    
    import javax.swing.*;
    import java.awt.*;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * 森林
     */
    public class Forest extends JFrame {
        private List<Tree> trees = new ArrayList<>();
    
        public void plantTree(int x, int y, String name, Color color, String otherTreeData) {
            TreeType treeType = TreeFactory.getTreeType(name, color, otherTreeData);
            Tree tree = new Tree(x, y, treeType);
            trees.add(tree);
        }
    
        @Override
        public void paint(Graphics graphics) {
            for (Tree tree : trees) {
                tree.draw(graphics);
            }
        }
    }
    

    测试

    package structural2.flyweight;
    
    import java.awt.*;
    
    public class Demo {
        private static int CANVAS_SIZE = 500;
        private static int TREES_TO_DRAW = 1000000;
        private static int TREE_TYPES = 2;
    
        public static void main(String[] args) {
            Forest forest = new Forest();
            for (int i = 0; i < Math.floor(TREES_TO_DRAW / TREE_TYPES); i++) {
                forest.plantTree(random(0, CANVAS_SIZE), random(0, CANVAS_SIZE),
                        "Summer Oak", Color.GREEN, "Oak texture stub");
                forest.plantTree(random(0, CANVAS_SIZE), random(0, CANVAS_SIZE),
                        "Autumn Oak", Color.ORANGE, "Autumn Oak texture stub");
            }
            forest.setSize(CANVAS_SIZE, CANVAS_SIZE);
            forest.setVisible(true);
    
            System.out.println(TREES_TO_DRAW + " trees drawn");
            System.out.println("---------------------");
            System.out.println("Memory usage:");
            System.out.println("Tree size (8 bytes) * " + TREES_TO_DRAW);
            System.out.println("+ TreeTypes size (~30 bytes) * " + TREE_TYPES + "");
            System.out.println("---------------------");
            System.out.println("Total: " + ((TREES_TO_DRAW * 8 + TREE_TYPES * 30) / 1024 / 1024) +
                    "MB (instead of " + ((TREES_TO_DRAW * 38) / 1024 / 1024) + "MB)");
        }
    
        private static int random(int min, int max) {
            return min + (int) (Math.random() * ((max - min) + 1));
        }
    }
    
    /**
     *1000000 trees drawn
     * ---------------------
     * Memory usage:
     * Tree size (8 bytes) * 1000000
     * + TreeTypes size (~30 bytes) * 2
     * ---------------------
     * Total: 7MB (instead of 36MB)
     */
    

    适用场景

    仅在程序必须支持大量对象且没有足够的内存容量时使用享元模式。

    应用该模式所获的收益大小取决于使用它的方式和情景。 它在下列情况中最有效:

    • 程序需要生成数量巨大的相似对象
    • 这将耗尽目标设备的所有内存
    • 对象中包含可抽取且能在多个对象间共享的重复状态。

    实现方式

    1. 将需要改写为享元的类成员变量拆分为两个部分:
      • 内在状态: 包含不变的、 可在许多对象中重复使用的数据的成员变量。
      • 外在状态: 包含每个对象各自不同的情景数据的成员变量
    2. 保留类中表示内在状态的成员变量, 并将其属性设置为不可修改。 这些变量仅可在构造函数中获得初始数值。
    3. 找到所有使用外在状态成员变量的方法, 为在方法中所用的每个成员变量新建一个参数,并使用该参数代替成员变量。
    4. 你可以有选择地创建工厂类来管理享元缓存池, 它负责在新建享元时检查已有的享元。 如果选择使用工厂, 客户端就只能通过工厂来请求享元, 它们需要将享元的内在状态作为参数传递给工厂。
    5. 客户端必须存储和计算外在状态 (情景) 的数值, 因为只有这样才能调用享元对象的方法。 为了使用方便, 外在状态和引用享元的成员变量可以移动到单独的情景类中。

  • 相关阅读:
    互联网预言大神:凯文·凯利——预测未来的12个趋势
    仅有算法远远不够:AI突破下一站,需要对硬件重新审视
    软件开发中经常使用的5种设计模式
    强化学习到底是什么,它如何运作?
    2020年网络安全的六大经验教训
    选择困难终结者:不同问题之下的机器学习算法
    为什么物联网如此重要?
    Pku3080 Blue Jeans
    pku1734 Musical Theme
    [Usaco2006 Dec]Milk Patterns
  • 原文地址:https://www.cnblogs.com/huaranmeng/p/14299522.html
Copyright © 2011-2022 走看看