【设计模式——学习笔记】23种设计模式——享元模式Flyweight(原理讲解+应用场景介绍+案例介绍+Java代码实现)

news2024/11/15 23:34:31

文章目录

  • 案例引入
  • 介绍
    • 基本介绍
    • 原理类图
      • 外部状态和内部状态
    • 登场角色
  • 案例实现
    • 案例1
      • 类图
      • 代码实现
    • 案例2
      • 类图
      • 代码实现
  • 享元模式在JDK源码中的使用
  • 总结

案例引入

你的公司主要做一些小型的外包项目,之前给客户A做一个产品展示网站,客户A的朋友感觉效果不错,也希望做这样的产品展示网站,但是要求有些不同

  • 有客户要求以新闻的形式发布
  • 有客户要求以博客的形式发布
  • 有客户要求以微信公众号的形式发布

【 传统方式】

直接复制粘贴一份代码,根据客户的不同要求进行定制修改

分析:

  • 需要的网站结构相似度很高,而且都不是高访问量网站,如果分成多个虚拟空间来处理,相当于一个相同网站的实例对象很多,造成服务器的资源浪费

解决思路:

  • 使用享元模式,整合到一个网站中,共享部分代码和数据,对于硬盘、内存、CPU、数据库空间等服务器资源都可以达成共享,减少服务器资源
  • 对于代码来说,由于是一份实例,维护和扩展都更加容易

介绍

基本介绍

  • 享元模式,也叫蝇量模式: 通过尽量共享实例来避免new出新的实例,让程序占用的内存更小
  • 常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象中如果有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则创建一个
  • 享元模式能够解决重复对象的内存浪费的问题,当系统中有大量相似对象,需要缓冲池时。不需总是创建新对象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率(如先创建好棋盘对象,用户开始下棋时,直接从缓冲池中获取这个棋盘对象返回即可)
  • 享元模式经典的应用场景就是池化技术了,String常量池、数据库连接池、缓冲池等等都是享元模式的应用,享元模式是池化技术的重要实现方式

在这里插入图片描述

原理类图

在这里插入图片描述

  • FlyWeight 是抽象的享元角色,他是产品的抽象类,同时定义出对象的外部状态(频繁变化的,每个对象都不一样,如下棋时棋子的位置)和内部状态(稳定的,如棋子的颜色)的接口或实现
  • ConcreteFlyWeight 是具体的享元角色,实现抽象角色定义相关业务,不同的客户可能对应不同的ConcreteFlyWeight
  • UnSharedConcreteFlyWeight 是不可共享的角色,一般不会出现在享元工厂,getFlyWeight方法获取不到
  • FlyWeightFactory 是享元工厂类,用于构建一个池容器(集合),同时提供从池中获取对象的方法
  • Client:使用享元工厂来获取实例

外部状态和内部状态

比如围棋、五子棋、跳棋,它们都有大量的棋子对象,围棋和五子棋只有黑白两色,跳棋颜色多点,所以棋子颜色就是棋子的内部状态,而各个棋子之间的差别就是位置的不同,当我们落子后落子颜色是定的,但位置是变化的,所以棋子坐标就是棋子的外部状态

  • 享元模式提出了两个要求: 细粒度和共享对象。这里就涉及到内部状态和外部状态,即将对象的信息分为两个部分: 内部状态和外部状态(使用享元模式,必须将对象的内部状态和外部状态分析出来)
  • 内部状态指对象共享出来的信息,不会随环境的改变而改变(可以共享,建议存储在享元角色内部)
  • 外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态(外部状态相关属性建议封装享元角色外的其他类中)

围棋理论上有361个空位可以放棋子,每盘棋都有可能有两三百个棋子对象产生,因为内存空间有限,一台服务器很难支持更多的玩家玩围棋游戏,如果用享元模式来处理棋子,那么棋子对象就可以减少到只有两个实例(一个黑子、一个白子,只是位置不同),这样就很好的解决了对象的开销问题

登场角色

在这里插入图片描述

  • Flyweight(轻量级):表示的就是那些实例会被共享的类
  • FlyweightFactory(轻量级工厂):FlyweightFactory角色是生成Flyweight角色的工厂
  • Client(请求者):Client角色使用FlyweightFactory 角色来生成Flyweight角色

案例实现

案例1

类图

每个客户的网站不同,所以将Website做成抽象类

在这里插入图片描述

  • 网站的用户不同,User是享元模式的外部状态
  • 内部状态全部写到ConcreteWebSite中,外部状态使用独立的类来封装

