设计模式之享元模式详解

设计模式之享元模式详解

概述

享元模式定义:

运用共享技术来有效地支持大量细粒度对象的复用。它==通过共享已经存在的对象来大幅度减少需要创建的对象数量==、避免大量相似对象的开销,从而提高系统资源的利用率。

设计模式之享元模式详解_第1张图片

结构

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

享元模式以共享的方式高效地支持大量细粒度对象的重用,能做到共享的关键就是区分了内部状态(Intrinsic State)和外部状态(Extrinsic State)。

  1. 内部状态,即不会随着环境的改变而改变的可共享部分。
  2. 外部状态,指随环境改变而改变的不可以共享的部分。(可以使用参数形式进行传递)享元对象的外部状态通常由客户端保存,并在享元对象被创建之后,需要使用的时候再传入到享元对象内部。一个外部状态与另一个外部状态之间是相互独立的。

享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化。

正因为区分了内部状态和外部状态,我们可以将具有相同内部状态的对象存储在享元池中,享元池中的对象是可以实现共享的,需要的时候就将对象从享元池中取出,实现对象的复用。通过向取出的对象注入不同的外部状态,可以得到一系列相似的对象,而这些对象在内存中实际上只存储一份。

享元模式结构较为复杂,一般结合工厂模式一起使用。主要有以下角色:

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

在享元模式中引入了享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池,当用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。

案例实现

【例】俄罗斯方块

下面的图片是众所周知的俄罗斯方块中的一个个方块,这些方块有形状相同但颜色不一样的,如果在俄罗斯方块这个游戏中,每个不同或形状相同但颜色不一样的的方块都是一个实例对象,这些对象就要占用很多的内存空间,我们利用享元模式进行实现,将相同形状不同颜色的颜色看成是可变的外部状态。

先来看类图:
设计模式之享元模式详解_第2张图片
代码如下:

享元类的设计是享元模式的要点之一,在享元类中要将内部状态和外部状态分开处理,通常将内部状态作为享元类的成员变量,而外部状态通过注入或形参的方式添加到享元类中。

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

public abstract class AbstractBox {
    //获取图形
    public abstract String getShape();
	//显示图形及颜色,颜色在调用该方法时由外部参数传递
    public void display(String color) {
        System.out.println("方块形状:" + this.getShape() + " 颜色:" + color);
    }
}

接下来就是定义不同的形状了,IBox类、LBox类、OBox类等,属于具体享元角色(根据情况可以使用单例模式)。

public class IBox extends AbstractBox {

    @Override
    public String getShape() {
        return "I";
    }
}

public class LBox extends AbstractBox {

    @Override
    public String getShape() {
        return "L";
    }
}

public class OBox extends AbstractBox {

    @Override
    public String getShape() {
        return "O";
    }
}

提供了一个工厂类(BoxFactory),用来管理享元对象(也就是AbstractBox子类对象),该工厂类对象只需要一个,所以可以使用单例模式。并给工厂类提供一个获取形状的方法。

public class BoxFactory {
	//这个map是用来存储已经创建的具体图形对象,当你需要具体形状时可以从该对象中获取
    private static HashMap<String, AbstractBox> map;

    private BoxFactory() {
        map = new HashMap<String, AbstractBox>();
        AbstractBox iBox = new IBox();
        AbstractBox lBox = new LBox();
        AbstractBox oBox = new OBox();
        map.put("I", iBox);
        map.put("L", lBox);
        map.put("O", oBox);
    }

    public static final BoxFactory getInstance() {
        return SingletonHolder.INSTANCE;
    }

    private static class SingletonHolder {
        private static final BoxFactory INSTANCE = new BoxFactory();
    }

    //根据图像名称获取对象
    public AbstractBox getBox(String key) {
        return map.get(key);
    }
}

设计模式之享元模式详解_第3张图片

享元模式补充

与其他模式使用

享元模式通常需要和其他模式一起联用,几种常见的联用方式如下:

(1)在享元模式的享元工厂类中通常提供一个静态的工厂方法用于返回享元对象,使用简单工厂模式来生成享元对象。

(2)在一个系统中,通常只有唯一一个享元工厂,因此可以使用单例模式进行享元工厂类的设计。

(3)享元模式可以结合组合模式形成复合享元模式,统一对多个享元对象设置外部状态。

单纯享元模式和复合享元模式

单纯享元模式

在单纯享元模式中,所有的具体享元类都是可以共享的,不存在非共享具体享元类。

复合享元模式

将一些单纯享元对象使用组合模式加以组合,还可以形成复合享元对象,这样的复合享元对象本身不能共享,但是它们可以分解成单纯享元对象,而后者则可以共享。
通过复合享元模式,可以确保复合享元类中所包含的每个单纯享元类都具有相同的外部状态,而这些单纯享元的内部状态往往可以不同。如果希望为多个内部状态不同的享元对象设置相同的外部状态,可以考虑使用复合享元模式。

优缺点和使用场景

当系统中存在大量相同或者相似的对象时,享元模式是一种较好的解决方案,它通过共享技术实现相同或相似的细粒度对象的复用,从而节约了内存空间,提高了系统性能。相比其他结构型设计模式,享元模式的使用频率并不算太高,但是作为一种以“节约内存,提高性能”为出发点的设计模式,它在软件开发中还是得到了一定程度的应用。

1,优点

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

2,缺点:

为了使对象可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态,使程序逻辑复杂

3,使用场景:

  • 一个系统有大量相同或者相似的对象,造成内存的大量耗费。
  • 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。
  • 在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源,因此,应当在需要多次重复使用享元对象时才值得使用享元模式。(比如说一开始就将享元模式中的对象添加进内存用的次数却寥寥无几,这样享元模式也就失去了意义)

你可能感兴趣的:(Java,设计模式,设计模式,享元模式,状态模式)