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

Flyweight是抽象享元角色。它是产品的抽象类,同时定义出对象的外部状态和内部状态的接口或实现;ConcreteFlyweight是具体享元角色,是具体的产品类,实现抽象角色定义的业务;UnshareedConcreteFlyweight是不可共享的享元角色,一般不会出现在享元工厂中;FlyweightFactory是享元工厂,它用于构造一个池容器,同时提供从池中获得对象的方法。
三、代码实现
1、Flyweight
/*** 所有具体享元类的超类或接口,通过这个接口,Flyweight可以接受并作用于外部状态。* @author suibibk.com**/public abstract class Flyweight {//内部状态:比如数据库连接的ip,端口,用户,密码,这些基本上不会变化的。private String intrinsic;//外部状态,比如棋牌里的黑白棋子,这里是颜色。当然不一定有外部状态,比如数据库连接池,就直接初始化池的大小。public String extrinsic;public Flyweight(String extrinsic) {this.extrinsic = extrinsic;}public String getIntrinsic() {return intrinsic;}public void setIntrinsic(String intrinsic) {this.intrinsic = intrinsic;}/*** 根据外部状态来定义不同的业务操作* @param extrinsic*/public abstract void operation(String extrinsic);}
2、ConcreteFlyweight
/*** 具体子类,比如数据库连接,这里当然operation也不一定需要* @author suibibk.com*/public class ConcreteFlyweight extends Flyweight{public ConcreteFlyweight(String extrinsic) {super(extrinsic);}@Overridepublic void operation(String extrinsic) {System.out.println("具体Flyweight:" + extrinsic);}}
3、UnshareedConcreteFlyweight
/*** 不需要共享的Flyweight子类。* @author suibibk.com**/public class UnshareedConcreteFlyweight extends Flyweight{public UnshareedConcreteFlyweight(String extrinsic) {super(extrinsic);}@Overridepublic void operation(String extrinsic) {System.out.println("不需要共享的Flyweight子类:"+extrinsic);}}
4、FlyweightFactory
/*** 一个享元工厂,用来创建并管理Flyweight对象,主要是用来确保合理地共享Flyweight,当* 用户请求一个Flyweight时,FlyweightFactory对象提供一个已创建的实例或创建一个实例。* @author suibibk.com*/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.println("已有 " + extrinsic + " 直接从池中取---->");} else {//根据外部状态创建享元对象flyweight = new ConcreteFlyweight(extrinsic);//放入池中pool.put(extrinsic, flyweight);System.out.println("创建 " + extrinsic + " 并从池中取出---->");}return flyweight;}}
5、Test
public class Test {public static void main(String[] args) {FlyweightFactory.getFlyweight("A");FlyweightFactory.getFlyweight("B");FlyweightFactory.getFlyweight("C");FlyweightFactory.getFlyweight("A");FlyweightFactory.getFlyweight("B");}}
运行实例,输出如下内容:
创建 A 并从池中取出---->创建 B 并从池中取出---->创建 C 并从池中取出---->已有 A 直接从池中取---->已有 B 直接从池中取---->
我们可以知道,有些对象已经创建过了的不需要再重新创建。
四、具体例子
上面我们按UML图大概实现了一个重用,共享池的概念,接下来我们再实现一个数据库连接池的例子。当然不一定非得遵照UML图,我觉得设计模式只是一种思想,如果我们的代码实现了这种思想即可。下面举的例子没有过多的考虑线程安全以及是否使用,因为最终要的是让用户体验:重用,共享池的概念。
1、享元-数据库连接(Connection)
public class Connection {private String ip;private String port;private String username;private String passward;public String getIp() {return ip;}public void setIp(String ip) {this.ip = ip;}public String getPort() {return port;}public void setPort(String port) {this.port = port;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassward() {return passward;}public void setPassward(String passward) {this.passward = passward;}//获取数据库连接public Connection(String ip, String port, String username, String passward) {super();this.ip = ip;this.port = port;this.username = username;this.passward = passward;System.out.println("获取一个新的数据库连接");}}
上面的ip这些都是上面所说的内部对象。这里没有外部对象。
2、享元池-数据库连接池(ConnectionPool)
/*** 数据库连接池,当然这里没有考虑线程安全,只是为了给大家理解享元的概念。* @author suibibk.com*/public class ConnectionPool {private List<Connection> connections = new ArrayList<Connection>();private String ip="127.0.0.1";private String port="3306";private String username = "suibibk";private String password = "123456";private Connection connection = null;//创建连接池的时候初始化连接池的大小public ConnectionPool(int poolSize) {for (int i = 0; i <poolSize; i++) {connections.add(new Connection(ip, port, username, password));}}public Connection getConnection() {if(connections.size()>0) {connection = connections.get(0);connections.remove(connection);System.out.println("数据库中有连接,直接返回");return connection;}else {System.out.println("数据库连接池已用尽,初始化大小不足");return null;}}/*** 关闭连接,这里是直接返回到连接池*/public void close() {System.out.println("释放一个连接");connections.add(connection);}}
上面再初始化数据库连接池的时候就初始化了一定的大小,若是不够则报错,表明用户需要初始化大一点,当然数据库连接池一般都会有默认的大小,只不过我这里是简单的举个例子而已,望大家不要介意,主要是理解思想。
2、测试类(TestConnection)
public class TestConnection {public static void main(String[] args) {ConnectionPool pool = new ConnectionPool(3);pool.getConnection();pool.getConnection();pool.getConnection();pool.getConnection();pool.close();pool.getConnection();}}
运行实例,输出结果如下:
获取一个新的数据库连接获取一个新的数据库连接获取一个新的数据库连接数据库中有连接,直接返回数据库中有连接,直接返回数据库中有连接,直接返回数据库连接池已用尽,初始化大小不足释放一个连接数据库中有连接,直接返回
由此可见,我们也是实现了:重用,共享池的理念。
五、使用场景
系统有大量相似对象需要缓冲池的场景
总结
相信根据上面的两个例子,应该都能够理解啥叫做享元模式,我看网上说什么优缺点是一定要有什么外部对象,内部对象,但是按我的理解就是,只要符合重用和共享池的标准,都可以称之为享元模式,或许这种理解不正确,望大家指正。
