享元模式也是一种结构型模式,这篇是介绍结构型模式的最后一篇了(由于代理模式很早以前就已经写过了)。享元模式采用一个共享来避免大量拥有相同内容对象的开销。这种开销最多见、最直观的就是内存损耗。html
享元模式是指运用共享技术有效的支持大量细粒度对象的复用。系统只使用少许的对象,而这些对象都很类似,状态变化很小,能够实现对象的屡次复用。因为享元模式要求可以共享的对象必须是细粒度对象,所以它又称为轻量级模式,它是一种对象结构型模式。java
咖啡问题,在一家咖啡店里有几种口味的咖啡,例如:拿铁、摩卡、卡布奇诺等等。最近这家店在搞促销活动,一中午能卖出几百杯咖啡,那么咖啡的口味就是一种共享的元素。下面用代码来实现一下这个例子。面试
定义订单接口设计模式
/** * 订单接口 */ public interface Order { //卖出咖啡 void sell(); }
具体的订单实现ide
public class CoffeeOrder implements Order { //咖啡口味 public String flavor; public CoffeeOrder(String flavor){ this.flavor = flavor; } @Override public void sell() { System.out.println("卖出了一份"+flavor+"的咖啡。"); } }
订单工厂类post
import com.google.common.collect.Maps; import java.util.Map; import java.util.Objects; /** * 订单工厂类 */ public class CoffeeOrderFactory { private static Map<String,Order> cof = Maps.newHashMap(); /** * 得到订单 * @param flavor 口味 * @return */ public static Order getOrder(String flavor){ Order order = cof.get(flavor); if(Objects.isNull(order)){ order = new CoffeeOrder(flavor); cof.put(flavor,order); } return order; } /** * 获取最终建立的对象个数 * @return */ public static int getSize(){ return cof.size(); } }
测试类性能
import org.assertj.core.util.Lists; import java.util.List; /** * 测试买咖啡 */ public class MyTest { public static void main(String[] args) { buyCoffee("拿铁"); buyCoffee("卡布奇诺"); buyCoffee("摩卡"); buyCoffee("拿铁"); buyCoffee("拿铁"); buyCoffee("拿铁"); buyCoffee("卡布奇诺"); buyCoffee("卡布奇诺"); buyCoffee("卡布奇诺"); buyCoffee("摩卡"); buyCoffee("摩卡"); buyCoffee("摩卡"); //打印出卖出的咖啡 coffeeOrderList.stream().forEach(Order::sell); System.out.println("一共卖出去"+coffeeOrderList.size()+"杯咖啡!"); System.out.println("一共生成了"+CoffeeOrderFactory.getSize()+"个Java对象!"); } //订单列表 public static List<Order> coffeeOrderList = Lists.newArrayList(); /** * 买咖啡 * @param flavor 口味 */ public static void buyCoffee(String flavor){ coffeeOrderList.add(CoffeeOrderFactory.getOrder(flavor)); } }
运行结果学习
卖出了一份拿铁的咖啡。
卖出了一份卡布奇诺的咖啡。
卖出了一份摩卡的咖啡。
卖出了一份拿铁的咖啡。
卖出了一份拿铁的咖啡。
卖出了一份拿铁的咖啡。
卖出了一份卡布奇诺的咖啡。
卖出了一份卡布奇诺的咖啡。
卖出了一份卡布奇诺的咖啡。
卖出了一份摩卡的咖啡。
卖出了一份摩卡的咖啡。
卖出了一份摩卡的咖啡。
一共卖出去12杯咖啡!
一共生成了3个Java对象!
从上面的运行结果能够看出来,虽然卖出去了12杯咖啡,可是最终的口味对象只有3个,由于咖啡口味只有在第一次使用的时候建立,后面就直接使用不会再建立了。测试
下面仍是来分析一下享元模式的结构吧,结构图以下:this
享元模式涉及到的角色有抽象享元角色、具体享元角色、复合享元角色、享元工厂角色,以及客户端角色。具体说明以下:
标准的享元模式中既包含享元对象又包含非享元对象,可是在实际使用过程当中咱们会用到具体两种特殊形式的享元模式:单纯享元模式和复合享元模式。
单纯享元模式是指,全部的具体享元对象都是能够共享的,不包括非享元对象。
复合享元模式是指,将一些单纯享元对象使用组合模式加以组合,还能够造成组合享元对象,这样的复合享元对象不能共享,可是它能够分解成单纯享元对象,分解后就能够共享了。
一、能够极大的减小内存中对象的数量,使得相同或类似的对象在内存中只保存一份,从而能够节约系统资源,提升系统性能。
二、享元模式的外部状态相对独立,并且不会影响其内部状态,从而使得享元对象能够在不一样的环境中被共享。
一、享元模式使得系统变得复杂,须要分离出内部状态和外部状态,这使得程序的逻辑复杂化。
想了解更多的设计模式请查看Java设计模式学习记录-GoF设计模式概述。
面试面到怀疑人生,继续加油吧!
二、为了使对象能够共享,享元模式须要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长。
适用场景
一个系统有大量相同或者类似的对象,形成内存的大量耗费。
对象的大部分状态均可之外部化,能够将这些外部状态传入对象中。
在使用享元模式时须要维护一个存储享元对象的享元池,而这须要耗费必定的系统资源,所以,应当在须要屡次重复使用享元对象时才值得使用享元模式。
延伸
在JDK中就有使用享元模式的例子,最多见的就是咱们使用的String类,你们都知道String类是被final修饰的,因此不会被继承,每次变动都会生成一个新的字符串,这样就有点占内存了。因此若是直接写出了一个字符串,当后面又写出了一个一样的字符串时会自动去堆中(JDK7以上)查看是否已经存在这个字符串了,若是已经存在则直接使用,若是不存在这个时候才在堆中再给开辟一块空间存储字符串。
例以下面的例子:
运行结果: