个人随笔
目录
十二、结构型-享元(Flyweight)模式
2020-09-09 21:39:24

终于,慢慢慢慢到了结构型设计模式的7种的最后一种了,简单的回顾下前面所学的六种,一开始是学了适配器模式,包括类的适配器,对象的适配器,接口的适配器,接着学习了很有用的装饰者模式,像Java中的IO就是用装饰者模式,然后学了代理模式,这个模式和装饰者模式的区别大家需要分清楚,后面学了外观模式,然后比较难理解的桥接模式,组合模式。下面学习7种之中的最后一种:享元模式。

一、什么是享元模式

享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。
主要我们要抓住两个字:重用。举个例子,假如数据库连接,每个连接都去新建一个对象,到后面可能会有超级多的连接,会导致性能下降的厉害。假如我们重用连接,也就是用一个连接池,每次去获取连接都从连接池获取,用完后再放回连接池。这样子就极大的提高了性能以及降低了连接操作的频次,不需要每次都新建连接。再比如我们围棋,要是每个棋子都创建一个对象,那么如果做一个围棋游戏,有成百上千万个棋子,想来是不科学,因此我们用享元模式的话就只需要建立两个对象,一黑一白,然后每个棋子的区别只是位置不同而已,这里涉及到了,外部状态及内部状态的概念,内部状态就是黑和白,不会变的。数据库连接的ip,端口,用户密码也会是内部状态不会变的。棋子的位置就是外部状态。
享元模式的主要目的是实现对象的共享,即共享池,当系统中对象多的时候可以减少内存的开销,通常与工厂模式一起使用。

二、UML图

  1. Flyweight是抽象享元角色。它是产品的抽象类,同时定义出对象的外部状态和内部状态的接口或实现;
  2. ConcreteFlyweight是具体享元角色,是具体的产品类,实现抽象角色定义的业务;
  3. UnshareedConcreteFlyweight是不可共享的享元角色,一般不会出现在享元工厂中;
  4. FlyweightFactory是享元工厂,它用于构造一个池容器,同时提供从池中获得对象的方法。

三、代码实现

1、Flyweight
  1. /**
  2. * 所有具体享元类的超类或接口,通过这个接口,Flyweight可以接受并作用于外部状态。
  3. * @author suibibk.com
  4. *
  5. */
  6. public abstract class Flyweight {
  7. //内部状态:比如数据库连接的ip,端口,用户,密码,这些基本上不会变化的。
  8. private String intrinsic;
  9. //外部状态,比如棋牌里的黑白棋子,这里是颜色。当然不一定有外部状态,比如数据库连接池,就直接初始化池的大小。
  10. public String extrinsic;
  11. public Flyweight(String extrinsic) {
  12. this.extrinsic = extrinsic;
  13. }
  14. public String getIntrinsic() {
  15. return intrinsic;
  16. }
  17. public void setIntrinsic(String intrinsic) {
  18. this.intrinsic = intrinsic;
  19. }
  20. /**
  21. * 根据外部状态来定义不同的业务操作
  22. * @param extrinsic
  23. */
  24. public abstract void operation(String extrinsic);
  25. }
2、ConcreteFlyweight
  1. /**
  2. * 具体子类,比如数据库连接,这里当然operation也不一定需要
  3. * @author suibibk.com
  4. */
  5. public class ConcreteFlyweight extends Flyweight{
  6. public ConcreteFlyweight(String extrinsic) {
  7. super(extrinsic);
  8. }
  9. @Override
  10. public void operation(String extrinsic) {
  11. System.out.println("具体Flyweight:" + extrinsic);
  12. }
  13. }
3、UnshareedConcreteFlyweight
  1. /**
  2. * 不需要共享的Flyweight子类。
  3. * @author suibibk.com
  4. *
  5. */
  6. public class UnshareedConcreteFlyweight extends Flyweight{
  7. public UnshareedConcreteFlyweight(String extrinsic) {
  8. super(extrinsic);
  9. }
  10. @Override
  11. public void operation(String extrinsic) {
  12. System.out.println("不需要共享的Flyweight子类:"+extrinsic);
  13. }
  14. }
4、FlyweightFactory
  1. /**
  2. * 一个享元工厂,用来创建并管理Flyweight对象,主要是用来确保合理地共享Flyweight,当
  3. * 用户请求一个Flyweight时,FlyweightFactory对象提供一个已创建的实例或创建一个实例。
  4. * @author suibibk.com
  5. */
  6. public class FlyweightFactory {
  7. //定义一个池容器
  8. //定义一个池容器
  9. private static HashMap<String, Flyweight> pool = new HashMap<>();
  10. //享元工厂
  11. public static Flyweight getFlyweight(String extrinsic) {
  12. Flyweight flyweight = null;
  13. if(pool.containsKey(extrinsic)) { //池中有该对象
  14. flyweight = pool.get(extrinsic);
  15. System.out.println("已有 " + extrinsic + " 直接从池中取---->");
  16. } else {
  17. //根据外部状态创建享元对象
  18. flyweight = new ConcreteFlyweight(extrinsic);
  19. //放入池中
  20. pool.put(extrinsic, flyweight);
  21. System.out.println("创建 " + extrinsic + " 并从池中取出---->");
  22. }
  23. return flyweight;
  24. }
  25. }
