享元模式浅析

源码面前,了无秘密。

1
package com.xiaolu.flyweightdemotest; 2 3 4 import org.junit.Assert; 5 import org.junit.Test; 6 7 import com.xiaolu.flyweight.FlyweigthtFactory; 8 import com.xiaolu.flyweight.IChess; 9 10 11 12 /*** 13 * 享元模式:flyweight有轻量的意思。享元即共享。这里的轻量到底指什么? 14 * 15 * 在享元模式中分为内蕴状态和外蕴状态 16 * 其中: 17 * 1) 内蕴状态是不随环境而改变的,因此可以用用于共享。 (而一个对象中的内蕴状态很少,flyweight更多的表明了这个模式的用途---轻量级对象的共享) 18 * 2) 外蕴状态是随着环境的变化而变化的。外蕴状态一般是由外部传入的。 19 * 20 * 享元模式定义: 所谓享元模式就是运行共享技术有效地支持大量细粒度对象的复用。 ps: 细粒度(其实也就是因为内蕴状态少,因此对象"小"----可以从内存的观点去考虑) 21 * 使用享元模式的场景是: 当系统需要大量创建对象时,如果能够使用享元模式可以大量节省内存,提高效率。 22 * 23 * 在这里思考一个问题:为什么是细粒度的? 也就是为什么是"小"对象(内蕴状态很少的)对象进行共享呢? 如果多了会如何,内蕴状态多了进行共享不是更能节省内存,享元模式的意义更大吗? 24 * 所谓能够共享,即多个对象内部的状态应当是一样的,否则就谈不上共享。那么如果一个对象的内蕴状态越多,能共享的可能性就越低。【如果有一个对象只有一个属性和有10个属性的对象,哪个更 25 * 容易共享呢? 如果A、B、C三个客户想要共享,那么如果有10个内蕴状态的,就需要10个属性都一致,A、B、C三个客户才可以共享。而 一个属性的则就方便多了。 当然多个属性的也并非不能共享, 26 * 但是从现实场景考虑-----还是算了】 27 * 28 * 但是还有一个问题: 是不是属性越少的就可以去使用享元模式进行共享? 29 * 不见得。那怕只有一个属性,但是如果属性的取值范围很大(甚至是任意的),那么享元模式基本上就没什么意义(每次都是创建新的) 30 * 31 * java中的String类型即使用了享元模式 "abc" == "abc" , 两个相同字符串共享同一个String对象。 ps:但是因为字符串相等,包括长度和顺序,因此如果一个程序中 32 * 没有任何两个字符串是相等的(equals),那么显然也就不能"共享"了。当然从大的范围看,还是有很多情况下,相同字符串多次出现的(否则sun也不傻......) 33 * 34 * 35 * 我觉得经典使用享元模式的场景是: 五子棋 (围棋) , 英文字符的书写。 36 * 五子棋,不管有多少个,都是只有黑白两色。只是分布在不同的位置。因此就可以使用享元模式(白色棋子、黑色棋子共享),而放置的位置信息则可以通过外蕴状态来输入。 37 * 英文字符。26*2 = 52个(大小写),其他的位置、颜色、粗细等,可以通过外部来传入。(否则一篇文章有多长,需要创建多少个对象?一本书呢?哦.....) 38 * 39 * 40 * 还有复合享元,下次有时间再看。 41 * 42 * @author xiaolu 2018年10月11日 43 */ 44 public class TestFlyweight { 45 @Test 46 public void test_StringFlyweight() throws Exception { 47 String a = "xiaolu"; 48 String b = "xiaolu"; 49 Assert.assertTrue(a == b); 50 } 51 52 53 @Test 54 public void test_flyweightdemo() throws Exception { 55 IChess black = FlyweigthtFactory.INSTANCE.get("black");// 黑色棋子 56 IChess white = FlyweigthtFactory.INSTANCE.get("white");//白色棋子 57 // 外蕴状态即是位置,通过外部参数传入 58 black.put(10, 10); 59 white.put(100, 100); 60 black.put(12, 12); 61 white.put(30, 30); 62 black.put(15 , 15 ); 63 white.put(100, 100); 64 65 // 因为有工厂的存在不用过多思考,一共创建了2个对象。【就是把black换成FlyweigthtFactory.INSTANCE.get("black")】也没什么区别,除了写起来麻烦。 66 } 67 68 69 }

 

 1 package com.xiaolu.flyweight;
 2 
 3 /**
 4  * 享元接口  此接口往往定义了享元对象的提供的行为,将外蕴状态通过行为的参数来传入
 5  * @author xiaolu 2018年10月11日
 6  */
 7 public interface IChess {
 8     
 9     /***
10      * 假设的放置棋子的方法 x, y 分别代表坐标
11      * @param x
12      * @param y
13      */
14     public void put(int x , int y);
15 }

 

 1 package com.xiaolu.flyweight;
 2 
 3 public class Chess implements IChess {
 4     private String color;
 5 
 6     /**
 7      * 网上资料将此处传入的常常称为"外蕴状态",个人觉得更应该叫做“内蕴状态”或者 "内蕴状态的对应者"
 8      * 没有这种对应,无法知道你想要"共享什么内蕴"的对象。
 9      * 
10      * @author : xiaolu 2018年10月11日
11      */
12     public Chess(String color) {
13         this.color = color;
14     }
15 
16     @Override
17     public void put(int x, int y) {
18         System.out.println(this.color + " 棋 放在 x=" + x + ", y=" + y + "的位置.");
19     }
20 
21 }

 

 1 package com.xiaolu.flyweight;
 2 
 3 import java.util.HashMap;
 4 import java.util.Map;
 5 
 6 /**
 7  * 享元工厂,采用单例实现(此处通过enum来实现,线程安全,规避反序列化-----新学到东西的一次实践)
 8  * ps:莫名就想起了cache----------------
 9  * @author xiaolu 2018年10月11日
10  */
11 public enum FlyweigthtFactory {
12     INSTANCE;
13 
14     private Map<String, IChess> chesses = new HashMap<String, IChess>(2);
15 
16     private FlyweigthtFactory() {
17         chesses.put("black", new Chess("black"));
18         chesses.put("white", new Chess("white"));
19     }
20 
21     /**
22      * 此处有一个有趣的地方:应该使用assert 呢还是,if判断抛异常?
23      * 使用assert ,assert在调试时,打开jre -ea即可起作用,而在产品中则一般不会开启-ea,从而省去了代码检查。关键点在于:
24      *     用户的输入点是需要我们去进行判断的(因为不知道从界面上获取的用户输入会如何-----思维跳出当前这个五子棋吧 ,  ,想想java-web开发,应该在controller就进行输入验证)。而此处逻辑是我们程序
25      * 自己内部调用的,如果此处去检查抛出异常,那么就是前面没有进行错误检查处理。属于程序逻辑设计问题了。
26      * 在开发时内部使用assert,一旦测试通过,则可以忽略掉。毕竟层层检查有时候是无意义并且浪费效率的。(此处也是借鉴了《大道至简》上的思维)
27      * @param color
28      * @return
29      */
30     public IChess get(String color) {
31         assert chesses.containsKey(color);
32         
33         /*
34          * 一般是如此实现----(cache的方式),由于五子棋,我们知道就是黑白两色,因此在构造函数中直接构造,也就不需要此处再去判断加入了。若范围不确定,则需要(就是一般cache的实现思维) 
35          * 
36          * if(!chesses.containsKey(color)){
37          *    chesses.put(color,new Chess(color));
38          *  }
39         */
40         return chesses.get(color);
41     }
42 }

 

posted @ 2018-10-11 23:46  一粒粟  阅读(431)  评论(0编辑  收藏  举报