源码学习系列:设计模式之单例模式
原文同步发表至个人博客【夜月归途】
原文链接:https://www.guitu18.com/post/2019/03/11/29.html
作者:夜月归途出处:http://www.guitu18.com/
本博客中未标明转载的文章归作者夜月归途和博客园所有。 欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
愿自己变得越来越好。2019年3月11日19:30:26
源码学习系列:设计模式之单例模式
单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并 提供一个全局访问点。单例模式是创建型模式。单
例模式在现实生活中应用也非常广泛。 例如,国家主席、公司 CEO、部门经理等。
在 J2EE 标准中,ServletContext、 ServletContextConfig 等;在 Spring 框架应用中 ApplicationContext;数据库的连接池也都是单例形式。
本篇目录:
- 饿汉式单例
- 懒汉式单例
- 反射破坏单例
- 序列化破坏单例
- 注册式单例之容器缓存
- 注册式单例之枚举单例
- ThreadLocal 线程单例
一、饿汉式单例
先看代码:
public class HungrySingleton { private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton(); private HungrySingleton() {} public static HungrySingleton getInstance() { return HUNGRY_SINGLETON; } }
代码非常简单,使用了static关键字,在类被加载的时候就进行实例化,并私有构造方法,提供一个静态方法返回实例的引用。这里的静态成员变量也可以使用静态代码块进行初始化,如:
static { HUNGRY_SINGLETON = new HungryStaticSingleton(); }
这种方式简单有效,没有任何形式的锁,所以不存在效率问题。但缺点是不管类有没有被使用,只要被加载就回初始化,会造成资源浪费,当这样的单例非常多的是时候,是需要占用很大的内存空间的。
二、懒汉式单例
为了解决上面的的问题,懒汉式单例应时而生:
public class LazySimpleSingleton { private static LazySimpleSingleton lazy = null; private LazySimpleSingleton() {} public synchronized static LazySimpleSingleton getInstance() { if (lazy == null) { lazy = new LazySimpleSingleton(); } return lazy; } }
这里是在需要获取实例的时候才会进行初始化,方法内部做了一个
null判断,保证只会被初始化一次,并且为了保证线程安全加了同步锁。
这样的好处是,只有在类真正需要调用的时候才会被初始化,但是因为加了同步锁,会导致在多线程环境下产生性能问题。需要继续优化,看代码:
public class LazyDoubleCheckSingleton { private volatile static LazyDoubleCheckSingleton lazy = null; private LazyDoubleCheckSingleton() {} public static LazyDoubleCheckSingleton getInstance() { if (lazy == null) { synchronized (LazyDoubleCheckSingleton.class){ if (lazy == null) { lazy = new LazySimpleSingleton(); } } } return lazy; } }
在
getInstance()的代码中,使用了双重检查,同步锁外层非空判断是为了提高性能,内层非空判断是为了线程安全,同时为了解决指令重排序问题,在静态成员变量前面加了
volatile关键字,关于
volatile相关知识这里不做展开。
这段代码解决了实例化之后的多线程访问的性能问题,但是在实例化之前,还是可能会有多个线程进入到
synchronized代码中,虽然只有一次,但是终究是不完美的。
再看看下面的代码:
public class LazyInnerClassSingleton { private LazyInnerClassSingleton() {} public static final LazyInnerClassSingleton getInstance() { return LazyHolder.LAZY; } private static class LazyHolder { private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton(); } }
顾名思义,这段代码使用了内部类的形式,利用了内部类一定要在调用前初始化的特点,且这个特性是JVM提供并保证的,我们不需要为其加任何锁,同时也避免了线程安全带的问题,这种方式避免了饿汉式的资源浪费,也兼顾了
synchronized的性能问题,算是比较完美的解决方案。
但是,为什么要来但是呢,因为总有一些不走寻常路的程序员,比如我,喜欢来一些野路子,会破坏上面的代码的单例性质,接着看。
三、反射破坏单例
就一上面最后一步的代码来说,正常调用时没有任何问题的。但是如果我偏不走
getInstance()方法,我用反射调用呢?
try { Class<?> clazz = LazyInnerClassSingleton.class; // 通过反射拿到私有的构造方法 Constructor c = clazz.getDeclaredConstructor(null); // 开启暴力访问,强吻,不愿意也要上 c.setAccessible(true); // 暴力初始化 LazyInnerClassSingleton c1 = (LazyInnerClassSingleton) c.newInstance(); LazyInnerClassSingleton c2 = (LazyInnerClassSingleton) c.newInstance(); // 很明显这里的c1和c2肯定不是同一个对象 System.out.println(c1 == c2); // false } catch (Exception e) { e.printStackTrace(); }
不仅仅是上面最后一步的代码,上面其他步骤中的代码,用反射强制调用都会破坏单例性质,我
Java大反射就是这么任性,就喜欢看你看不惯我又干不掉我的样子,哈哈。
你说,那我可以在私有构造方法中抛异常,比如:
private LazyInnerClassSingleton() { if (LazyHolder.LAZY != null) { throw new RuntimeException("不允许创建多个实例"); } }
但这时又回到最开始的线程安全问题上了,多线程反射调用时,你要保证构造方法的线程安全,又要加锁,又要处理。等于又回到第一步第二部中的循环中去了。
四、序列化破坏单例
再看另一种情况,序列化。当我们将一个对象创建好后,有时候需要将对象写入磁盘或者通过网络流传输,需要先序列化,下次使用时,进行反序列化。但是反序列化之后的对象会重新分配内存,相当于重新创建。这种情况下也会破坏单例。以第一步饿汉式单例为例,看下面的代码:
HungrySingleton s1 = null; HungrySingleton s2 = HungrySingleton.getInstance(); FileOutputStream fos = null; try { fos = new FileOutputStream("HungrySingleton.obj"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(s2); oos.flush(); oos.close(); FileInputStream fis = new FileInputStream("HungrySingleton.obj"); ObjectInputStream ois = new ObjectInputStream(fis); s1 = (HungrySingleton)ois.readObject(); ois.close(); System.out.println(s1); // com.guitu18.singleton.HungrySingleton@11531931 System.out.println(s2); // com.guitu18.singleton.HungrySingleton@7f31245a System.out.println(s1 == s2); // false } catch (Exception e) { e.printStackTrace(); }
这里经过反序列化之后,返回的对象是一个新的对象,为什么会这样呢?
点进去
readObject()一探究竟:
public final Object readObject() throws IOException, ClassNotFoundException { if (enableOverride) { return readObjectOverride(); } // if nested read, passHandle contains handle of enclosing object int outerHandle = passHandle; try { Object obj = readObject0(false); handles.markDependency(outerHandle, passHandle); ClassNotFoundException ex = handles.lookupException(passHandle); ... return obj; } finally { ... } }
为了便于阅读和保证页面简洁,省去了无关代码,下同。
可以看出在读取对象时时调用了
Object obj = readObject0(false),继续点进去
private Object readObject0(boolean unshared) throws IOException { ... // 上面略去的部分代码是取得tc值,下面根据tc匹配类型选择不同的读取方式 try { switch (tc) { case TC_NULL: return readNull(); case TC_REFERENCE: return readHandle(unshared); case TC_CLASS: return readClass(unshared); case TC_CLASSDESC: case TC_PROXYCLASSDESC: return readClassDesc(unshared); case TC_STRING: case TC_LONGSTRING: return checkResolve(readString(unshared)); case TC_ARRAY: return checkResolve(readArray(unshared)); case TC_ENUM: return checkResolve(readEnum(unshared)); case TC_OBJECT: return checkResolve(readOrdinaryObject(unshared)); ... } } finally { depth--; bin.setBlockDataMode(oldMode); } }
上面略去的部分代码是取得
tc值,下面根据
tc匹配类型选择不同的读取方式,这里走的是最后一个
TC_OBJECT分支
checkResolve(readOrdinaryObject(unshared)),点进
readOrdinaryObject(unshared)方法后会继续找到
resolveObject(obj)方法,在这个方法里我找到了答案:
private Object readOrdinaryObject(boolean unshared) throws IOException { ... ObjectStreamClass desc = readClassDesc(false); desc.checkDeserialize(); Class<?> cl = desc.forClass(); if (cl == String.class || cl == Class.class || cl == ObjectStreamClass.class) { throw new InvalidClassException("invalid class descriptor"); } Object obj; try { obj = desc.isInstantiable() ? desc.newInstance() : null; } catch (Exception ex) { ... } ... if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) { Object rep = desc.invokeReadResolve(obj); if (unshared && rep.getClass().isArray()) { rep = cloneArray(rep); } if (rep != obj) { // Filter the replacement object if (rep != null) { if (rep.getClass().isArray()) { filterCheck(rep.getClass(), Array.getLength(rep)); } else { filterCheck(rep.getClass(), -1); } } handles.setObject(passHandle, obj = rep); } }return obj; }
在这里做了一个判断
desc.isInstantiable(),是否可以被序列化,判断依据也因简单,有没有构造方法。
这里判断为
true走了
desc.newInstance()返回了一个新的对象。这样也就解释了,单例为什么会被序列化破坏了。
那么就没有办法解决吗?有,我们只需要添加一个
readResolve方法就可以了,还是以饿汉式为例,看代码:
public class HungrySingleton implements Serializable { public final static HungrySingleton INSTANCE = new HungrySingleton(); private HungrySingleton() { } public static HungrySingleton getInstance() { return INSTANCE; } private Object readResolve() { return INSTANCE; } }
在最后添加了一个
readResolve方法,返回了当前实例化出来的对象,这样就可以保证我们在经过序列化和反序列化之后还是原来的对象,保证了单例性质。
那么为什么会这样呢?继续看上面
readOrdinaryObject方法代码最后的那个
if判断
desc.hasReadResolveMethod():
if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) { Object rep = desc.invokeReadResolve(obj); if (unshared && rep.getClass().isArray()) { rep = cloneArray(rep); } if (rep != obj) { // Filter the replacement object if (rep != null) { if (rep.getClass().isArray()) { filterCheck(rep.getClass(), Array.getLength(rep)); } else { filterCheck(rep.getClass(), -1); } } handles.setObject(passHandle, obj = rep); } }
这里很有趣,如果
desc.hasReadResolveMethod()判断为
true那么会调用一个
invokeReadResolve()方法,如果
obj != rep会将
rep赋值给
obj返回。
在
desc.hasReadResolveMethod()只做了一个判断:
boolean hasReadResolveMethod() { requireInitialized(); return (readResolveMethod != null); }
继续追踪这个成员变量
readResolveMethod,我们在代码中找到了为其赋值的地方:
readResolveMethod = getInheritableMethod(cl, "readResolve", null, Object.class);
我们继续查看
getInheritableMethod()这个方法做了什么:
private static Method getInheritableMethod(Class<?> cl, String name, Class<?>[] argTypes, Class<?> returnType) { Method meth = null; Class<?> defCl = cl; while (defCl != null) { try { meth = defCl.getDeclaredMethod(name, argTypes); break; } catch (NoSuchMethodException ex) { defCl = defCl.getSuperclass(); } } if ((meth == null) || (meth.getReturnType() != returnType)) { return null; } meth.setAccessible(true); int mods = meth.getModifiers(); if ((mods & (Modifier.STATIC | Modifier.ABSTRACT)) != 0) { return null; } else if ((mods & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0) { return meth; } else if ((mods & Modifier.PRIVATE) != 0) { return (cl == defCl) ? meth : null; } else { return packageEquals(cl, defCl) ? meth : null; } }
意思很明白,通过反射根据名称、形参和返回值在Class中查找方法,找到就将该方法返回并赋值给成员变量
readResolveMethod。根据调用时传入的参数,我们看出是在查找一个名为
readResolve,无形参,返回值为
Object的方法,也就是说
readResolveMethod保存的是我们自己添加的
readResolve()方法。
再回到前面的
if判断,如果该方法不为空,则调用该方法,并将返回值赋值给
obj作为反序列化结果返回。
到这里谜团就揭开了,为什么添加了
readResolve就能保证序列化不会破坏单例了,因为反序列化后拿到的就是我们添加的
readResolve方法的返回值。
其实
JVM在设计之初就考虑到了序列化会造成各种特殊情况,并对其做了一系列特殊处理。这里在反序列化时,虽然还是会
new出来一个新的对象,但是在检查了
readResolve方法后,会将该方法调用后的结果返回,之前
new出来的对象会被垃圾回收器回收,这一切都是在
JVM的保障下完成的。
五、注册式单例之容器缓存
注册式单例,也叫登记式单例。其实质就是维护一个
Map来实现的,通过
key来获取存放在
Map中的实例。
大名鼎鼎的
Spring的
IOC容器
BeanFactory就是注册式单例。在调用时,先判断
Map中是否已有该实例,有就直接返回,没有就创建一个保存到
Map中再返回。
下面是一个
IOC容器的简化版实现,代码比较简单,就不做说明和测试了。
public class ContainerSingleton { private static Map<String, Object> ioc = new ConcurrentHashMap<String, Object>(); private ContainerSingleton() { } public static Object getInstance(String className) { synchronized (ioc) { if (!ioc.containsKey(className)) { Object obj = null; try { obj = Class.forName(className).newInstance(); ioc.put(className, obj); } catch (Exception e) { e.printStackTrace(); } return obj; } else { return ioc.get(className); } } } }
六、注册式单例之枚举单例
再来看枚举式单例,枚举单例使用起来非常简单,代码如下:
public enum EnumSingleton { INSTANCE; private Object data; public Object getData() { return data; } public void setData(Object data) { this.data = data; } }
直接上测试代码:
try { EnumSingleton instance1 = EnumSingleton.getInstance(); instance1.setData(new Object()); EnumSingleton instance2 = EnumSingleton.getInstance(); System.out.println(instance1 == instance2); // true System.out.println(instance1.getData()); // java.lang.Object@234bef66 System.out.println(instance2.getData()); // java.lang.Object@234bef66 System.out.println(instance1.getData() == instance1.getData()); // true FileOutputStream fos = new FileOutputStream("EnumSingleton.obj"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(instance2); oos.flush(); oos.close(); FileInputStream fis = new FileInputStream("EnumSingleton.obj"); ObjectInputStream ois = new ObjectInputStream(fis); EnumSingleton instance3 = (EnumSingleton) ois.readObject(); ois.close(); System.out.println(instance1.getData()); // java.lang.Object@234bef66 System.out.println(instance3.getData()); // java.lang.Object@234bef66 System.out.println(instance1.getData() == instance3.getData()); // true } catch (Exception e) { e.printStackTrace(); }
可以看出即便是序列化,也不能破坏枚举的单例性质。
为什么如此神奇呢?反编译
EnumSingleton.class看看:
public final class EnumSingleton extends Enum { public static EnumSingleton[] values() { return (EnumSingleton[])$VALUES.clone(); } public static EnumSingleton valueOf(String name) { return (EnumSingleton)Enum.valueOf(com/guitu18/singleton/EnumSingleton, name); } private EnumSingleton(String s, int i) { super(s, i); }public Object getData() { return data; } public void setData(Object data) { this.data = data; } public static EnumSingleton getInstance() { return INSTANCE; } public static final EnumSingleton INSTANCE; private Object data; private static final EnumSingleton $VALUES[]; static { INSTANCE = new EnumSingleton("INSTANCE", 0); $VALUES = (new EnumSingleton[] { INSTANCE }); } }
代码最后,有一个静态代码块,是以饿汉式的方式对
INSTANCE进行了赋值。那么我们并没有添加
readResolve方法,序列化为什么没有破坏枚举式单例呢?还是点进去
readObject()方法一探究竟,同上这里只贴关键部分:
switch (tc) { ... case TC_ENUM: return checkResolve(readEnum(unshared)); case TC_OBJECT: return checkResolve(readOrdinaryObject(unshared)); ... }
这里根据类型判断,进了
readEnum()方法,继续点进去:
private Enum<?> readEnum(boolean unshared) throws IOException { ... String name = readString(false); Enum<?> result = null; Class<?> cl = desc.forClass(); if (cl != null) { try { @SuppressWarnings("unchecked") Enum<?> en = Enum.valueOf((Class)cl, name); result = en; } catch (IllegalArgumentException ex) { throw (IOException) new InvalidObjectException( "enum constant " + name + " does not exist in " + cl).initCause(ex); } ... } ... return result; }
关键的一行代码:
Enum<?> en = Enum.valueOf((Class)cl, name);点进去就到了
Enum这里:
public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) { T result = enumType.enumConstantDirectory().get(name); if (result != null) return result; if (name == null) throw new NullPointerException("Name is null"); throw new IllegalArgumentException( "No enum constant " + enumType.getCanonicalName() + "." + name); }
看这一句
enumType.enumConstantDirectory().get(name)猜测
enumConstantDirectory()返回的应该是一个
Map类的东西,点进去看验证了猜想是对的,这里到达了
Class类了。
Map<String, T> enumConstantDirectory() { if (enumConstantDirectory == null) { T[] universe = getEnumConstantsShared(); if (universe == null) throw new IllegalArgumentException( getName() + " is not an enum type"); Map<String, T> m = new HashMap<>(2 * universe.length); for (T constant : universe) m.put(((Enum<?>)constant).name(), constant); enumConstantDirectory = m; } return enumConstantDirectory; }
enumConstantDirectory()方法返回了一个枚举名称和枚举常量的映射,在返回的这个
Map中我们能根据枚举名称获取到对应的枚举常量实例。
在上面的方法中,可以看到是先获取到枚举实例数组,然后遍历放入这个
Map中的。这个数组是调用了
getEnumConstantsShared()获取到的:
T[] getEnumConstantsShared() { if (enumConstants == null) { if (!isEnum()) return null; try { final Method values = getMethod("values"); ... T[] temporaryConstants = (T[])values.invoke(null); enumConstants = temporaryConstants; } ... } return enumConstants; }
在这个方法中,先获取了一个名为
values的方法,之后再调用该方法,将方法的返回值
return回去。
这里的这个
values方法其实就是我们反编译枚举类后看到的那个
values方法:
public static EnumSingleton[] values() { return (EnumSingleton[])$VALUES.clone(); } public static final EnumSingleton INSTANCE; private static final EnumSingleton $VALUES[]; static { INSTANCE = new EnumSingleton("INSTANCE", 0); $VALUES = (new EnumSingleton[] { INSTANCE }); }
values方法返回的,正是保存着在
static代码块中已经实例化好的枚举实例的
$VALUES数组,至此谜团终于揭开。
那么回到前一步的
readEnum()方法,调用的
Enum.valueOf()正是通过枚举的
Class对象和
枚举名称找到唯一的枚举实例。因此,序列化时枚举对象不会被类加载器加载多次。所以,枚举也是一种注册式单例。
到这里已经证实了,序列化不会破坏枚举的单例特性。那么在
Java中无所不能的反射能否攻破枚举式单例呢?
继续上代码测试:
try { Class clazz = EnumSingleton.class; // 获取私有构造方法 Constructor constructor = clazz.getDeclaredConstructor(null); // 开启暴力访问,强吻,不愿意也要上 constructor.setAccessible(true); EnumSingleton enumSingleton = (EnumSingleton) constructor.newInstance(); } catch (Exception e) { e.printStackTrace(); }
执行直接报错了:
java.lang.NoSuchMethodException: com.guitu18.singleton.EnumSingleton.<init>() at java.lang.Class.getConstructor0(Class.java:3082) at java.lang.Class.getDeclaredConstructor(Class.java:2178)
找不到这样一个构造方法,通过前面的反编译我们也看到了,没有无参构造方法,只有一个两参数的构造方法:
private EnumSingleton(String s, int i) { super(s, i); }
这里也是直接调用了父类的构造方法,那么我们就获取这个带参构造方法:
try { Class clazz = EnumSingleton.class; // 获取私有构造方法 Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class); // 开启暴力访问,强吻,不愿意也要上 constructor.setAccessible(true); EnumSingleton enumSingleton = (EnumSingleton) constructor.newInstance("INSTANCE", 0); } catch (Exception e) { e.printStackTrace(); }
执行还是报错,不能通过反射创建枚举:
java.lang.IllegalArgumentException: Cannot reflectively create enum objects at java.lang.reflect.Constructor.newInstance(Constructor.java:417) at com.guitu18.singleton.EnumSingletonTest.main(EnumSingletonTest.java:52)
通过打断点发现,我们能拿到这样一个构造方法,但是在调用
newInstance()时报错,哪里有错点哪里,继续点进去
newInstance方法:
public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (!override) { if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class<?> caller = Reflection.getCallerClass(); checkAccess(caller, clazz, null, modifiers); } } if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects"); ConstructorAccessor ca = constructorAccessor; // read volatile if (ca == null) { ca = acquireConstructorAccessor(); } @SuppressWarnings("unchecked") T inst = (T) ca.newInstance(initargs); return inst; }
真相见分晓,
newInstance执行时会检查类型,如果是枚举类型,在实例化时直接抛出异常。
至此,也证实了“枚举现如今已成为实现Singleton的最佳方法”这句话,在《Effective Java》一书中也推荐使用枚举实现单例。
这一切的一切都是源于
JDK枚举的语法特殊性,还有那个在
Java中无所不能的反射也在为枚举保驾护航,使得枚举式单例成为单例模式的一种最优雅的实现。
七、ThreadLocal 线程单例
ThreadLocal线程单例其实应该也算是注册式单例的一种,它是利用
ThreadLocal的特点,保证在同一个线程内一个对象是单例的。
精简版代码实现:
public class ThreadLocalSingleton { private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance = new ThreadLocal<ThreadLocalSingleton>() { @Override protected ThreadLocalSingleton initialValue() { return new ThreadLocalSingleton(); } }; private ThreadLocalSingleton() { } public static ThreadLocalSingleton getInstance() { return threadLocalInstance.get(); } }
代码比较简单,直接上测试:
public static void main(String[] args) { String tName = Thread.currentThread().getName(); System.out.println(tName + " : " + ThreadLocalSingleton.getInstance()); System.out.println(tName + " : " + ThreadLocalSingleton.getInstance()); System.out.println(tName + " : " + ThreadLocalSingleton.getInstance()); System.out.println(tName + " : " + ThreadLocalSingleton.getInstance()); System.out.println(tName + " : " + ThreadLocalSingleton.getInstance()); for (int i = 0; i < 5; i++) { Thread thread = new Thread(new Runnable() { @Override public void run() { String tName = Thread.currentThread().getName(); System.out.println(tName + ":" + ThreadLocalSingleton.getInstance()); } }); thread.start(); } }
控制台输出:
main : com.guitu18.singleton.ThreadLocalSingleton@4554617c main : com.guitu18.singleton.ThreadLocalSingleton@4554617c main : com.guitu18.singleton.ThreadLocalSingleton@4554617c main : com.guitu18.singleton.ThreadLocalSingleton@4554617c main : com.guitu18.singleton.ThreadLocalSingleton@4554617c Thread-0:com.guitu18.singleton.ThreadLocalSingleton@50e1d3f3 Thread-2:com.guitu18.singleton.ThreadLocalSingleton@6a456558 Thread-1:com.guitu18.singleton.ThreadLocalSingleton@4e8a6c13 Thread-4:com.guitu18.singleton.ThreadLocalSingleton@13d8848c Thread-3:com.guitu18.singleton.ThreadLocalSingleton@283323cb
可以看到,在同一个线程中,获取的都是用一个对象,在不通的线程中,获取的是不同的对象。
这种模式在一些特定的场景有着特殊的作用,比如数据库连接池,多数据源切换等。它保证了在同一个线程内所获得的都是同一个连接。
单例模式分析完毕,如有不完善的地方还望大家指出,互勉。
Java之路很长,还要继续努力。
- PureMVC学习系列-从源码深度剖析PureMVC(从PureMVC中看设计模式-Facade门面模式)
- 从源码中学习设计模式系列——单例模式序/反序列化以及反射攻击的问题(二)
- PureMVC学习系列-从源码深度剖析PureMVC(从PureMVC中看设计模式-Command命令模式) .
- java设计模式学习系列之六:Observer, 观察者模式---小例
- 设计模式学习系列之UML图(行为型模式 上)
- 设计模式学习系列十六:命令模式(Command)
- 递归算法学习系列、Nhibernate学习系列、扩展GridView系列、设计模式系列、wse系列
- Android设计模式系列(2)--SDK源码之观察者模式
- 设计模式学习系列十二:观察者模式(Observer)
- Android设计模式系列(12)--SDK源码之生成器模式(建造者模式)
- java设计模式学习系列之一:单例模式(Singleton) 收藏
- 步步为营 .NET 设计模式学习笔记系列总结
- Android设计模式系列(4)--SDK源码之模板方法模式
- 设计模式学习系列七:装饰模式(Decorator)
- WPF学习系列 MVVM设计模式 一
- Android设计模式学习系列
- 设计模式学习系列五:适配器模式(Adapter)
- cppunit源码剖析—学习设计模式
- 设计模式学习系列之UML图(结构型模式)
- 设计模式之简单工厂模式(“从头开始学习设计模式”系列)