java与设计模式-享元模式

java与设计模式-享元模式

一、定义

享元模式(Flyweight Pattern) 是池技术的重要实现方式, 其定义如下: Use sharing tosupport large numbers of fine-grained objects efficiently.(使用共享对象可有效地支持大量的细粒度的对象。)

享元模式的定义为我们提出了两个要求: 细粒度的对象和共享对象。 我们知道分配太多的对象到应用程序中将有损程序的性能, 同时还容易造成内存溢出, 那怎么避免呢? 就是享元模式提到的共享技术。 我们先来了解一下对象的内部状态和外部状态。

要求细粒度对象, 那么不可避免地使得对象数量多且性质相近, 那我们就将这些对象的信息分为两个部分: 内部状(intrinsic) 与外部状态(extrinsic) 。

  • 内部状态

内部状态是对象可共享出来的信息, 存储在享元对象内部并且不会随环境改变而改变,如我们例子中的id、 postAddress等, 它们可以作为一个对象的动态附加信息, 不必直接储存在具体某个对象中, 属于可以共享的部分。

  • 外部状态

外部状态是对象得以依赖的一个标记, 是随环境改变而改变的、 不可以共享的状态, 如我们例子中的考试科目+考试地点复合字符串, 它是一批对象的统一标识, 是唯一的一个索引值。

二、通用类图

在这里插入图片描述

三、角色分析

  • Flyweight-抽象享元角色

它简单地说就是一个产品的抽象类, 同时定义出对象的外部状态和内部状态的接口或实现。

  • ConcreteFlyweight-具体享元角色

具体的一个产品类, 实现抽象角色定义的业务。 该角色中需要注意的是内部状态处理应该与环境无关, 不应该出现一个操作改变了内部状态, 同时修改了外部状态, 这是绝对不允许的。

  • unsharedConcreteFlyweight-不可共享的享元角色

不存在外部状态或者安全要求(如线程安全) 不能够使用共享技术的对象, 该对象一般不会出现在享元工厂中。

  • FlyweightFactory-享元工厂

职责非常简单, 就是构造一个池容器, 同时提供从池中获得对象的方法。

享元模式的目的在于运用共享技术, 使得一些细粒度的对象可以共享, 我们的设计确实也应该这样, 多使用细粒度的对象, 便于重用或重构。

扫描二维码关注公众号,回复: 8677559 查看本文章

四、经典代码实现

抽象的享元角色

public abstract class FlyWeight {

    /**
     * 内部状态
     */
    private String intrinsic;

    /**
     * 外部状态
     * <p>{@code final}</p>
     */
    protected final String extrinsic;

    public FlyWeight(String extrinsic) {
        this.extrinsic = extrinsic;
    }

    /**
     * 定义业务操作
     */
    public abstract void operate();


    public String getIntrinsic() {
        return intrinsic;
    }

    public void setIntrinsic(String intrinsic) {
        this.intrinsic = intrinsic;
    }
}

抽象享元角色一般为抽象类, 在实际项目中, 一般是一个实现类, 它是描述一类事物的方法。 在抽象角色中, 一般需要把外部状态和内部状态(当然了, 可以没有内部状态, 只有行为也是可以的) 定义出来, 避免子类的随意扩展。我们再来看具体的享元角色。

具体享元角色

public class ConcreteFlyWeight01 extends FlyWeight {

    /**
     * 接受外部状态
     * @param extrinsic 外部状态
     */
    public ConcreteFlyWeight01(String extrinsic) {
        super(extrinsic);
    }

    /**
     * 根据外部状态进行逻辑处理
     */
    @Override
    public void operate() {
        System.out.println("业务处理1...");
    }
    
}
public class ConcreteFlyWeight02 extends FlyWeight {

    /**
     * 接受外部状态
     * @param extrinsic 外部状态
     */
    public ConcreteFlyWeight02(String extrinsic) {
        super(extrinsic);
    }