代码实现

【抽象网站】

package com.atguigu.flyweight;

public abstract class WebSite {

   /**
    * 抽象方法
    * @param user
    */
   public abstract void use(User user);
}

【具体网站】

package com.atguigu.flyweight;

/**
 * 具体网站
 */
public class ConcreteWebSite extends WebSite {

   /**
    * 网站发布的形式(类型)
    * 共享的部分,内部状态
    */
    private String type = "";

   /**
    * 构造器
    * @param type
    */
   public ConcreteWebSite(String type) {
        this.type = type;
    }


    @Override
    public void use(User user) {
        System.out.println("网站的发布形式为:" + type + " 在使用中 .. 使用者是" + user.getName());
    }


}

【享元工厂】

package com.atguigu.flyweight;

import java.util.HashMap;

/**
 * 网站工厂类,根据需要返回一个网站
 */
public class WebSiteFactory {

   /**
    * 集合, 充当池的作用
    */
   private HashMap<String, ConcreteWebSite> pool = new HashMap<>();

   /**
    * 根据网站的类型,返回一个网站, 如果没有就创建一个网站,并放入到池中,并返回
    * @param type
    * @return
    */
   public WebSite getWebSiteCategory(String type) {
      if(!pool.containsKey(type)) {
         //就创建一个网站,并放入到池中
         pool.put(type, new ConcreteWebSite(type));
      }
      return (WebSite)pool.get(type);
   }

   /**
    * 获取网站分类的总数 (池中有多少个网站类型)
    * @return
    */
   public int getWebSiteCount() {
      return pool.size();
   }
}

【客户】

package com.atguigu.flyweight;

public class User {

   private String name;


   public User(String name) {
      super();
      this.name = name;
   }

   public String getName() {
      return name;
   }

   public void setName(String name) {
      this.name = name;
   }


}

【主类】

package com.atguigu.flyweight;

public class Client {

   public static void main(String[] args) {

      // 创建一个工厂类
      WebSiteFactory factory = new WebSiteFactory();

      // 客户要一个以新闻形式发布的网站
      WebSite webSite1 = factory.getWebSiteCategory("新闻");
      webSite1.use(new User("tom"));

      // 客户要一个以博客形式发布的网站
      WebSite webSite2 = factory.getWebSiteCategory("博客");
      webSite2.use(new User("jack"));

      // 客户要一个以博客形式发布的网站
      WebSite webSite3 = factory.getWebSiteCategory("博客");
      webSite3.use(new User("smith"));

      // 客户要一个以博客形式发布的网站
      WebSite webSite4 = factory.getWebSiteCategory("博客");
      webSite4.use(new User("king"));

      System.out.println("网站的分类共=" + factory.getWebSiteCount());
   }

}

【运行】

网站的发布形式为:新闻 在使用中 .. 使用者是tom
网站的发布形式为:博客 在使用中 .. 使用者是jack
网站的发布形式为:博客 在使用中 .. 使用者是smith
网站的发布形式为:博客 在使用中 .. 使用者是king
网站的分类共=2

Process finished with exit code 0

案例2

类图

在这里插入图片描述

代码实现

【大型字符类】

package com.atguigu.flyweight.Sample;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

/**
 * 大型字符
 * 从文件中读取大型字符的字体数据,并保存到内存中
 * 大型字符会消耗较多内存,需要考虑尽可能高香BigChar类的实例
 */
public class BigChar {
    /**
     * 字符名字
     */
    private char charname;
    /**
     * 保存大型字符对应的字符串(由'#' '.' '\n'组成)
     */
    private String fontdata;

    /**
     * 构造函数
     * 传入数字,获取对应的大型字符,存储到fontdata中
     * @param charname
     */
    public BigChar(char charname) {
        this.charname = charname;
        try {
            BufferedReader reader = new BufferedReader(
                    new FileReader("big" + charname + ".txt")
            );
            String line;
            StringBuffer buf = new StringBuffer();
            while ((line = reader.readLine()) != null) {
                buf.append(line);
                buf.append("\n");
            }
            reader.close();
            this.fontdata = buf.toString();
        } catch (IOException e) {
            this.fontdata = charname + "?";
        }
    }

    /**
     * 显示大型字符
     */
    public void print() {
        System.out.print(fontdata);
    }
}

代码中读取的txt文件,分别是一些符号形成的数字,如big1.txt,内容如下:

