【设计模式】享元模式

设计享元模式的意图是什么?

  • 复用对象,节省内存(当然前提是享元对象是不可变的对象)

享元模式需要解决什么问题?

如果一个软件系统在运行时所创建的相同或相似对象数量太多,将会导致运行代价过高,带来系统资源浪费、性能下降等问题。

所以,对于这种情况,享元对象有大量的对象可以复用,在服务端减少接口的调用,在客户端减少内存的占用

UML结构图

什么时候可以使用这个享元模式?

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

(生活实例、软件实例)应用实例

底层应用实例(这些这里没有进行总结,以后补充,可以看看其他博主的)

  • String常量池:没有的对象创建后存在池中,若池中存在该对象则直接从池中取出。
  • 数据库连接池

在生活中

1、比如我们想要开发一款棋盘类游戏每次设置一句游戏是不是都得设置一个基础棋盘桌,还有一副基础棋子。这时候如果超过100万人同时在线,那么我们就得同时new了几十万个棋盘桌以及大量的棋牌。

所以相对来说,想一想可以尝试利用享元模式来进行相对的优化,首先我们这个棋盘桌是不是可以进行优化,因为这得记录每一句游戏的状态,还有桌上棋牌的情况等。但是对于一副棋牌来说,我们确是可以利用享元模式来缓存一副,这样就可以所有人都同时共用一副缓存的棋盘了,这样就可以大大优化内存。

实例

内部状态与外部状态

享元模式的定义就需要两个要求,但是要求细粒度对象,所以不可避免就会使对象数量多且性质相近,所以就可以把这些对象信息分为两类:外部状态和内部状态
内部状态:指对象共享出来的信息,存储在享元对象内部并且不会随环境的改变而改变;
(内部状态可以共享(例如:字符的内容))
外部状态:指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。
(享元对象的外部状态通常由客户端保存,并在享元对象被创建之后,需要使用的时候再传入到享元对象内部。)(一个外部状态与另一个外部状态之间是相互独立的(例如:字符的颜色和大小))

我们举一个最简单的例子,棋牌类游戏大家都有玩过吧,比如说说围棋和跳棋,它们都有大量的棋子对象,围棋和五子棋只有黑白两色,跳棋颜色略多一点,但也是不太变化的,所以棋子颜色就是棋子的内部状态;而各个棋子之间的差别就是位置的不同,我们落子嘛,落子颜色是定的,但位置是变化的,所以方位坐标就是棋子的外部状态。

  那么为什么这里要用享元模式呢?可以想象一下,上面提到的棋类游戏的例子,比如围棋,理论上有361个空位可以放棋子,常规情况下每盘棋都有可能有两三百个棋子对象产生,因为内存空间有限,一台服务器很难支持更多的玩家玩围棋游戏,如果用享元模式来处理棋子,那么棋子对象就可以减少到只有两个实例,这样就很好的解决了对象的开销问题。

1)无外部状态(只有内部状态)

共享网络设备(无外部状态):实例说明

很多网络设备都是支持共享的,如交换机、集线器等,多台终端计算机可以连接同一台网络设备,并通过该网络设备进行数据转发,如图所示,现用享元模式模拟共享网络设备的设计原理。


public class Hub implements NetworkDevice{
    private String type;

    public Hub() {
    }

    public Hub(String type) {
        this.type = type;
    }

    @Override
    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    @Override
    public void use(){
        System.out.println("使用的网络设备"+ type);
    }
}
public class Switch implements NetworkDevice{
    private String type;

    public Switch() {
    }

    public Switch(String type) {
        this.type = type;
    }

    @Override
    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    @Override
    public void use(){
        System.out.println("使用的网络设备"+ type);
    }
}
public interface NetworkDevice {
    /**
     * @return 返回使用的不同type
     */
    public String getType();

    public void use();
}
无外部状态的享元模式的工程类
public class DeviceFactory {

