zoukankan      html  css  js  c++  java
  • 【设计模式(11)】结构型模式之享元模式

    个人学习笔记分享,当前能力有限,请勿贬低,菜鸟互学,大佬绕道

    如有勘误,欢迎指出和讨论,本文后期也会进行修正和补充


    前言

    对于后端开发者而言,池化技术相当常见,比如线程池、数据库连接池、缓冲池,以及最最常见的Bean

    对于Bean池,在系统初始化的时候初始化装载Bean对象,系统中需要使用的时候直接调用Bean池中的对象即可,而无需每次都去初始化一遍,以节省资源消耗


    针对String的性能优化,Java引用了缓存池的概念,即创建String类型数据的时候,会检查缓存池中是否有相同内容的String类型对象,如果有会被直接引用,没有则会创建一个存入缓存池,从而避免重复创建String对象的无谓资源消耗

    Integer型数据默认会有-128~127的缓存池,以规避这些高频率出现的数据的重复创建过程,节省资源


    享元(Flyweight)模式运用共享技术,通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。


    1.介绍

    使用目的:运用共享技术来复用需要重复使用的资源

    使用时机:系统中需要使用大量对象,且系统不依赖这些对象的身份,且这些对象可以分组,每组都可以用一个对象来代替

    解决问题:系统中存在大量相同的对象,造成无谓的资源消耗,甚至造成内存溢出

    实现方法:用 HashMap 存储这些对象,并使用唯一标识标记,对于不存在的对象创建并存入,已存在的则直接取出使用

    应用实例:

    • Java中的String缓存
    • 池化技术,如线程池、连接池、bean池等

    优点:减少了系统的资源消耗,降低了系统压力,提高执行效率

    缺点:提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。

    注意事项

    • 注意划分外部状态和内部状态,否则容易引起线程安全问题
    • 这些对象最好由工厂控制

    2.结构

    享元模式的主要角色有如下。

    • 抽象享元角色(Flyweight):是所有的具体享元类的基类,为具体享元规范需要实现的公共接口。
    • 具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口。
    • 享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。

    image-20201027145103678

    • 客户端(client)调用享元工厂(Flyweight Factory)角色中的方法
    • 享元工厂通过HashMap结构持有具体享元(Concrete Flyweight)角色对象
    • 具体享元角色则需要实现接口Flyweight

    此图中未包括复杂数据的存取,请按照实际需求调整,实际上的数据肯定更加复杂


    3.实现

    1. 定义抽象享元角色(Flyweight)

      interface Flyweight {
          void operation();
      }
      
    2. 定义具体享元(Concrete Flyweight)角色,实现接口Flyweight

      class ConcreteFlyweight implements Flyweight {
          private String key;
      
          ConcreteFlyweight(String key) {
              this.key = key;
              System.out.println("具体享元" + key + "被创建!");
          }
      
          public void operation() {
              System.out.println("具体享元" + key + "被调用,");
          }
      }
      
    3. 定义享元工厂(Flyweight Factory)角色,管理Concrete Flyweight

      class FlyweightFactory {
          private HashMap<String, Flyweight> flyweights = new HashMap<>();
      
          public Flyweight getFlyweight(String key) {
              Flyweight flyweight = (Flyweight) flyweights.get(key);
              if (flyweight != null) {
      //            System.out.println("具体享元" + key + "已经存在,被成功获取!");
              } else {
                  flyweight = new ConcreteFlyweight(key);
                  flyweights.put(key, flyweight);
              }
              return flyweight;
          }
      }
      

    完整代码

    package com.company.test.flyweight;
    
    import java.util.HashMap;
    
    interface Flyweight {
        void operation();
    }
    
    class ConcreteFlyweight implements Flyweight {
        private String key;
    
        ConcreteFlyweight(String key) {
            this.key = key;
            System.out.println("具体享元" + key + "被创建!");
        }
    
        public void operation() {
            System.out.println("具体享元" + key + "被调用,");
        }
    }
    
    class FlyweightFactory {
        private HashMap<String, Flyweight> flyweights = new HashMap<>();
    
        public Flyweight getFlyweight(String key) {
            Flyweight flyweight = (Flyweight) flyweights.get(key);
            if (flyweight != null) {
    //            System.out.println("具体享元" + key + "已经存在,被成功获取!");
            } else {
                flyweight = new ConcreteFlyweight(key);
                flyweights.put(key, flyweight);
            }
            return flyweight;
        }
    }
    
    public class FlyweightTest {
        public static void main(String[] args) {
            FlyweightFactory factory = new FlyweightFactory();
            System.out.println("------------------------------------------ 分别引用享元a,b ------------------------------------------");
            factory.getFlyweight("a").operation();
            factory.getFlyweight("b").operation();
    
            System.out.println("------------------------------------------ 分别引用享元a,b,c ------------------------------------------");
            factory.getFlyweight("a").operation();
            factory.getFlyweight("b").operation();
            factory.getFlyweight("c").operation();
        }
    }
    

    运行结果

    image-20201027150024246

    • 第一轮调用享元a,b时,均需要创建对象再引用
    • 第二轮调用享元a,b时不再创建,而是直接引用,但对于c依然需要创建后引用
    • 同理,之后如果再次调用享元a,b,c也不需要创建,而是直接引用即可

    4.模拟示例

    为了更加贴近我们实际使用的情况,模拟一个下载管理器

    临时手写的,肯定不够完整,仅写出其中主要逻辑,其中下载逻辑未补全

    package com.company.test.flyweight;
    
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * 任务接口
     */
    interface DownloadTask {
        void startDownload();
    
        void stopDownload() throws InterruptedException;
    }
    
    /**
     * 公共任务虚拟类
     */
    abstract class CommonTask implements DownloadTask {
        protected String fileName;
        protected int progress;
        protected boolean isAlive;
        protected Thread thread;
    
        @Override
        public void stopDownload() {
            //todo 仅做暂停任务,如果要删除任务,请先暂停,不然可能导致线程问题
            isAlive = false;
            System.out.println(new Date().toString() + ": " + fileName + " " + "downLoad stop!");
        }
    
        @Override
        public void startDownload() {
            //已下载完成,不再继续下载
            if (progress >= 100) {
                //todo 这里仅做提示,实际应用中可以添加其他逻辑
                System.out.println(new Date().toString() + ": " + fileName + " " + "has been downloaded!");
                return;
            }
            System.out.println(new Date().toString() + ": " + fileName + " " + "downLoad start!");
            //正在下载中,不需要重建线程
            if (!isAlive) {
                isAlive = true;
                thread = createThread();
                thread.start();
            }
        }
    
        protected Thread createThread() {
            return new Thread(() -> {
                while (isAlive && progress < 100) {
                    //todo 这里只做模拟进度,实际进度请自行扩展,不做赘述
                    progress += 20;
                    System.out.println(new Date().toString() + ": " + fileName + " " + progress + "%");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                if (progress >= 100) {
                    isAlive = false;
                    System.out.println(new Date().toString() + ": " + fileName + " " + "has bean downLoaded successfully!");
                }
            });
        }
    
    }
    
    class VideoTask extends CommonTask {
        //todo video类型的特有逻辑
    
        public VideoTask(String fileName) {
            this.fileName = fileName;
            this.progress = 0;
            this.isAlive = false;
        }
    }
    
    class PicTask extends CommonTask {
        //todo picture类型的特有逻辑
    
        public PicTask(String fileName) {
            this.fileName = fileName;
            this.progress = 0;
            this.isAlive = false;
        }
    }
    
    /**
     * 下载管理器
     */
    class DownloadManager {
        /**
         * 任务池
         */
        Map<String, DownloadTask> tasks = new HashMap<>();
    
        /**
         * 获取任务,或者创建任务
         */
        public DownloadTask getTask(String fileName) {
            DownloadTask task = tasks.get(fileName);
            if (task == null) {
                if (fileName.endsWith(".mp3")) {
                    task = new VideoTask(fileName);
                } else {
                    task = new PicTask(fileName);
                }
                tasks.put(fileName, task);
            }
            return task;
        }
    }
    
    public class DownloadTest {
        public static void main(String[] args) throws InterruptedException {
            DownloadManager downloadManager = new DownloadManager();
    
            System.out.println("------------------------------------------ 第一轮测试 ------------------------------------------");
            //获取任务,并开始下载
            DownloadTask task1 = downloadManager.getTask("你的酒馆对我打了烊.mp3");
            task1.startDownload();
            //三秒后暂停下载
            Thread.sleep(3000);
            task1.stopDownload();
    
            System.out.println("------------------------------------------ 第二轮测试 ------------------------------------------");
            //三秒再次获取任务并下载
            Thread.sleep(3000);
            DownloadTask task2 = downloadManager.getTask("你的酒馆对我打了烊.mp3");
            task2.startDownload();
            //获取新任务并下载
            DownloadTask task3 = downloadManager.getTask("土拨鼠尖叫.jpg");
            task3.startDownload();
    
            System.out.println("------------------------------------------ 第三轮测试 ------------------------------------------");
            //三秒后重新开始两项任务
            Thread.sleep(3000);
            DownloadTask task4 = downloadManager.getTask("你的酒馆对我打了烊.mp3");
            task4.startDownload();
            DownloadTask task5 = downloadManager.getTask("土拨鼠尖叫.jpg");
            task5.startDownload();
    
            //维持主线程
            while (true) {
            }
        }
    }
    

    运行结果

    image-20201027173439398

    • 第一轮测试中新建mp3下载,并在三秒后终止,结束时下载进度为60%
    • 第二轮再次启动mp3的下载,并新建jpg下载
    • 第三轮先等待三秒再启动下载
      • mp3已下载完成,提示已下载
      • jpg仍在下载中,则继续下载,直至下载完成

    仅供参考池化逻辑,实际应用中的下载管理器比这要复杂多了,但依然需要池化管理


    5.其他问题

    5.1.为什么需要池化管理

    节省系统资源,降低内存消耗,加快执行速度

    比如上面示例中,不使用池化管理,则每次获取任务都需要重建,造成不必要的消耗,且会丢失已下载进度


    作者:Echo_Ye

    WX:Echo_YeZ

    Email :echo_yezi@qq.com

    个人站点:在搭了在搭了。。。(右键 - 新建文件夹)

    5.2.如何共享

    在合适的地方初始化享元工厂即可,如果想在多个类中公用,也可以将其丢进bean池,或者单例模式轻松解决,Android也可以使用服务


    5.3.池化一定需要享元工厂吗?是否可以将池放在客户端里?

    当然可以,实际上很多地方也是这么做的,但是封装性不够好,且不便于与其他客户端共享

    通常如果客户端里定义享元工厂,会将类封装起来,不将池暴露出去,达到类似的效果


    别的想起来再写吧。。。


    后记

    实际开发中经常使用到池化管理,算是常用的实战技巧了,享元模式只是更加规范化的整理出来而已


    作者:Echo_Ye

    WX:Echo_YeZ

    Email :echo_yezi@qq.com

    个人站点:在搭了在搭了。。。(右键 - 新建文件夹)

  • 相关阅读:
    git
    *** errRun
    Centos与Debian的安装命令
    HTML基础
    基本的SQL语言
    phpstudy靶场搭建
    Centos7下搭建服务器(apache+mysql+php)
    Centos7设置yum源
    Linux基础
    一个服务器中搭建多个站点
  • 原文地址:https://www.cnblogs.com/silent-bug/p/13886445.html
Copyright © 2011-2022 走看看