一、概述
享元模式:“享”就是分享之意,指一物被众人共享,而这也正是该模式的终旨所在。
享元模式有点类似于单例模式,都是只生成一个对象来被共享使用。
享元的目的是为了减少不会要额内存消耗,将多个对同一对象的访问集中起来,不必为每个访问者创建一个单独的对象,以此来降低内存的消耗。
这里有个问题,那就是对共享对象的修改,为了避免出现这种情况,我们将这些对象的公共部分,或者说是不变化的部分抽取出来形成一个对象。这个对象就可以避免到修改的问题。
二、享元模式结构图
因为享元模式结构比较复杂,一般结合工厂模式一起使用,在它的结构图中包含了一个享元工厂类。
在享元模式结构图中包含如下几个角色:
1、Flyweight(抽象享元类):定义需要共享的对象业务接口。享元类被创建出来总是为了实现某些特定的业务逻辑.
2、ConcreteFlyweight(具体享元类):实现抽象享元类的接口,完成某一具体逻辑。在这里表示可以被借出。
3、FlyweightFactory(享元工厂类):于创建具体享元类,维护相同的享元对象。当请求对象已经存在时,直接返回对象,不存在时,在创建对象。
在例子中的解释就是图书馆,保存了所有的书,当学生借书时,有就拿走,没有买一本新书。这里面其实是使用了单例模式的。
4、UnsharedConcreteFlyweight(非共享具体享元类):并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
三、享元模式的实现
在享元模式中引入了享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池,当用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。
接下来,实现一个图书馆借书的享元模式。
1、模型图
2、代码实现
第一步:学生用户类
/**
* 学生
*/
public class Student {
private String name;
public Student(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
第二步:定义抽象享元类(Book)
/**
* 借书 -- 抽象享元类
*/
public interface Book {
/**
* 借书--享元类公共方法
* @param student
*/
void borrow(Student student);
}
第三步:定义具体享元类(ConcreteBook)
/**
* 具体享元类
*/
public class ConcreteBook implements Book {
//书名
private String bookName;
public ConcreteBook(String bookName){
this.bookName = bookName;
}
@Override
public void borrow(Student student) {
System.out.println(student.getName()+"---- 他借走了这本书是 :"+this.bookName);
}
}
第四部步:享元工厂(Llibrary)
/**
* 享元工厂类
*/
public class Library {
// map充当对象享元池 图书馆维护一个图书列表
private static Map<String,Book> bookMap = new HashMap<>();
//图书馆外借书
public static Book getBook(String bookName){
// 从享元池中拿 书本
Book concreteBook = bookMap.get(bookName);
// 如果享元池中没有此对象 书
if(concreteBook == null ){
// 创建对象 书
concreteBook = new ConcreteBook(bookName);
// 存到享元池中
bookMap.put(bookName,concreteBook);
}
// 返回对象 书
return concreteBook;
}
public static int getSize(){
return bookMap.size();
}
}
第五步:模拟学生去借书
/**
* 设计模式 结构型模式 之 享元模式
* 测试类
*/
public class MainTest {
public static void main(String[] args) {
Book book1 = Library.getBook("JAVA基础");
book1.borrow(new Student("小明"));
Book book3 = Library.getBook("JAVA高并发");
book3.borrow(new Student("小红"));
System.out.println("-----书籍还回图书馆后");
Book book2 = Library.getBook("JAVA基础");
book2.borrow(new Student("小黑"));
// 测试是否是同一个对象
System.out.println("小明和小黑借的书是否为同一本书:" + (book1 == book2));
// 工厂类中享元池中对象数量
System.out.println("图书馆时间借出了 :" + Library.getSize()+ " 本书");
}
}
运行结果:
小明---- 他借走了这本书是 :JAVA基础
小红---- 他借走了这本书是 :JAVA高并发
-----书籍还回图书馆后
小黑---- 他借走了这本书是 :JAVA基础
小明和小黑借的书是否为同一本书:true
图书馆时间借出了 :2 本书
四、总结
从上面代码和运行结果这可以看到,不同学生借书事是 “享” 用同一本书对象。在享元对象池中只有两个对象。
1、享元模式优点
(1)节省内存空间,对于可重复的对象只会被创建一次,对象比较多时,就会极大的节省空间。
(2)提高效率,由于创建对象的数量减少,所以对系统内存的需求也减小,使得速度更快,效率更高。
2、享元模式缺点
外部状态由客户端保存,共享对象读取外部状态的开销可能比较大
享元模式要求将内部状态与外部状态分离,这使得程序的逻辑复杂化,同时也增加了状态维护成本
其实对于享元类有内部状态和外部状态,其区分就是图书馆的书一部分可以外借(外部状态),一部分不可外借(内部状态),两个状态的划分对于书籍管理来说有点复杂化了。
3、享元模式与单例模式的区别
(1)享元设计模式是一个类有很多对象,而单例是一个类仅一个对象。
(2)享元模式是为了节约内存空间,提升程序性能,而单例模式则主要是出于共享状态的目的。
OK,以上就是享元模式,在使用的时候只需要记住享元模式的核心思想,然后根据自己的业务需求来选择,因为大大多情况下都不会使用一种设计模式,而是多种设计模式的组合。如有问题还请批评指正。