终于,慢慢慢慢到了结构型设计模式的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);
}
@Override
public 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);
}
@Override
public 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();
}
}
运行实例,输出结果如下:
获取一个新的数据库连接
获取一个新的数据库连接
获取一个新的数据库连接
数据库中有连接,直接返回
数据库中有连接,直接返回
数据库中有连接,直接返回
数据库连接池已用尽,初始化大小不足
释放一个连接
数据库中有连接,直接返回
由此可见,我们也是实现了:重用,共享池的理念。
五、使用场景
系统有大量相似对象
需要缓冲池的场景
总结
相信根据上面的两个例子,应该都能够理解啥叫做享元模式,我看网上说什么优缺点是一定要有什么外部对象,内部对象,但是按我的理解就是,只要符合重用和共享池的标准,都可以称之为享元模式,或许这种理解不正确,望大家指正。