    ArrayList<NetworkDevice> devices = new ArrayList<>();
    int totalTerminal = 0;

    public DeviceFactory(){
        NetworkDevice nd1=new Switch("Cisco Catalyst 9400 系列交换机");
        devices.add(nd1);
        NetworkDevice nd2=new Hub("TP-LINK-????");
        devices.add(nd2);
    }

    public NetworkDevice getNetworkDevice(String type){
        if("Cisco".equalsIgnoreCase(type))
        {
            totalTerminal++;
            return (NetworkDevice)devices.get(0);
        }
        else if("tp".equalsIgnoreCase(type))
        {
            totalTerminal++;
            return (NetworkDevice)devices.get(1);
        }
        else
        {
            return null;
        }
    }

    /**
     * 有几种类型的网络设备
     * @return 网络设备类型
     */
    public int getTotalDevice(){
        return devices.size();
    }

    /**
     * 总共有多少网络设备
     * @return 网络设备数量
     */
    public int getTotalTerminal(){
        return totalTerminal;
    }
}

2)有外部状态

共享网络设备(有外部状态):实例说明

虽然网络设备可以共享,但是分配给每一个终端计算机的端口(Port)是不同的,因此多台计算机虽然可以共享同一个网络设备,但必须使用不同的端口。我们可以将端口从网络设备中抽取出来作为外部状态,需要时再进行设置。

public class Hub implements NetworkDevice {
    private String type;

    public Hub() {
    }

    public Hub(String type) {
        this.type = type;
    }

    @Override
    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public void use(Port port) {
        System.out.println("使用的网络设备"+ this.type + "port是"+ port.getPort());
    }
}
public class Switch implements NetworkDevice {
    private String type;

    public Switch() {
    }

    public Switch(String type) {
        this.type = type;
    }

    @Override
    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public void use(Port port){
        System.out.println("使用的网络设备"+ this.type + "port是"+ port.getPort());
    }
}
public class Port
{
    private String port;

    public Port(String port)
    {
        this.port=port;
    }

    public void setPort(String port)
    {
        this.port=port;
    }

    public String getPort()
    {
        return this.port;
    }
}
public interface NetworkDevice {

    public String getType();

    public void use(Port port);
}
有外部状态的享元模式的工程类 ``` public class DeviceFactory {
ArrayList<NetworkDevice> devices = new ArrayList<>();
int totalTerminal = 0;

public DeviceFactory(){
    NetworkDevice nd1=new Switch("Cisco Catalyst 9400 系列交换机");
    devices.add(nd1);
    NetworkDevice nd2=new Hub("TP-LINK-????");
    devices.add(nd2);
}

public NetworkDevice getNetworkDevice(String type){
    if("Cisco".equalsIgnoreCase(type))
    {
        totalTerminal++;
        return devices.get(0);
    }
    else if("tp".equalsIgnoreCase(type))
    {
        totalTerminal++;
        return (NetworkDevice)devices.get(1);
    }
    else
    {
        return null;
    }
}

/**
 * 有几种类型的网络设备
 * @return 网络设备类型
 */
public int getTotalDevice(){
    return devices.size();
}

/**
 * 总共有多少网络设备
 * @return 网络设备数量
 */
public int getTotalTerminal(){
    return totalTerminal;
}

}

</details>

##享元模式的优缺点?

其实享元模式并不是利用的很多
### 优点
* 大大减少了对象的创建,降低了程序内存的占用,提高效率(这个前面说的太多了)
* 外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享

### 缺点
* 提高系统复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变
* 为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长
##使用享元模式的时候需要注意什么?
* 一定需要区分内部状态和外部状态(否则会引起线程安全问题)(内部状态不可以从客户端设置,而外部状态必须从客户端设置,该模式可以提高内存使用率)
* 这些类必须有一个工厂类加以控制
posted @ 2022-10-19 21:44  雨季悠理  阅读(62)  评论(0编辑  收藏  举报