小侃设计模式(十一)-享元模式

1.概述

享元模式(Flyweight Pattern)也叫蝇量模式,它是运用共享技术有效地支持大量细粒度的对象,解决重复对象浪费内存的问题。它常用于系统底层的开发,解决系统的性能问题。比如数据库连接池,这里面都是已经创建好的连接对象,需要时直接拿来使用,避免重复创建。享元模式最经典的使用场景就是池技术了,String常量池、数据库连接池等、缓冲池等都是享元模式的使用。本文将介绍享元模式的原理及使用方式。

2.原理及使用

2.1 原理

享元模式的类图如下所示:
在这里插入图片描述
类图中主要包含四个核心角色:

抽象的享元角色(FlyWeight): 它是产品的抽象类,同时定义出对象的内部状态(不会随着环境的改变而改变的可共享部分)和外部状态(随环境改变而改变的不可以共享的部分);
具体的享元角色(ConcreteFlyWeight):是具体的产品类,实现抽象角色定义的相关业务;
不可共享角色(UnsharedConcreteFlyWeight): 不能被共享的子类可设计成不可共享角色;
享元工厂类(FlyweightFactory):负责创建和管理享元角色,当使用者创建享元对象时,会由享元工厂先从工厂中获取,若存在则对外提供,不存在则由工厂创建。

2.2 案例

如果要开发一个在线斗地主游戏,已知一副牌有54张,如果每张牌都是一个单独的对象,也就是一桌需要存储54个对象,如果有1000、10000、1000000桌的话,需要存储的对象分别是100054,1000054,1000000*54,很明显这不合理。

通过享元模式来设计上述场景,如下:
在这里插入图片描述

抽象的享元角色就是Poker,它的内部分别包含了两个抽象方法,发牌方法push()和获取当前牌的状态;
享元角色就是ConcretePoker,它的内部有两个属性:牌的数字(number)和花色(color),分别有两个方法getNumber()、getColor()来获取当前牌的数字和花色;
不可共享角色就是UnsharedConcretePoker,内部有一个属性status(当前牌的状态:已出、未出),可能还存储张牌的用户信息userId等这些不可共享角色;
享元工厂就是PokerFactory,内部有一个pokers数组,用来存储享元角色ConcretePoker。
这样划分的好处是:整个系统内存中只存储了54张牌的信息,即使参与的人数再多,变更的也只是不可共享角色部分,极大地节约了内存资源。

编码如下:

public abstract class Poker {

    abstract void push();

    abstract boolean getStatus();
}


public class ConcretePoker extends Poker {

    public ConcretePoker(String number, String color) {
        this.number = number;
        this.color = color;
    }

    private String number;

    private String color;

    public String getNumber() {
        return number;
    }

    public String getColor() {
        return color;
    }

    @Override
    public String toString() {
        return "ConcretePoker{" +
                "number='" + number + '\'' +
                ", color='" + color + '\'' +
                '}';
    }

    @Override
    void push() {
        throw new UnsupportedOperationException();
    }

    @Override
    boolean getStatus() {
        throw new UnsupportedOperationException();
    }
}

public class UnsharedConcretePoker extends Poker {

    public UnsharedConcretePoker(Long userId) {
        this.userId = userId;
    }

    private Boolean status;

    private Long userId;

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public void setStatus(boolean status) {
        this.status = status;
    }

    @Override
    void push() {
        System.out.println("牌已经被打出");
        setStatus(true);
    }

    @Override
    boolean getStatus() {
        System.out.println("牌的状态是:" + status);
        return status;
    }
}


public class PokerFactory {

    private HashMap<String, ConcretePoker> pokers = new HashMap<>();


    private void addPoker(ConcretePoker poker) {
        //以牌的number+":"+color来做key
        String key = poker.getNumber() + ":" + poker.getColor();
        pokers.put(key, poker);
        System.out.println("添加牌:" + poker.toString() + "成功");
    }

    public ConcretePoker getPoker(String key) {
        ConcretePoker concretePoker = pokers.get(key);
        //双重验证,多线程安全
        if (concretePoker == null) {
            synchronized (PokerFactory.class) {
                ConcretePoker concretePoker1 = pokers.get(key);
                if (concretePoker1 == null) {
                    //创建ConcretePoker对象放入列表
                    String[] split = key.split(":");
                    ConcretePoker concretePoker2 = new ConcretePoker(split[0], split[1]);
                    pokers.put(key, concretePoker2);
                    System.out.println("工厂添加牌:" + concretePoker2.toString() + "成功");
                    return concretePoker2;
                }
            }
        } else {
            System.out.println("工厂已存在牌:" + concretePoker.toString());
        }
        return concretePoker;
    }
}

//测试类
public class Test {
    public static void main(String[] args) {
        String[] strings = new String[]{"A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "Small", "Big"};
        PokerFactory pokerFactory = new PokerFactory();

        //Hearts:红桃,Spades:黑桃,Diamond:方片,Clubs:梅花
        String[] colors = new String[]{"Hearts", "Spades", "Diamond", "Clubs"};
        Random random = new Random();
        for (int i = 0; i < 20; i++) {
            int index = random.nextInt(strings.length);
            String number = strings[index];
            if (!number.equals("Small") && !number.equals("Big")) {
                int colorIndex = random.nextInt(colors.length);
                String color = colors[colorIndex];
                String key = number + ":" + color;
                ConcretePoker poker = pokerFactory.getPoker(key);
            } else {
                if (number.equals("Small")) {
                    String key = "Small:King";
                    ConcretePoker poker = pokerFactory.getPoker(key);
                } else {
                    String key = "Big:King";
                    ConcretePoker poker = pokerFactory.getPoker(key);
                }
            }
        }
    }
}

运行结果如下:
在这里插入图片描述

2.3 享元模式的应用

享元模式在各种池的场景中使用较多,这里以Integer常量池为例,当值处于-128到127时,会从缓存池中获取,否则会生成新的对象指向所创建对象的地址。
在这里插入图片描述

3.小结

1.享元模式大大地减少了对象地创建,降低了程序内存的应用,提高了效率;
2.使用享元模式时,需要注意划分内部状态和外部状态,并且需要一个工厂类加以控制;
3.享元模式的使用场景是系统中存在大量对象,且这些对象消耗大量内存,这些对象的状态大部分可以外部化;
4.享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变。

4.参考文献

1.《设计模式-可复用面向对象软件的基础》-Erich Gamma、Richard Helm、Ralph Johnson、John Vlissides
2.《可复用物联网Web3D框架的设计与实现》-程亮(知网)
3.https://www.bilibili.com/video/BV1G4411c7N4-尚硅谷设计模式
4.https://zhuanlan.zhihu.com/p/74872012

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值