设计模式:享元模式(Flyweight)

定义与分类 享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。GOF中对享元模式的定义为:采用一个共享类来避免大量拥有相同内容的“小类”的开销。这种开销中最常见、直观的影响就是增加了内存的损耗。享元模式以共享的方式高效的支持大量的细粒度对象,减少其带来的开销。
【设计模式:享元模式(Flyweight)】在名字和定义中都体现出了共享这一个核心概念,那么怎么来实现共享呢?事物之间都是不同的,但是又存在一定的共性,如果只有完全相同的事物才能共享,那么享元模式可以说就是不可行的;因此我们应该尽量将事物的共性共享,而又保留它的个性。为了做到这点,享元模式中区分了内蕴状态和外蕴状态。内蕴状态就是共性,外蕴状态就是个性了。
内蕴状态存储在享元内部,不会随环境的改变而有所不同,是可以共享的;外蕴状态是不可以共享的,它随环境的改变而改变的,因此外蕴状态是由客户端来保持(因为环境的变化是由客户端引起的)。在每个具体的环境下,客户端将外蕴状态传递给享元,从而创建不同的对象出来。
我们引用《Java 与模式》中的分类,将享元模式分为:单纯享元模式和复合享元模式。
单纯享元模式
先从简单的入手,看看单纯享元模式的结构。
1) 抽象享元角色(Flyweight):为具体享元角色规定了必须实现的方法,而外蕴状态就是以参数的形式通过此方法传入。在Java 中可以由抽象类、接口来担当。
2) 具体享元角色(ConcreteFlyweight):实现抽象角色规定的方法。如果存在内蕴状态,就负责为内蕴状态提供存储空间。
3) 享元工厂角色(FlyweightFactory):负责创建和管理享元角色。要想达到共享的目的,这个角色的实现是关键!
4) 客户端角色(Client):维护对所有享元对象的引用,而且还需要存储对应的外蕴状态。
类图为:
设计模式:享元模式(Flyweight)
文章图片

复合享元模式
再来看看复合享元模式的结构。
1) 抽象享元角色(Flyweight):为具体享元角色规定了必须实现的方法,而外蕴状态就是以参数的形式通过此方法传入。在Java 中可以由抽象类、接口来担当。
2) 具体享元角色(ConcreteFlyweight):实现抽象角色规定的方法。如果存在内蕴状态,就负责为内蕴状态提供存储空间。
3) 复合享元角色(ConcreteCompositeFlyweight):它所代表的对象是不可以共享的,并且可以分解成为多个单纯享元对象的组合。
4) 享元工厂角色(FlyweightFactory):负责创建和管理享元角色。要想达到共享的目的,这个角色的实现是关键!
5) 客户端角色(Client):维护对所有享元对象的引用,而且还需要存储对应的外蕴状态。统比一下单纯享元对象和复合享元对象,里面只多出了一个复合享元角色,但是它的结构就发生了很大的变化。
我们还是使用类图来表示下:
设计模式:享元模式(Flyweight)
文章图片

正如你所想,复合享元模式采用了组合模式——为了将具体享元角色和复合享元角色同等对待和处理。这也就决定了复合享元角色中所包含的每个单纯享元都具有相同的外蕴状态,而这些单纯享元的内蕴状态可以是不同的。
享元模式的实现 单纯享元模式

//抽象享元角色--Flyweight public interface Shape { void draw(); } //具体享元角色--ConcreteFlyweight public class Circle implements Shape { private String color; private int x; private int y; private int radius; public Circle(String color){ this.color = color; }public void setX(int x) { this.x = x; }public void setY(int y) { this.y = y; }public void setRadius(int radius) { this.radius = radius; }@Override public void draw() { System.out.println("Circle: Draw() [Color : " + color +", x : " + x +", y :" + y +", radius :" + radius); } } //享元工厂角色--FlyweightFactory public class ShapeFactory { private static final HashMap circleMap = new HashMap(); public static Shape getCircle(String color) { Circle circle = (Circle)circleMap.get(color); if(circle == null) { circle = new Circle(color); circleMap.put(color, circle); System.out.println("Creating circle of color : " + color); } return circle; } } //客户端角色--Client public class Client{ private static final String colors[] = { "Red", "Green", "Blue", "White", "Black" }; public static void main(String[] args) {for(int i=0; i < 20; ++i) { Circle circle = (Circle)ShapeFactory.getCircle(getRandomColor()); circle.setX(getRandomX()); circle.setY(getRandomY()); circle.setRadius(100); circle.draw(); } } private static String getRandomColor() { return colors[(int)(Math.random()*colors.length)]; } private static int getRandomX() { return (int)(Math.random()*100 ); } private static int getRandomY() { return (int)(Math.random()*100); } }