......##........
..######........
......##........
......##........
......##........
......##........
..##########....
................

【享元工厂】

package com.atguigu.flyweight.Sample;

import java.util.HashMap;

/**
 * 共享 BigChar 实例
 */
public class BigCharFactory {
    /**
     * 管理已经生成的BigChar的实例
     */
    private HashMap pool = new HashMap();
    /**
     * Singleton模式
     */
    private static BigCharFactory singleton = new BigCharFactory();

    /**
     * 构造函数
     */
    private BigCharFactory() {
    }

    /**
     * 获取唯一的实例
     * @return
     */
    public static BigCharFactory getInstance() {
        return singleton;
    }

    /**
     * 生成(共享)BigChar类的实例
     * @param charName
     * @return
     */
    public synchronized BigChar getBigChar(char charName) {
        BigChar bc = (BigChar) pool.get("" + charName);
        if (bc == null) {
            // 生成BigChar的实例
            bc = new BigChar(charName);
            pool.put("" + charName, bc);
        }
        return bc;
    }
}

使用synchronized来修饰方法有以下作用:

  1. 确保同一时间只有一个线程可以访问被修饰的方法。当一个线程获得了对象的锁后,其他线程必须等待该线程释放锁才能访问该方法
  2. 解决多线程并发访问共享资源产生的数据不一致问题。当多个线程同时访问一个共享资源时,可能会出现数据竞争的问题,导致数据的不一致。使用synchronized修饰方法可以确保同一时间只有一个线程访问共享资源,从而避免数据不一致的问题
  3. 需要注意的是,synchronized修饰方法时锁定的是调用该方法的对象。如果有多个对象调用同一个被synchronized修饰的方法,那么每个对象都会有一个独立的锁。因为这里使用的是单例模式,所以只有一个对象

【工厂调用类】

传入一个字符串,根据每个字符,获取其BigChar,存储到bigchars中。并提供一个print方法来显示bigchars的元素

package com.atguigu.flyweight.Sample;

public class BigString {
    /**
     * “大型字符”的数组
     */
    private BigChar[] bigchars;

    /**
     * 构造函数
     *
     * @param string
     */
    public BigString(String string) {
        bigchars = new BigChar[string.length()];
        BigCharFactory factory = BigCharFactory.getInstance();
        for (int i = 0; i < bigchars.length; i++) {
            bigchars[i] = factory.getBigChar(string.charAt(i));
        }
    }

    /**
     * 显示
     */
    public void print() {
        for (int i = 0; i < bigchars.length; i++) {
            bigchars[i].print();
        }
    }
}

【主类】

package com.atguigu.flyweight.Sample;

public class Main {
    public static void main(String[] args) {
        String input = "1212123";
        BigString bs = new BigString(input);
        bs.print();
    }
}

【运行】

......##........
..######........
......##........
......##........
......##........
......##........
..##########....
................
....######......
..##......##....
..........##....
......####......
....##..........
..##............
..##########....
................
......##........
..######........
......##........
......##........
......##........
......##........
..##########....
................
....######......
..##......##....
..........##....
......####......
....##..........
..##............
..##########....
................
......##........
..######........
......##........
......##........
......##........
......##........
..##########....
................
....######......
..##......##....
..........##....
......####......
....##..........
..##............
..##########....
................
....######......
..##......##....
..........##....
......####......
..........##....
..##......##....
....######......
................

Process finished with exit code 0

在这里插入图片描述

享元模式在JDK源码中的使用

在这里插入图片描述

在这里插入图片描述

package com.atguigu.jdk;

public class FlyWeight {

   public static void main(String[] args) {
      //如果 Integer.valueOf(x) x 在  -128 --- 127 直接,就是使用享元模式返回,如果不在范围类,则仍然 new

      //小结:
      //1. 在valueOf 方法中,先判断值是否在 IntegerCache 中,如果不在,就创建新的Integer(new), 否则,就直接从 缓存池 返回
      //2. valueOf 方法,就使用到享元模式
      //3. 如果使用valueOf 方法得到一个Integer 实例,范围在 -128 - 127 ,执行速度比 new 快
      Integer x = Integer.valueOf(127); // 得到 x实例,类型 Integer
      Integer y = new Integer(127); // 得到 y 实例,类型 Integer
      Integer z = Integer.valueOf(127);//..
      Integer w = new Integer(127);

      System.out.println(x.equals(y)); // 大小,true
      System.out.println(x == y ); // false
      System.out.println(x == z ); // true
      System.out.println(w == x ); // false
      System.out.println(w == y ); // false
      
      Integer x1 = Integer.valueOf(200);
      Integer x2 = Integer.valueOf(200);
      System.out.println("x1==x2" + (x1 == x2)); // 数据不在范围内,返回false

   }

}

