本文介绍: 享元模式是结构型模式中最简单的一个模式,享是共享的意思,元是最小单元或细小的对象的意思。也就是说对一些需要大量重复使用的很细的对象进行缓存,缓存了就可以重复使用,例如Integer类中对整型-128到127进行了缓存,使用的正是享元模式。它有点类似单例模式,不过单例模式只共享一个类的唯一对象,而享元模式是共享多个类的唯一对象。给出定义如下:运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似对象的开销,从而提高系统资源的利用率。

享元模式

享元模式是结构型模式中最简单的一个模式,享是共享的意思,元是最小单元或细小的对象的意思。也就是说对一些需要大量重复使用的很细的对象进行缓存,缓存了就可以重复使用,例如Integer类中对整型-128到127进行了缓存,使用的正是享元模式。它有点类似单例模式,不过单例模式只共享一个类的唯一对象,而享元模式是共享多个类的唯一对象。

给出定义如下:运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似对象的开销,从而提高系统资源的利用率。用大白话讲就是如果一个项目中有很多很细很小的对象,那么如果对象太多那么会导致JVM内存利用率低下,所以需要对这些很零散的小对象进行统一管理。

享元(Flyweight )模式中存在以下两种状态:

  1. 内部状态,即不会随着环境的改变而改变的可共享部分,例如下面例子中俄罗斯方块对象中的shape属性就是内部状态,固定死的属性。避免重复创建对象,节省内存空间。根据内部状态把对象存储在共享池,需要时去共享池取就行。
  2. 外部状态,指随环境改变而改变的不可以共享的部分。享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化。例如,下面俄罗斯方块对象的颜色属性,每种类型的方块都可以有不同的颜色。允许外部进行修改。

内部状态可以理解为对象固有的属性,不能改变,例如俄罗斯方块对象的shape属性,而外部状态是随环境而变化的,例如在不同游戏背景下方块的颜色不一样。因此,对于不变的属性是可以共享的,而变化的属性例如颜色是不可共享的,因为假设现在同时开启了两个不同背景的俄罗斯方块游戏,那么方块的颜色应该是不一样的。

享元模式的主要有以下角色:

  • 抽象享元角色(Flyweight):通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
  • 具体享元(Concrete Flyweight)角色 :它实现了抽象享元类,称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
  • 非享元(Unsharable Flyweight)角色 :并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
  • 享元工厂(Flyweight Factory)角色 :负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。

案例:俄罗斯方块

俄罗斯方块这个游戏中,需要反复使用到有很多不同的方块(I型,L型,Z型等),每个不同的方块都是一个实例对象,这些对象就要占用很多的内存空间,如果每次出现对象都重新new一个,那么堆上占用的空间就会很多,这里还好俄罗斯方块也就不超过十种,如果是类似于类似于数字游戏的对象呢?每个数字都是一个对象实例,这里仅用俄罗斯方块利用享元模式进行实现。

先来看类图:
在这里插入图片描述

代码实现

俄罗斯方块有不同的形状,我们可以对这些形状向上抽取出AbstractBox,用来定义共性的属性和行为。

public abstract class AbstractBox {
    private Character shape;
    private String color;

    public AbstractBox(Character shape){
        this.shape = shape;
    }

    public Character getShape() {
        return shape;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    @Override
    public String toString() {
        return "AbstractBox{" +
                "shape=" + shape +
                ", color='" + color + ''' +
                '}';
    }
}

接下来就是定义不同的形状了,IBox类、LBox类、ZBox类等。这些类对应着的概念就是元,享元就是共享(缓存)这些频繁创建的对象。

public class IBox extends AbstractBox{
    public IBox(){
        super('I');
        super.setColor("White");
    }
}


public class LBox extends AbstractBox{
    public LBox(){
        super('L');
        super.setColor("White");
    }
}

public class ZBox  extends  AbstractBox{
    public ZBox(){
        super('Z');
        super.setColor("White");
    }
}

提供了一个工厂类(BoxFactory),用来管理享元对象(也就是AbstractBox子类对象),该工厂类对象只需要一个,所以可以使用单例模式。并给工厂类提供一个获取形状的方法。如此的话,需要方块对象只需要从单例工厂中获取即可,所获取到的对象都是缓存的,不会重复创建,节省大量空间。


public class BoxFactory {
    private static HashMap<Character,AbstractBox> map;

    private BoxFactory(){
        map = new HashMap<>();
        IBox iBox = new IBox();
        LBox lBox = new LBox();
        ZBox zBox = new ZBox();
        map.put('I',iBox);
        map.put('L',lBox);
        map.put('Z',zBox);
    }

    private static class SingletonHolder{
        // 静态内部类实现单例模式,如果不懂请学本系列的单例模式
        private static final BoxFactory INSTANCE = new BoxFactory();
    }
    public static final BoxFactory getInstance(){
        return SingletonHolder.INSTANCE;
    }
    public AbstractBox getBox(Character c){
        return map.get(c);
    }
}


// 客户类,测试调用类
public class Main {
    public static void main(String[] args) {
        BoxFactory instance = BoxFactory.getInstance();
        AbstractBox ibox = instance.getBox('I');
        System.out.println(ibox);
        System.out.println(instance.getBox('Z'));
        System.out.println(instance.getBox('L'));
    }
}

优点

  • 极大减少内存中相似或相同对象数量,节约系统资源,提供系统性能
  • 享元模式中的外部状态相对独立,且不影响内部状态

缺点

为了使对象可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态,使程序逻辑复杂。上面的俄罗斯方块的例子只考虑了内部状态,对外部状态没有考虑进去,如果考虑进去将会非常复杂。

使用场景

  • 一个系统有大量相同或者相似的对象,造成内存的大量耗费。
  • 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。
  • 在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源,因此,应当在需要多次重复使用享元对象时才值得使用享元模式。

JDK例子: Integer 使用了享元模式,默认先创建并缓存 -128 ~ 127 之间数的 Integer 对象,当调用 valueOf 时如果参数在 -128 ~ 127 之间则计算下标并从缓存中返回,否则创建一个新的 Integer 对象。

参考内容:
传智播客系列设计模式笔记
https://zhuanlan.zhihu.com/p/86135908
https://zhuanlan.zhihu.com/p/74872012
https://juejin.cn/post/7088505397639643173

建议阅读下一篇:结构型设计模式——外观模式

原文地址:https://blog.csdn.net/cj151525/article/details/135442122

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。

如若转载,请注明出处:http://www.7code.cn/show_54274.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注