验证输出
Creating circle of color : Black Circle: Draw() [Color : Black, x : 19, y :21, radius :100 Creating circle of color : Green Circle: Draw() [Color : Green, x : 98, y :73, radius :100 Creating circle of color : Blue Circle: Draw() [Color : Blue, x : 49, y :58, radius :100 Circle: Draw() [Color : Green, x : 6, y :81, radius :100 Creating circle of color : White Circle: Draw() [Color : White, x : 72, y :26, radius :100 Circle: Draw() [Color : Green, x : 57, y :10, radius :100 Circle: Draw() [Color : Black, x : 7, y :48, radius :100 Circle: Draw() [Color : Green, x : 32, y :9, radius :100 Circle: Draw() [Color : Green, x : 30, y :22, radius :100 Creating circle of color : Red Circle: Draw() [Color : Red, x : 55, y :12, radius :100 Circle: Draw() [Color : White, x : 20, y :47, radius :100 Circle: Draw() [Color : Black, x : 91, y :94, radius :100 Circle: Draw() [Color : Green, x : 72, y :30, radius :100 Circle: Draw() [Color : Green, x : 43, y :42, radius :100 Circle: Draw() [Color : Blue, x : 53, y :10, radius :100 Circle: Draw() [Color : Green, x : 4, y :2, radius :100 Circle: Draw() [Color : White, x : 8, y :32, radius :100 Circle: Draw() [Color : Red, x : 60, y :44, radius :100 Circle: Draw() [Color : White, x : 72, y :39, radius :100 Circle: Draw() [Color : White, x : 25, y :21, radius :100

复合享元模式
//抽象享元角色--Flyweight public interface ICoffee { public void serveCoffee(CoffeeContext context); } //具体享元角色--ConcreteFlyweight public class Coffee implements ICoffee { private final String flavor; //Coffee的风格public Coffee(String newFlavor) { this.flavor = newFlavor; System.out.println("Coffee is created! - " + flavor); }public String getFlavor() { return this.flavor; }public void serveCoffee(CoffeeContext context) { System.out.println("Serving " + flavor + " to table " + context.getTable()); } } //复合享元角色--ConcreteCompositeFlyweight class CoffeeContext { private final int tableNumber; public CoffeeContext(int tableNumber) { this.tableNumber = tableNumber; }public int getTable() { return this.tableNumber; } } //享元工厂角色--FlyweightFactory public class CoffeeFactory { private HashMap flavors = new HashMap(); public Coffee getCoffeeFlavor(String flavorName) { Coffee flavor = flavors.get(flavorName); if (flavor == null) { flavor = new Coffee(flavorName); flavors.put(flavorName, flavor); } return flavor; }public int getTotalCoffeeFlavorsMade() { return flavors.size(); } } //客户端角色--Client public class Waitress { // coffee array private static Coffee[] coffees = new Coffee[20]; // table array private static CoffeeContext[] tables = new CoffeeContext[20]; private static int ordersCount = 0; private static CoffeeFactory coffeeFactory; public static void takeOrder(String flavorIn, int table) { coffees[ordersCount] = coffeeFactory.getCoffeeFlavor(flavorIn); tables[ordersCount] = new CoffeeContext(table); ordersCount++; }public static void main(String[] args) { coffeeFactory = new CoffeeFactory(); takeOrder("Cappuccino", 2); takeOrder("Cappuccino", 2); takeOrder("Regular Coffee", 1); takeOrder("Regular Coffee", 2); takeOrder("Regular Coffee", 3); takeOrder("Regular Coffee", 4); takeOrder("Cappuccino", 4); takeOrder("Cappuccino", 5); takeOrder("Regular Coffee", 3); takeOrder("Cappuccino", 3); for (int i = 0; i < ordersCount; ++i) { coffees[i].serveCoffee(tables[i]); }System.out.println("\nTotal Coffee objects made: " + coffeeFactory.getTotalCoffeeFlavorsMade()); } }

验证输出
Coffee is created! - Cappuccino Coffee is created! - Regular Coffee Serving Cappuccino to table 2 Serving Cappuccino to table 2 Serving Regular Coffee to table 1 Serving Regular Coffee to table 2 Serving Regular Coffee to table 3 Serving Regular Coffee to table 4 Serving Cappuccino to table 4 Serving Cappuccino to table 5 Serving Regular Coffee to table 3 Serving Cappuccino to table 3Total Coffee objects made: 2

总结 使用优缺点
享元模式优点就在于它能够大幅度的降低内存中对象的数量;而为了做到这一步也带来了它的缺点:它使得系统逻辑复杂化,而且在一定程度上外蕴状态影响了系统的速度。所以一定要切记使用享元模式的条件:
1)系统中有大量的对象,他们使系统的效率降低。
2)这些对象的状态可以分离出所需要的内外两部分。
外蕴状态和内蕴状态的划分以及两者关系的对应也是非常值得重视的。只有将内外划分妥当才能使内蕴状态发挥它应有的作用;如果划分失误,在最糟糕的情况下系统中的对象是一个也不会减少的!两者的对应关系的维护和查找也是要花费一定的空间(当然这个比起不使用共享对象要小得多)和时间的,可以说享元模式就是使用时间来换取空间的。可以采用相应的算法来提高查找的速度。

    推荐阅读