小技巧 如果使用valueOf 方法得到一个Integer 实例,范围在 -128 - 127 ,执行速度比 new 快

总结

  • 享元模式可以这样理解,“享”就表示共享,“元”表示对象
  • 系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以共用时我们就可以考虑选用享元模式
  • 用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用HashMap/HashTable存储
  • 享元模式大大减少了对象的创建,降低了程序内存的占用,同时可以减少new 实例的时间的花费,提高效率
  • 享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是我们使用享元模式需要注意的地方
  • 使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制

【注意】

  • 由于多处使用了共享对象,如果修改共享对象,多处会受到影响
  • 被池管理的实例不会被垃圾回收,如果不用的实例,可以从池中移除

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/811497.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

uniapp小程序自定义loding,通过状态管理配置全局使用

一、在项目中创建loding组件 在uniapp的components文件夹下创建loding组件&#xff0c;如图&#xff1a; 示例代码&#xff1a; <template><view class"loginLoading"><image src"../../static/loading.gif" class"loading-img&q…

SpringBoot统一异常处理和统一返回格式

上篇博客我们讲解了使用AOP来进行统一的用户登录判断&#xff0c;其实像这种功能统一且使用较多的地方&#xff0c;都可以用AOP来处理&#xff0c;除了统⼀的⽤户登录判断之外&#xff0c;AOP 还可以实现&#xff1a; 统⼀⽇志记录统⼀⽅法执⾏时间统计&#xff08;在性能优化…

8.事件对象

8.1获取事件对象 ●事件对象是什么 也是个对象&#xff0c;这个对象里有事件触发时的相关信息 例如&#xff1a;鼠标点击事件中&#xff0c;事件对象就存了鼠标点在哪个位置等信息 ●使用场景 可以判断用户按下哪个键&#xff0c;比如按下回车键可以发布新闻 可以判断鼠标点击…

【动态规划】子数组系列

文章目录 动态规划&#xff08;子数组系列&#xff09;1. 最大子数组和2. 环形子数组的最大和3. 乘积最大子数组4. 乘积为正的最长子数组的长度5. 等差数列划分6. 最长湍流子数组7. 单词拆分8. 环形字符串中的唯一的子字符串 动态规划&#xff08;子数组系列&#xff09; 1. 最…

ETHERCAT转CCLINK网关连接ethercat转换器

你们有没有遇到这样的问题&#xff1f;在生产管理系统中&#xff0c;数据互联互通是非常重要的&#xff0c;但ETHERCAT和CCLINK这两个协议之间的通讯一直是个大问题。今天&#xff0c;我给大家带来了一个好消息——捷米JM-ECT-CCLK&#xff0c;这是一款让各种CCLINK总线和ETHER…

WEB:php_rce

背景知识 Linux命令 thinkPHPv5漏洞 题目 打开页面&#xff0c;页面显示为thinkphp v5的界面&#xff0c;可以判断框架为thinkPHP&#xff0c;可以去网上查找相关的漏洞 由题目可知&#xff0c;php rec是一个通过远程代码执行漏洞来攻击php程序的一种方式 因为不知道是php版…

三数之和——力扣15

