设计模式之享元模式

概述

享元模式(FlyWeight):运用共享技术有效的支持大量细粒度对象的重用。

享元对象能做到共享的关键就是区分了内部状态外部状态

内部状态:可以共享,不会随环境变化而变化

外部状态:不可以共享,会随环境变化而变化

使用场景

  • 系统中存在大量的相似对象
  • 细粒度的对象都具备较接近的外部状态,而且内部状态与环境无关,也就是说对象没有特定身份.
  • 需要缓冲池的场景比如线程池,数据库连接池,这些都利用享元模式共享了部分属性,在池中操作。
  • String类的设计也是享元模式

UML

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

单纯享元模式

在单纯的享元模式中,所有的享元对象都是可以共享的.抽象享元模式UML如下

其中涉及到的角色有:

  • Flyweight (抽象享元角色): 享元对象抽象基类或者接口,规定出所有具有享元角色需要实现的方法.
  • ConcreteFlyweight (具体享元角色) : 具体的享元对象,实现了Flyweight接口
  • FlyweightFactory (享元工厂角色) : 享元工厂,负责管理享元对象池和创建享元对象.本角色必须保证享元对象可以被系统适当地共享.
复合享元模式

复合享元模式将一些单纯享元使用合成模式加以复合.形成复合享元对象,复合享元对象本身不能共享,但可以分解为单纯享元对象实现共享,复合享元对象的UML如下

其中涉及到的角色

  • Flyweight (抽象享元角色) : 享元对象的抽象接口.
  • ConcreteFlyweight (具体享元角色) : 实现了Flyweight接口的享元角色.
  • ConcreteCompositeFlyweight (复合享元角色) : 单纯享元对象的组合,不可共享的享元对象.
  • FlyweightFactory (享元工厂角色) : 创建和管理享元角色.

实例

单纯享元

如下以火车票售票系统为例模拟,当数以万计的人查询票价信息时,假设 出发地-终点地,上铺,下铺,坐票看做是享元对象进行缓存,减少服务器压力,如下是一个单纯享元模式示例

  • 首先创建Flyweight
package com.dyleaf.structure.FlyweightPattern.Simple;

/**
 * 抽象享元类
 */
public interface Ticket {
    public void showTicketInfo(String bunk);  //外部状态传入
}
  • 创建ConcreteFlyweight对象,这里是火车票
package com.dyleaf.structure.FlyweightPattern.Simple;

import Struct.FlyweightPattern.Simple.Ticket;

import java.util.Random;

/**
 * 具体享元角色类
 */
public class TrainTicket implements Ticket {

    public String from;
    public String to;
    public String bunk;
    public int price;

    //负责为内部状态提供存储空间
    public TrainTicket(String from, String to) {
        this.from = from;
        this.to = to;
    }

    @Override
    public void showTicketInfo(String bunk) {
        price = new Random().nextInt(300);
        System.out.println("购买 从" + from + " 到" + to + "的" + bunk + "火车票" + "," + "价格" + price);
    }
}

  • 创建FlyweightFactory享元工厂
package com.dyleaf.structure.FlyweightPattern.Simple;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


/**
 * 享元工厂
 */
public class TicketFactory {
    public static Map<String, Ticket> stringTicketMap = new ConcurrentHashMap<>(); //内部状态

    public static Ticket getTicket(String from, String to) {
        String key = from + "-" + to;
        if (stringTicketMap.containsKey(key)) {
            System.out.println("使用缓存-->" + key);
            return stringTicketMap.get(key);
        } else {
            System.out.println("创建对象-->" + key);
            Ticket ticket = new TrainTicket(from, to);
            stringTicketMap.put(key,ticket);
            return new TrainTicket(from, to);
        }
    }
}

  • Client调用代码如下
package com.dyleaf.structure.FlyweightPattern.Simple;

public class Test {
    public static void main(String[] args) {
        Ticket ticket1 = TicketFactory.getTicket("北京", "青岛");
        ticket1.showTicketInfo("上铺");
        Ticket ticket2 = TicketFactory.getTicket("北京", "青岛");
        ticket2.showTicketInfo("下铺");
        Ticket ticket3 = TicketFactory.getTicket("北京", "青岛");
        ticket3.showTicketInfo("坐票");
    }
}

输出

创建对象-->北京-青岛
购买 从北京 到青岛的上铺火车票,价格161
使用缓存-->北京-青岛
购买 从北京 到青岛的下铺火车票,价格259
使用缓存-->北京-青岛
购买 从北京 到青岛的坐票火车票,价格162

复杂享元

  • 首先创建Flyweight
package com.dyleaf.structure.FlyweightPattern.Complex;

