享元模式

作者: Jackson杰 | 来源:发表于2019-04-10 13:53 被阅读14次

    一 定义

    享元模式是池技术的重要实现。

    定义:使用共享对象可有效地支持大量的细粒度的对象。

    享元模式的定义为我们提出了两个要求,细粒度的对象和共享对象。我们知道创建过多的对象分配到内存中很容易造成内存溢出,享元模式就可以尽可能地减少内存的使用量,比较适用于可能存在大量重复对象的场景,通过共享技术,避免创建过多的对象。

    先来了解两个概念:内部状态和外部状态

    • 内部状态
      内部状态是对象可以共享出来的信息,存储在享元对象内部并且不会随着环境改变而改变。

    • 外部状态
      外部状态是对象得以依赖的一个标记,是随着环境改变而改变的,不可以共享的状态。

    二 模式结构

    角色介绍:

    • Flyweight:抽象享元角色
      享元对象的抽象基类或者接口。

    • ConcreteFlyweight:具体享元角色
      具体的一个产品类,实现抽象角色定义的业务。

    • FlyweightFactory:享元工厂
      负责管理享元对象池和创建享元对象。

    三 实例

    • 抽象享元角色,一般为抽象类。在抽象角色中,一般需要把外部状态和内部状态定义出来,当然,如果没有内部状态,也是可以的。
      内部状态是共享出来的,不随环境改变而改变。
      外部状态是对象作为依赖的标记。
    public abstract class Flyweight {
        // 内部状态
        private String intrinsic;
    
        // 外部状态
        protected final String Extrinsic;
    
        // 要去享元角色必须接收外部状态
        public Flyweight(String extrinsic){
            this.Extrinsic=extrinsic;
        }
    
        // 定义业务操作
        public abstract void operate();
    
        // 内部状态的getter/setter
    
        public String getIntrinsic() {
            return intrinsic;
        }
    
        public void setIntrinsic(String intrinsic) {
            this.intrinsic = intrinsic;
        }
    }
    
    • 具体的享元角色
      实现自己的业务逻辑,然后接收外部状态,以便内部业务逻辑对外部状态的依赖。
    public class ConcreteFlyweight extends Flyweight{
        // 接受外部状态
        public ConcreteFlyweight(String extrinsic) {
            super(extrinsic);
        }
    
        // 根据外部状态进行逻辑处理
        @Override
        public void operate() {
            // 业务处理
    
        }
    }
    
    • 不需要共享的Flyweight子类
    public class UnsharedConcreteFlyweight extends Flyweight{
    
        public UnsharedConcreteFlyweight(String extrinsic) {
            super(extrinsic);
        }
    
        @Override
        public void operate() {
    
        }
    }
    
    • 享元工厂
    public class FlyweightFactory {
        // 定义一个池容器
        private static HashMap<String, Flyweight> pool = new HashMap<>();
    
        // 享元工厂
        public static Flyweight getFlyweight(String Extrinsic) {
    
            // 需要返回的对象
            Flyweight flyweight=null;
    
            // 在池中没有该对象
            if (pool.containsKey(Extrinsic)){
                flyweight=pool.get(Extrinsic);
                System.out.print("已有 " + Extrinsic + " 直接从池中取---->\n");
            }else {
                // 根据外部状态创建享元对象
                flyweight=new ConcreteFlyweight(Extrinsic);
                // 放置到池中
                pool.put(Extrinsic,flyweight);
                System.out.print("创建 " + Extrinsic + " 并从池中取出---->\n");
            }
    
            return flyweight;
    
        }
    }
    
    • 测试代码
     Flyweight flyweightX = FlyweightFactory.getFlyweight("X");
     Flyweight flyweightY = FlyweightFactory.getFlyweight("Y");
      Flyweight flyweightZ = FlyweightFactory.getFlyweight("Z");
      Flyweight flyweightReX = FlyweightFactory.getFlyweight("X");
       Flyweight unsharedFlyweight = new UnsharedConcreteFlyweight("X");
    
    • 运行结果


    我们以过年回家买火车票为例,过年回家买火车票是件痛苦的事情,很多人都用书票软件向服务端发出请求,对于每个请求,服务端必须做出应答。在用户设置了出发地和目的地后,每次请求都要返回查询结果,当无数的人请求时,如果每次都需要重新创建一个查询结果,那么必然造成大量重复对象的创建,销毁等,使得内存占用率高居不下,所以这个问题可以通过享元模式得到很好的解决。
    对于一列火车来说,我们拿高铁举例,
    不变的状态比如高铁的名字都是复兴号。
    变化的状态是出发城市和终止城市。

    • 创建抽象享元对象
      我们把火车的始发城市和终止城市作为外部状态,然后定义业务逻辑,根据座位等级,显示车票票价。
    public abstract class Ticket {
    
        // 内部状态
        private String name="和谐号";
    
        // 外部状态
        protected String from;
        protected String to;
    
        public Ticket(String from,String to){
            this.from=from;
            this.to=to;
        }
    
        // 定义业务逻辑,根据座位等级,显示价格
        public abstract void showTicketInfo(String seat);
    
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
    }
    
    • 具体的享元角色
    public class TrainTicket extends Ticket{
    
    
        public TrainTicket(String from, String to) {
            super(from, to);
        }
    
        @Override
        public void showTicketInfo(String seat) {
            if (from.equals("北京") && to.equals("青岛")){
                switch (seat){
                    case "business":
                        System.out.println(from+"到"+to+getName()+"列车:商务座的价格是988元!");
                        break;
                    case "one":
                        System.out.println(from+"到"+to+getName()+"列车:一等座的价格是689元!");
                        break;
                    case "two":
                        System.out.println(from+"到"+to+getName()+"列车:二等座的价格是298元!");
                        break;
                }
            }else if (from.equals("北京") && to.equals("济南")){
                switch (seat){
                    case "business":
                        System.out.println(from+"到"+to+getName()+"列车:商务座的价格是598元!");
                        break;
                    case "one":
                        System.out.println(from+"到"+to+getName()+"列车:一等座的价格是389元!");
                        break;
                    case "two":
                        System.out.println(from+"到"+to+getName()+"列车:二等座的价格是186元!");
                        break;
                }
            }
    
        }
    }
    
    • 享元工厂
      我们以 "出发地-目的地" 为键,存储出票信息的对象,这个是对象得以依赖的标记,就这样,如果缓存中没有这个对象,则创建这个对象,并将对象加入到缓存中,如果有则直接从缓存中取出。
    public class TicketFactory {
    
        // 定义一个池容器
        private static Map<String,Ticket> pool=new HashMap<>();
    
        // 享元工厂
        public static Ticket getTicket(String from,String to){
            String key=from+"-"+to;
            if (pool.containsKey(key)){
                System.out.println("使用缓存==>"+key);
                return pool.get(key);
            }else {
                System.out.println("创建对象==>"+key);
                Ticket ticket=new TrainTicket(from,to);
                pool.put(key, ticket);
                return ticket;
            }
        }
    }
    
    • 测试代码
    
           Ticket ticket01=TicketFactory.getTicket("北京","青岛");
           ticket01.showTicketInfo("business");
    
           Ticket ticket02=TicketFactory.getTicket("北京","青岛");
           ticket02.showTicketInfo("one");
    
           Ticket ticket03=TicketFactory.getTicket("北京","青岛");
           ticket03.showTicketInfo("two");
    
           Ticket ticket04=TicketFactory.getTicket("北京","济南");
           ticket04.showTicketInfo("two");
    
    • 运行结果


    从运行结果可以看出,除了第一次是创建的对象以外,其它的都是使用缓存的对象。

    四 优缺点

    • 优点:享元模式可以大大减少应用程序创建的对象,降低应用程序内存的占用,增强程序的性能
    • 缺点:享元模式提高了系统的复杂性,需要分离出外部状态和内部状态,而且外部状态具有固化效果,导致系统的逻辑混乱。

    五 使用场景

    • 系统中存在大量的相似对象。
    • 细粒度的对象都具备较接近的外部状态,而且内部状态和环境无关。
    • 需要缓冲池的场景。

    相关文章

      网友评论

        本文标题:享元模式

        本文链接:https://www.haomeiwen.com/subject/ftxabqtx.html