    /**
     * 根据外部状态进行逻辑处理
     */
    @Override
    public void operate() {
        System.out.println("业务处理2...");
    }
    
}

这很简单, 实现自己的业务逻辑, 然后接收外部状态, 以便内部业务逻辑对外部状态的依赖。 注意, 我们在抽象享元中对外部状态加上了final关键字, 防止意外产生, 什么意外?获得了一个外部状态, 然后无意修改了一下, 池就混乱了!

在程序开发中, 确认只需要一次赋值的属性则设置为final类型, 避免无意修改导致逻辑混乱, 特别是Session级的常量或变量.

享元工厂

public class FlyWeightFactory {

    /**
     * 池容器
     */
    private static HashMap<String, FlyWeight> pool = new HashMap<>(32);

    /**
     * 享元模式工厂
     * @param extrinsic key
     * @return 享元对象
     */
    public static FlyWeight getFlyWeight(String extrinsic) {
        FlyWeight flyWeight = null;
        if (pool.containsKey(extrinsic)) {
            flyWeight = pool.get(extrinsic);
        } else {
            flyWeight = new ConcreteFlyWeight01(extrinsic);
            pool.put(extrinsic, flyWeight);
        }
        return flyWeight;
    }
}

场景类

public class Main {

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            String extrinsic = "key " + i;
            FlyWeight flyWeight = FlyWeightFactory.getFlyWeight(extrinsic);
            System.out.println(flyWeight);
        }
    }
}

打印结果如下:

...
...省略了很多行
com.gyoomi.designpattern.flyweight.demo01.ConcreteFlyWeight01@1fb3ebeb
com.gyoomi.designpattern.flyweight.demo01.ConcreteFlyWeight01@548c4f57
com.gyoomi.designpattern.flyweight.demo01.ConcreteFlyWeight01@1218025c
com.gyoomi.designpattern.flyweight.demo01.ConcreteFlyWeight01@816f27d

五、享元模式的应用

5.1 享元模式的优点

享元模式是一个非常简单的模式, 它可以大大减少应用程序创建的对象, 降低程序内存的占用, 增强程序的性能, 但它同时也提高了系统复杂性, 需要分离出外部状态和内部状态, 而且外部状态具有固化特性, 不应该随内部状态改变而改变, 否则导致系统的逻辑混乱。

5.2 享元模式的缺点

在一定程度上加重了系统的复杂性。

5.3 享元模式的使用场景

  • 系统中存在大量的相似对象
  • 细粒度的对象都具备较接近的外部状态, 而且内部状态与环境无关, 也就是说对象没有特定身份
  • 需要缓冲池的场景

5.4 享元模式的线程安全问题

线程安全是一个老生常谈的话题, 只要使用Java开发都会遇到这个问题, 我们之所以要在今天的享元模式中提到该问题, 是因为该模式有太大的几率发生线程不安全, 为什么呢?

假如说我们key理解成业务上的非唯一的属性,则池中的共享对象有可能在高并发的场景下,两个或以上的取到同一的内部状态的对象,三个线程同时操作一个对象,则就出现了我们所说的线程安全问题。

所以说设置的享元对象数量太少, 导致每个线程都到对象池中获得对象, 然后都去修改其属性, 于是就出现一些不和谐数据。只要使用Java开发,线程问题是不可避免的, 那我们怎么去避免这个问题呢? 享元模式是让我们使用共享技术,而Java的多线程又有如此问题, 该如何设计呢? 没什么可以参考的标准, 只有依靠经验, 在需要的地方考虑一下线程安全, 在大部分的场景下都不用考虑。 我们在使用享元模式时, 对象池中的享元对象尽量多, 多到足够满足业务为止。

发布了158 篇原创文章 · 获赞 147 · 访问量 27万+

猜你喜欢

转载自blog.csdn.net/weixin_39723544/article/details/103075805