设计模式 Java设计模式——享元模式 代长亚 2024-11-25 2025-09-05 一、引子 在Java中,String类型具有一些独特的特性。其一,String类型的对象一旦被创建就不可改变;其二,当两个String对象所包含的内容相同时,JVM只创建一个String对象对应这两个不同的对象引用。我们可以通过以下代码来验证这两个特性:
1 2 3 4 5 6 7 public class TestPattern { public static void main (String[] args) { String n = "I Love Java" ; String m = "I Love Java" ; System.out.println(n == m); } }
上述代码会输出true
,这表明在JVM中n
和m
两个引用指向了同一个String对象。若在系统输出之前添加一行代码m = m + "hehe";
,此时n == m
的结果将变为false
。这是因为执行添加语句时,m
指向了一个新创建的String对象,而非修改原来引用的对象。String类型的设计避免了创建大量相同内容的String对象时产生的不必要资源损耗,是享元模式应用的范例,下面让我们深入学习享元模式。
二、定义与分类 享元模式英文称为“Flyweight Pattern”。其定义为:采用共享来避免大量拥有相同内容对象的开销,这里最常见、直观的开销就是内存损耗。享元模式以共享的方式高效支持大量细粒度对象。在该模式中,核心概念是共享,为实现共享,区分了内蕴状态和外蕴状态。内蕴状态是共性,存储在享元内部,不会随环境改变而不同,可共享;外蕴状态是个性,随环境改变而改变,由客户端保持,在具体环境下,客户端将外蕴状态传递给享元以创建不同对象。
根据《Java与模式》,享元模式分为单纯享元模式和复合享元模式。
(一)单纯享元模式
结构
抽象享元角色 :在Java中可由抽象类、接口担当,为具体享元角色规定必须实现的方法,外蕴状态以参数形式通过此方法传入。
具体享元角色 :实现抽象角色规定的方法,若存在内蕴状态,负责为其提供存储空间。
享元工厂角色 :负责创建和管理享元角色,是实现共享的关键。其实现通常使用Singleton模式,确保工厂对象只产生一个实例。
客户端角色 :维护对所有享元对象的引用,并存储对应的外蕴状态。
单纯享元模式的类图如下: [此处可插入单纯享元模式类图,展示Client、Flyweight、FlyweightFactory、ConcreteFlyweight之间的关系]
该模式结构类似简单工厂模式,但重点不同。简单工厂模式主要使系统不依赖于实现细节,而享元模式旨在采用共享技术避免大量相同内容对象的开销。
举例(以咖啡店订单为例)
假设一家咖啡店有多种口味的咖啡(如拿铁、摩卡、卡布奇诺等),接到大量订单时,咖啡口味可设置为共享,不必为每一杯单独生成对象。
以下是相关代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 import java.util.*;public abstract class Order { public abstract void sell () ; } public class FlavorOrder extends Order { public String flavor; public FlavorOrder (String flavor) { this .flavor = flavor; } @Override public void sell () { System.out.println("卖出一份" + flavor + "的咖啡。" ); } } public class FlavorFactory { private Map<String, Order> flavorPool = new HashMap <>(); private static FlavorFactory flavorFactory = new FlavorFactory (); private FlavorFactory () { } public static FlavorFactory getInstance () { return flavorFactory; } public Order getOrder (String flavor) { Order order = null ; if (flavorPool.containsKey(flavor)) { order = flavorPool.get(flavor); } else { order = new FlavorOrder (flavor); flavorPool.put(flavor, order); } return order; } public int getTotalFlavorsMade () { return flavorPool.size(); } } public class Client { private static List<Order> orders = new ArrayList <>(); private static FlavorFactory flavorFactory; private static void takeOrders (String flavor) { orders.add(flavorFactory.getOrder(flavor)); } public static void main (String[] args) { flavorFactory = FlavorFactory.getInstance(); takeOrders("摩卡" ); takeOrders("卡布奇诺" ); takeOrders("香草星冰乐" ); takeOrders("香草星冰乐" ); takeOrders("拿铁" ); takeOrders("卡布奇诺" ); takeOrders("拿铁" ); takeOrders("卡布奇诺" ); takeOrders("摩卡" ); takeOrders("香草星冰乐" ); takeOrders("卡布奇诺" ); takeOrders("摩卡" ); takeOrders("香草星冰乐" ); takeOrders("拿铁" ); takeOrders("拿铁" ); for (Order order : orders) { order.sell(); } System.out.println("\n客户一共买了 " + orders.size() + " 杯咖啡! " ); System.out.println("共生成了 " + flavorFactory.getTotalFlavorsMade() + " 个FlavorOrder java对象! " ); } }
输出结果显示,通过口味共享极大减少了对象数目,降低了内存消耗。例如,客户一共买了15杯咖啡,但只生成了4个FlavorOrder
Java对象。
(二)复合享元模式
结构
抽象享元角色 :同单纯享元模式,为具体享元角色规定必须实现的方法,外蕴状态以参数形式传入。
具体享元角色 :实现抽象角色规定的方法,负责内蕴状态的存储空间(若有)。
复合享元角色 :所代表对象不可共享,但可分解为多个单纯享元对象的组合。
享元工厂角色 :负责创建和管理享元角色,实现共享的关键。
客户端角色 :维护对所有享元对象的引用,并存储对应的外蕴状态。
复合享元模式的类图如下: [此处可插入复合享元模式类图,展示Client、Flyweight、FlyweightFactory、ConcreteFlyweight、ConcreteCompositeFlyweight之间的关系]
该模式左半部类似简单工厂模式,右半部类似合成模式。合成模式用于将具体享元角色和复合享元角色同等对待和处理,确保复合享元中包含的单纯享元具有相同外蕴状态,而单纯享元内蕴状态往往不同。
举例(在餐馆点菜场景下)
以去餐馆吃饭为例,内蕴状态代表菜肴种类,外蕴状态是点菜人。
首先定义抽象享元角色:
1 2 3 4 5 6 interface Menu { public void setPersonMenu (String person, List list) ; public List findPersonMenu (String person, List list) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class PersonMenu implements Menu { private String dish; public PersonMenu (String dish) { this .dish = dish; } public synchronized void setPersonMenu (String person, List list) { list.add(person); list.add(dish); } public List findPersonMenu (String person, List list) { List dishList = new ArrayList <>(); Iterator it = list.iterator(); while (it.hasNext()) { if (person.equals((String) it.next())) { dishList.add(it.next()); } } return dishList; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class FlyweightFactory { private Map menuList = new HashMap (); private static FlyweightFactory factory = new FlyweightFactory (); private FlyweightFactory () { } public static FlyweightFactory getInstance () { return factory; } public synchronized Menu factory (String dish) { if (menuList.containsKey(dish)) { return (Menu) menuList.get(dish); } else { Menu menu = new PersonMenu (dish); menuList.put(dish, menu); return menu; } } public int getNumber () { return menuList.size(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class PersonMenuMuch implements Menu { private Map MenuList = new HashMap <>(); public PersonMenuMuch () { } public void add (String key, Menu menu) { MenuList.put(key, menu); } public synchronized void setPersonMenu (String person, List list) { } public List findPersonMenu (String person, List list) { List nothing = null ; return nothing; } }
1 2 3 4 5 6 7 8 9 public Menu factory (String[] dish) { PersonMenuMuch menu = new PersonMenuMuch (); String key = null ; for (int i = 0 ; i < dish.length; i++) { key = dish[i]; menu.add(key, this .factory(key)); } return menu; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Client { private static FlyweightFactory factory; public static void main (String[] args) { List list1 = new ArrayList <>(); factory = FlyweightFactory.getInstance(); Menu list = factory.factory("尖椒土豆丝" ); list.setPersonMenu("ai92" , list1); list = factory.factory("红烧肉" ); list.setPersonMenu("ai92" , list1); list = factory.factory("地三鲜" ); list.setPersonMenu("ai92" , list1); list = factory.factory("地三鲜" ); list.setPersonMenu("ai92" , list1); list = factory.factory("红焖鲤鱼" ); list.setPersonMenu("ai92" , list1); list = factory.factory("红烧肉" ); list.setPersonMenu("ai921" , list1); list = factory.factory("红焖鲤鱼" ); list.setPersonMenu("ai921" , list1); list = factory.factory("地三鲜" ); list.setPersonMenu("ai921" , list1); System.out.println(factory.getNumber()); List list2 = list.findPersonMenu("ai921" , list1); Iterator it = list2.iterator(); while (it.hasNext()) { System.out.println(" " + it.next()); } } }
(三)两种模式对比
复杂度方面 :复合享元模式比单纯享元模式复杂。
共享效果方面 :复合享元模式在共享上未达预期,虽内部单纯享元可共享,但复合享元角色使用两个Map
保存内蕴状态和对象,未节省空间和对象个数,违背享元模式初衷,应尽量使用单纯享元模式。
三、使用优缺点 (一)优点 享元模式能大幅降低内存中对象数量,提高程序运行速度,例如在处理大量重复字符串或文本系统中字母对象时可节省资源。
(二)缺点
为实现对象共享,需将一些状态外部化,使程序逻辑复杂化。
读取外部状态会使运行时间稍变长。
(三)使用条件
系统中有大量对象,影响系统效率。
对象状态可分离为内外两部分,且内外状态划分及对应关系维护很重要,划分不当可能无法减少对象数量,对应关系维护需花费一定空间和时间,享元模式是以时间换空间,可使用B树等优化对应关系查找。
四、总结 享元模式较为复杂,实际应用相对较少,但共享思想对系统优化有益,在企业级架构设计中应用广泛,如缓存体系。Java中的String和Integer类是其应用实例。