5、Test
  1. public class Test {
  2. public static void main(String[] args) {
  3. FlyweightFactory.getFlyweight("A");
  4. FlyweightFactory.getFlyweight("B");
  5. FlyweightFactory.getFlyweight("C");
  6. FlyweightFactory.getFlyweight("A");
  7. FlyweightFactory.getFlyweight("B");
  8. }
  9. }

运行实例,输出如下内容:

  1. 创建 A 并从池中取出---->
  2. 创建 B 并从池中取出---->
  3. 创建 C 并从池中取出---->
  4. 已有 A 直接从池中取---->
  5. 已有 B 直接从池中取---->

我们可以知道,有些对象已经创建过了的不需要再重新创建。

四、具体例子

上面我们按UML图大概实现了一个重用,共享池的概念,接下来我们再实现一个数据库连接池的例子。当然不一定非得遵照UML图,我觉得设计模式只是一种思想,如果我们的代码实现了这种思想即可。下面举的例子没有过多的考虑线程安全以及是否使用,因为最终要的是让用户体验:重用,共享池的概念。

1、享元-数据库连接(Connection)
  1. public class Connection {
  2. private String ip;
  3. private String port;
  4. private String username;
  5. private String passward;
  6. public String getIp() {
  7. return ip;
  8. }
  9. public void setIp(String ip) {
  10. this.ip = ip;
  11. }
  12. public String getPort() {
  13. return port;
  14. }
  15. public void setPort(String port) {
  16. this.port = port;
  17. }
  18. public String getUsername() {
  19. return username;
  20. }
  21. public void setUsername(String username) {
  22. this.username = username;
  23. }
  24. public String getPassward() {
  25. return passward;
  26. }
  27. public void setPassward(String passward) {
  28. this.passward = passward;
  29. }
  30. //获取数据库连接
  31. public Connection(String ip, String port, String username, String passward) {
  32. super();
  33. this.ip = ip;
  34. this.port = port;
  35. this.username = username;
  36. this.passward = passward;
  37. System.out.println("获取一个新的数据库连接");
  38. }
  39. }

上面的ip这些都是上面所说的内部对象。这里没有外部对象。

2、享元池-数据库连接池(ConnectionPool)
  1. /**
  2. * 数据库连接池,当然这里没有考虑线程安全,只是为了给大家理解享元的概念。
  3. * @author suibibk.com
  4. */
  5. public class ConnectionPool {
  6. private List<Connection> connections = new ArrayList<Connection>();
  7. private String ip="127.0.0.1";
  8. private String port="3306";
  9. private String username = "suibibk";
  10. private String password = "123456";
  11. private Connection connection = null;
  12. //创建连接池的时候初始化连接池的大小
  13. public ConnectionPool(int poolSize) {
  14. for (int i = 0; i <poolSize; i++) {
  15. connections.add(new Connection(ip, port, username, password));
  16. }
  17. }
  18. public Connection getConnection() {
  19. if(connections.size()>0) {
  20. connection = connections.get(0);
  21. connections.remove(connection);
  22. System.out.println("数据库中有连接,直接返回");
  23. return connection;
  24. }else {
  25. System.out.println("数据库连接池已用尽,初始化大小不足");
  26. return null;
  27. }
  28. }
  29. /**
  30. * 关闭连接,这里是直接返回到连接池
  31. */
  32. public void close() {
  33. System.out.println("释放一个连接");
  34. connections.add(connection);
  35. }
  36. }

上面再初始化数据库连接池的时候就初始化了一定的大小,若是不够则报错,表明用户需要初始化大一点,当然数据库连接池一般都会有默认的大小,只不过我这里是简单的举个例子而已,望大家不要介意,主要是理解思想。

2、测试类(TestConnection)
  1. public class TestConnection {
  2. public static void main(String[] args) {
  3. ConnectionPool pool = new ConnectionPool(3);
  4. pool.getConnection();
  5. pool.getConnection();
  6. pool.getConnection();
  7. pool.getConnection();
  8. pool.close();
  9. pool.getConnection();
  10. }
  11. }

运行实例,输出结果如下:

  1. 获取一个新的数据库连接
  2. 获取一个新的数据库连接
  3. 获取一个新的数据库连接
  4. 数据库中有连接,直接返回
  5. 数据库中有连接,直接返回
  6. 数据库中有连接,直接返回
  7. 数据库连接池已用尽,初始化大小不足
  8. 释放一个连接
  9. 数据库中有连接,直接返回

由此可见,我们也是实现了:重用,共享池的理念。

五、使用场景

  1. 系统有大量相似对象
  2. 需要缓冲池的场景

总结

相信根据上面的两个例子,应该都能够理解啥叫做享元模式,我看网上说什么优缺点是一定要有什么外部对象,内部对象,但是按我的理解就是,只要符合重用和共享池的标准,都可以称之为享元模式,或许这种理解不正确,望大家指正。

 266

啊!这个可能是世界上最丑的留言输入框功能~


当然,也是最丑的留言列表

有疑问发邮件到 : suibibk@qq.com 侵权立删
Copyright : 个人随笔   备案号 : 粤ICP备18099399号-2