文章目录 题目描述法一 双指针排序 题目描述 法一 双指针排序 class Solution{ public:vector<vector<int>> threeSum(vector<int>& nums){int nnums.size();vector<vector<int>> ans;sort(nums.begin(), nums.end());for(int first0;first&…

项目实战 — 消息队列(2){创建核心类}

目录 一、创建项目 二、创建核心类 &#x1f345; 1、 编写交换机类&#xff0c;Exchange &#x1f345; 2、编写存储消息的队列&#xff0c;MSGQueue &#x1f345; 3、编写绑定类&#xff0c;binding &#x1f345; 4、编写消息&#xff0c;Message 一、创建项目 二、创…

【Golang 接口自动化04】 解析接口返回JSON串

目录 前言 解析到结构体 json数据与struct字段是如何相匹配的呢&#xff1f; 解析到interface Go类型和JSON类型 实例代码 simpleJson 总结 资料获取方法 前言 上一次我们一起学习了如何解析接口返回的XML数据&#xff0c;这一次我们一起来学习JSON的解析方法。 JSO…

Mysql 索引失效

1、模糊查询%在前面&#xff0c;无法排序所以失效 2、函数计算 3、表达式计算length(NAME) 4、隐式转换 5、联合索引非最左匹配 6、or 必须都为索引列 事务特性 &#xff08;来自小林coding 事务隔离级别是怎么实现的&#xff1f; | 小林coding (xiaolincoding.com)&#…

基于Open3D的点云处理13-分割

平面分割&#xff08;基于RANSAC&#xff09; 使用RANSAC算法从点云中拟合平面&#xff1b; 接口&#xff1a;segment_plane 测试&#xff1a;Plane-segmentation import open3d as o3dpcd_point_cloud o3d.data.PCDPointCloud() pcd o3d.io.read_point_cloud(pcd_point_cl…

从零开始学Docker(二):启动第一个Docker容器

宿主机环境&#xff1a;RockyLinux 9 这个章节不小心搞成命令学习了&#xff0c;后面在整理成原理吧 Docker生命周期 拉取并启动Nginx容器 # 查找镜像 例如&#xff1a;nginx [root192 ~]# docker search nginx 我们可以看到&#xff0c;第一个时官方认证构建的nginx # 拉…

beego通过gorm访问mysql数据库

一、下载golang 二、解压下载包到C盘 三、配置golang系统环境变量 四、进入新建的工作目录C:\project下载并安装beego 五、将新生成的bee.exe所在的路径c:\project\bin加入到系统变量path里面 六、下载并安装mysql 例如在上图中&#xff0c; 选“No thanks,just start my down…

iOS开发-实现上下翻转轮播循环信息播报效果

iOS开发-实现上下翻转轮播循环信息播报效果 在开发中经常遇到需要实现轮播信息播报&#xff0c;例如以下效果 一、使用UITableView实现展示列表 UITableView 定义UITableView的实例&#xff0c;并让当前视图遵守UITableView的两个协议 property (nonatomic, weak, nullable)…

【Linux多线程】线程的互斥与同步(附抢票案例代码+讲解)

线程的互斥与同步 &#x1f4ab; 概念引入⭐️临界资源&#xff08;Critical Resource&#xff09;&#xff1a;&#x1f31f;临界区&#xff08;Critical Section&#xff09;&#xff1a;✨互斥&#xff08;Mutex&#xff09;&#xff1a; ⚡️结合代码看互斥☄️ 代码逻辑&a…

无涯教程-jQuery - Progressbar组件函数

小部件进度条功能可与JqueryUI中的小部件一起使用。一个简单的进度条显示有关进度的信息。一个简单的进度条如下所示。 Progressbar - 语法 $( "#progressbar" ).progressbar({value: 37 }); Progressbar - 示例 以下是显示进度条用法的简单示例- <!doctype …

appium的基本使用

appium的基本使用 一、appium的基本使用appium环境安装1、安装Android SDK 2、安装Appium3、安装手机模拟器4、Pycharm安装 appium-python-alicent5、连接appium和模拟器6、Python代码调用appium软件&#xff0c;appium软件在通过adb命令调用android操作系统&#xff08;模拟器…

如何在局域网外SSH远程访问连接到家里的树莓派?

文章目录 如何在局域网外SSH远程访问连接到家里的树莓派&#xff1f;如何通过 SSH 连接到树莓派步骤1. 在 Raspberry Pi 上启用 SSH步骤2. 查找树莓派的 IP 地址步骤3. SSH 到你的树莓派步骤 4. 在任何地点访问家中的树莓派4.1 安装 Cpolar4.2 cpolar进行token认证4.3 配置cpol…

详解Mybatis之分页插件【PageHelper】

编译软件&#xff1a;IntelliJ IDEA 2019.2.4 x64 操作系统&#xff1a;win10 x64 位 家庭版 Maven版本&#xff1a;apache-maven-3.6.3 Mybatis版本&#xff1a;3.5.6 文章目录 一. 什么是分页&#xff1f;二. 为什么使用分页&#xff1f;三. 如何设计一个Page类&#xff08;分…

【Python】Web学习笔记_flask(1)——getpost

flask提供的request请求对象可以实现获取url或表单中的字段值 GET请求 从URL中获取name、age两个参数 from flask import Flask,url_for,redirect,requestappFlask(__name__)app.route(/) def index():namerequest.args.get(name)agerequest.args.get(age)messagef姓名:{nam…