/**
 * 抽象享元类
 */
public interface Ticket {
    public void showTicketInfo(String bunk);  //外部状态传入
}
  • 创建ConcreteFlyweight对象,这里是火车票
package com.dyleaf.structure.FlyweightPattern.Complex;

import Struct.FlyweightPattern.Simple.Ticket;

import java.util.Random;

/**
 * 具体享元角色类
 */
public class TrainTicket implements Ticket {

    public String from;
    public String to;
    public String bunk;
    public int price;

    //负责为内部状态提供存储空间
    public TrainTicket(String from, String to) {
        this.from = from;
        this.to = to;
    }

    @Override
    public void showTicketInfo(String bunk) {
        price = new Random().nextInt(300);
        System.out.println("购买 从" + from + " 到" + to + "的" + bunk + "火车票" + "," + "价格" + price);
    }
}

  • 创建ConcreteCompositeFlyweight对象,这里是指定位置的火车票
package com.dyleaf.structure.FlyweightPattern.Complex;

import java.util.HashMap;
import java.util.Map;

public class CompositeTrainTicket implements Ticket {

    private Map<String, Ticket> map = new HashMap<>();

    /**
     * 增加一个新的单纯享元对象到聚集中
     */
    public void add(String key, Ticket trainTicket) {
        map.put(key, trainTicket);
    }

    /**
     * 外蕴状态作为参数传入到方法中
     */
    @Override
    public void showTicketInfo(String bunk) {
        Ticket ticket = null;
        for (Object o:map.keySet()) {
            ticket = map.get(o);
            ticket.showTicketInfo(bunk);
        }
    }
}

  • 创建FlyweightFactory享元工厂
package com.dyleaf.structure.FlyweightPattern.Complex;

import jdk.internal.util.xml.impl.Pair;

import java.security.KeyPair;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


/**
 * 享元工厂
 */
public class TicketFactory {
    public static Map<String, Ticket> stringTicketMap = new ConcurrentHashMap<>(); //内部状态


    public Ticket getTicket(List<Map.Entry<String,String>> fromToPair){
        CompositeTrainTicket ticket = new CompositeTrainTicket();
        for (Map.Entry p:fromToPair) {
            ticket.add(p.getKey()+"-"+p.getValue(), this.getTicket((String) p.getKey(), (String) p.getValue()));
        }
        return ticket;
    }

    public static Ticket getTicket(String from, String to) {
        String key = from + "-" + to;
        if (stringTicketMap.containsKey(key)) {
            System.out.println("使用缓存-->" + key);
            return stringTicketMap.get(key);
        } else {
            System.out.println("创建对象-->" + key);
            Ticket ticket = new TrainTicket(from, to);
            stringTicketMap.put(key,ticket);
            return new TrainTicket(from, to);
        }
    }
}

  • Client调用代码如下
package com.dyleaf.structure.FlyweightPattern.Complex;

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class Test {
    public static void main(String[] args) {
        List<Map.Entry<String, String>> list = new ArrayList<>();
        list.add(new AbstractMap.SimpleEntry<String, String>("北京", "青岛"));
        list.add(new AbstractMap.SimpleEntry<String, String>("北京", "杭州"));
        list.add(new AbstractMap.SimpleEntry<String, String>("北京", "福州"));

        TicketFactory ticketFactory = new TicketFactory();
        CompositeTrainTicket ticket1 = (CompositeTrainTicket) ticketFactory.getTicket(list);
        CompositeTrainTicket ticket2 = (CompositeTrainTicket) ticketFactory.getTicket(list);
        ticket1.showTicketInfo("下铺");
        ticket2.showTicketInfo("上铺");

    }
}

输出

创建对象-->北京-青岛
创建对象-->北京-杭州
创建对象-->北京-福州
使用缓存-->北京-青岛
使用缓存-->北京-杭州
使用缓存-->北京-福州
购买 从北京 到福州的下铺火车票,价格83
购买 从北京 到青岛的下铺火车票,价格249
购买 从北京 到杭州的下铺火车票,价格245
购买 从北京 到福州的上铺火车票,价格85
购买 从北京 到青岛的上铺火车票,价格36
购买 从北京 到杭州的上铺火车票,价格141

优缺点

优点:

1.极大的减少内存中对象的数量

2.相同或相似对象内存中只存在一份,极大的节约资源,提高系统性能

3.外部状态相对独立,不影响内部状态

缺点:

1.模式较复杂,使程序逻辑复杂化

2.为了节省内存,共享了内部状态,分离出外部状态,而读取外部状态使运行时间变长。

see source code

原文地址:https://www.cnblogs.com/Dyleaf/p/8507045.html