`
nathan09
  • 浏览: 155324 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论

【读书笔记】HeadFirst设计模式——单件不简单:详述实现Singleton模式需要考虑的方方面面

 
阅读更多

什么是单件?

单件就是保证一个类仅有一个实例,并提供一个访问它的全局访问点。——GOF

单件模式简单吗?

简单,的确简单,因为只有一个类。

单件不简单!

其实单件并不见得简单,而且还有点小复杂。其复杂度正是为了保证单件所要达到“仅有一个实例”的宏伟目标而引起的。

当然在一般情况下,单件是简单的。但是在考虑了懒加载、并发、反射、序列化、子类化等诸多因素后,为了保证只有一个实例,复杂度就大大提高了。

下面就从这些方面一一来看如何保证单件只有一个实例,然后看看是不是并不像想象的简单。


1.So easy!饿汉

很简单,直接访问静态域,为防修改,定义成final的。当然构造函数必须是私有的。

/**
 * 最简单的单件实现,直接访问静态域
 * 
 * @author nathan
 * 
 */
public class SimpleSingleton {
	public final static SimpleSingleton INSTANCE = new SimpleSingleton();

	private SimpleSingleton() {

	}

	public void doSomething() {
		System.out.println("SimpleSingleton.doSomething");
	}
}

2.1的变体

通过静态方法访问

/**
 * 最简单的单件实现,访问静态方法
 * 
 * @author nathan
 * 
 */
public class SimpleSingleton2 {
	private final static SimpleSingleton2 INSTANCE = new SimpleSingleton2();

	private SimpleSingleton2() {

	}

	public void doSomething() {
		System.out.println("SimpleSingleton2.doSomething");
	}

	public static SimpleSingleton2 getInstance() {
		return INSTANCE;
	}
}

3.复杂度+1:反反射调用私有构造函数创建实例。

实现方式,在构造函数中判断是否为空,否则抛出异常

/**
 * 反反射调用构造函数的单件实现
 * 
 * @author nathan
 * 
 */
public class AntiRefSingleton {

	public static AntiRefSingleton instance = new AntiRefSingleton();

	private AntiRefSingleton() {
		if (instance != null) {
			throw new RuntimeException(
					"This is a Singleton Class, please use AntiRefSingleton.instance to get the Object!");
		}
	}

	public void doSomething() {
		System.out.println("SimpleSingleton.doSomething");
	}

}


3.复杂度+2:懒汉

为防止加载没用的加载比较耗时的单件

/**
 * 懒加载的单件实现,但有并发问题
 * 
 * @author nathan
 * 
 */
public class LazySingleton {
	private static LazySingleton instance = null;

	private LazySingleton() {
		if (instance != null) {
			throw new RuntimeException(
					"This is a Singleton Class, please use the getInstance function to get the Object!");
		}
	}

	public void doSomething() {
		System.out.println("LazySingleton.doSomething");
	}

	public static LazySingleton getInstance() {
		if (instance == null) {
			instance = new LazySingleton();
		}
		return instance;
	}
}

4.复杂度+3:并发控制

懒汉做事总是不靠谱,必须要有额外的机制保证线程安全——DCL(双重检查加锁)

/**
 * 使用DCL技术实现的并发安全的懒加载单件实现
 * 
 * @author nathan
 * 
 */
public class ConcurrentSingleton {
	/**
	 * 必须声明为volatile的
	 */
	private static volatile ConcurrentSingleton instance = null;

	private ConcurrentSingleton() {
		if (instance != null) {
			throw new RuntimeException(
					"This is a Singleton Class, please use the getInstance function to get the Object!");
		}
	}

	public void doSomething() {
		System.out.println("ConcurrentSingleton.doSomething");
	}

	public static ConcurrentSingleton getInstance() {
		if (instance == null) {// 使用双重检查锁定技术
			synchronized (ConcurrentSingleton.class) {
				if (instance == null) {
					instance = new ConcurrentSingleton();
				}
			}
		}
		return instance;
	}
}

5.懒汉变体

使用静态内部类,也是线程安全的

/**
 * 使用静态内部类实现懒汉单例,而且是线程安全的
 * 
 * @author nathan
 * 
 */
public class HolderSingleton {
	private static class SingletonHolder {
		private static final HolderSingleton INSTANCE = new HolderSingleton();
	}

	public static HolderSingleton getInstance() {
		return SingletonHolder.INSTANCE;
	}
}

6.复杂度+4:接下来考虑序列化

《Effective Java》中作者已经给出了方案,即添加readResolve方法。如下:

/**
 * 可序列化的单件实现(同时是并发安全的懒加载的),但只能在同一个jvm中使用,不能跨jvm
 * 
 * @author nathan
 * 
 */
public class SerializableSingleton implements Serializable {
	private static final long serialVersionUID = 5691590550973506283L;
	private transient String description;

	public void doSomething() {
		description = "SerializableSingleton";
	}

	@Override
	public String toString() {
		return super.toString() + " [description=" + description + "]";
	}

	/**
	 * 必须声明为volatile的
	 */
	private static volatile transient SerializableSingleton instance = null;

	private SerializableSingleton() {
		if (instance != null) {
			throw new RuntimeException(
					"This is a Singleton Class, please use the getInstance function to get the Object!");
		}
	}

	public static SerializableSingleton getInstance() {
		if (instance == null) {// 使用双重检查锁定技术
			synchronized (SerializableSingleton.class) {
				if (instance == null) {
					instance = new SerializableSingleton();
				}
			}
		}
		return instance;
	}

	private Object readResolve() {
		// 抛弃反序列化的实例,返回原实例
		return instance;
	}
}

7.复杂度+5:跨jvm序列化

第6中方案作者给出了解决问题的思路,但未真正解决序列化问题。因为它只能在同一个jvm中适应。但是在同一个jvm中序列化单例似乎意义不大。下面是kuajvm的单例实现方式。简单修改6中的readResolve方法即可。

/**
 * 能跨jvm使用的可序列化的单件实现(同时是并发安全、懒加载的)
 * 
 * @author nathan
 * 
 */
public class SerializableSingleton2 implements Serializable {
	private static final long serialVersionUID = 5691590550973506283L;
	private String description;
	private int count;

	public void doSomething() {
		description = "SerializableSingleton2";
		count = 100;
	}

	@Override
	public String toString() {
		return super.toString() + " [description=" + description + ",count="
				+ count + "]";
	}

	public void setCount(int count) {
		this.count = count;
	}

	public int getCount() {
		return count;
	}

	/**
	 * 必须声明为volatile的
	 */
	private static volatile SerializableSingleton2 instance = null;

	private SerializableSingleton2() {
		if (instance != null) {
			throw new RuntimeException(
					"This is a Singleton Class, please use the getInstance function to get the Object!");
		}
	}

	public static SerializableSingleton2 getInstance() {
		if (instance == null) {// 使用双重检查锁定技术
			synchronized (SerializableSingleton2.class) {
				if (instance == null) {
					instance = new SerializableSingleton2();
				}
			}
		}
		return instance;
	}

	private Object readResolve() {
		if (instance == null) {// 使用双重检查锁定技术
			synchronized (SerializableSingleton2.class) {
				if (instance == null) {
					instance = this;// 如果是第一次反序列化,则使用该实例,否则不管它
				}
			}
		}
		return instance;
	}

	public static void serialize(String file) throws Exception {
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(
				new File(file)));
		oos.writeObject(instance);
	}

	public static void antiSerialize(String file) throws Exception {
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
				new File(file)));
		instance = (SerializableSingleton2) ois.readObject();
	}
}

8.复杂度+6:考虑单件的继承

在GOF的《设计模式》中给出了方案,即采用register的方式。但按其书中描述,子类的构造函数必须是公有的,这就违背了单例的初衷。因此必须借助“反射”机制实现对子类的实例化。

9.复杂度+7:再考虑基于继承的单件体系的跨jvm的序列化

(8和9合后的并代码如下)

/**
 * 一个可子类化、可序列化的单件实现
 * 
 * @author nathan
 * 
 */
public class SubableSingleton implements Serializable {
	private String description;
	private int count;

	public void doSomething() {
		description = "SubableSingleton";
		count = 100;
	}

	@Override
	public String toString() {
		return super.toString() + " [description=" + description + ",count="
				+ count + "]";
	}

	public void setCount(int count) {
		this.count = count;
	}

	public int getCount() {
		return count;
	}

	// 以下代码实现单件支持

	private static final long serialVersionUID = 5713856529741473199L;
	private static SingletonHolder holder = null;
	private String name;

	protected SubableSingleton() {
		this(SubableSingleton.class);
	}

	protected SubableSingleton(Class<? extends SubableSingleton> clazz) {
		if (holder.lookup(clazz.getName()) != null) {
			throw new RuntimeException(
					"This is a Singleton Class, please use getInstance function to get the Object!");

		}
		name = clazz.getName();
	}

	/**
	 * 注意:使用synchronized代替DCL进行简单并发控制
	 * 
	 * @param clazz
	 * @return
	 */
	public static synchronized SubableSingleton getInstance(
			Class<? extends SubableSingleton> clazz) {
		if (holder == null) {
			holder = new SingletonHolder();
		}

		SubableSingleton instance = holder.lookup(clazz.getName());

		if (instance == null) {
			try {
				Constructor<? extends SubableSingleton> constructor = clazz
						.getDeclaredConstructor();
				constructor.setAccessible(true);
				instance = constructor.newInstance();
				holder.register(clazz.getName(), instance);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		return instance;
	}

	private synchronized Object readResolve() {
		System.out.println("SubableSingleton.readResolve");
		if (holder == null) {
			holder = new SingletonHolder();
			holder.register(this.name, this);
		}
		return holder.lookup(this.name);
	}

	public static void serialize(String file) throws Exception {
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(
				new File(file)));
		oos.writeObject(holder);
	}

	public static void antiSerialize(String file) throws Exception {
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
				new File(file)));
		holder = (SingletonHolder) ois.readObject();
	}

	/**
	 * 单例持有类,私有
	 * 
	 * @author nathan
	 * 
	 */
	private static class SingletonHolder implements Serializable {
		private static final long serialVersionUID = -4221190210772287103L;

		private Map<String, SubableSingleton> registry = new HashMap<String, SubableSingleton>();

		public void register(String name, SubableSingleton subableSingleton) {
			registry.put(name, subableSingleton);
		}

		public SubableSingleton lookup(String name) {
			return registry.get(name);
		}

		private synchronized Object readResolve() {
			System.out.println("SingletonHolder.readResolve");
			// 抛弃反序列化的实例,返回原实例
			if (holder == null) {
				holder = this;
			}
			return holder;
		}
	}
}
/**
* 子类必须在构造函数中调用父类的带参构造函数,完成反反射控制
* @author nathan
*
*/
public class SubSingleton extends SubableSingleton {

	private static final long serialVersionUID = 2430773476223417288L;

	protected SubSingleton() {
		super(SubSingleton.class);
	}
}

那么,你还认为单件简单吗?欢迎交流!

参考:

GOF的《设计模式》

《Effective Java》

《单件模式的7种写法》http://www.360doc.com/content/10/1213/09/2703996_77599342.shtml


附:相关单元测试

public class SingletonTest {
	@Test
	public void testSimpleSingleton() {
		Assert.assertEquals(SimpleSingleton.INSTANCE, SimpleSingleton.INSTANCE);
	}

	@Test
	public void testSimpleSingleton2() {
		Assert.assertEquals(SimpleSingleton2.getInstance(),
				SimpleSingleton2.getInstance());
	}

	@Test
	public void testAntiRefSingleton() throws Exception {
		Assert.assertEquals(AntiRefSingleton.instance,
				AntiRefSingleton.instance);

		try {
			Constructor<AntiRefSingleton> constructor = AntiRefSingleton.class
					.getDeclaredConstructor();
			constructor.setAccessible(true);
			constructor.newInstance();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Test
	public void testLazySingleton() {
		Assert.assertEquals(LazySingleton.getInstance(),
				LazySingleton.getInstance());
	}

	@Test
	public void testConcurrentSingleton() {
		Assert.assertEquals(ConcurrentSingleton.getInstance(),
				ConcurrentSingleton.getInstance());
	}

	@Test
	public void testSubableSingleton() {
		Assert.assertNotNull(SubableSingleton
				.getInstance(SubableSingleton.class));
		Assert.assertTrue(SubableSingleton.getInstance(SubableSingleton.class) instanceof SubableSingleton);
		Assert.assertEquals(
				SubableSingleton.getInstance(SubableSingleton.class),
				SubableSingleton.getInstance(SubableSingleton.class));

		Assert.assertNotNull(SubableSingleton.getInstance(SubSingleton.class));
		Assert.assertTrue(SubableSingleton.getInstance(SubSingleton.class) instanceof SubSingleton);
		Assert.assertEquals(SubableSingleton.getInstance(SubSingleton.class),
				SubableSingleton.getInstance(SubSingleton.class));
	}

	@Test
	public void testSubableSingletonSerialize() throws Exception {
		SubableSingleton instance = SubableSingleton
				.getInstance(SubableSingleton.class);
		SubableSingleton instance2 = SubableSingleton
				.getInstance(SubSingleton.class);
		instance.doSomething();
		instance2.doSomething();

		SubableSingleton.serialize("testSubableSingletonSerialize.jser");
		SubableSingleton.antiSerialize("testSubableSingletonSerialize.jser");

		Assert.assertEquals(instance,
				SubableSingleton.getInstance(SubableSingleton.class));
		Assert.assertEquals(instance2,
				SubableSingleton.getInstance(SubSingleton.class));
	}

	@Test
	public void testSubableSingletonSerialize2() throws Exception {

		SubableSingleton.antiSerialize("testSubableSingletonSerialize.jser");

		SubableSingleton instance = SubableSingleton
				.getInstance(SubableSingleton.class);
		SubableSingleton instance2 = SubableSingleton
				.getInstance(SubSingleton.class);
		Assert.assertEquals(100, instance.getCount());
		Assert.assertEquals(100, instance2.getCount());

		instance.setCount(20);

		SubableSingleton.antiSerialize("testSubableSingletonSerialize.jser");
		instance = SubableSingleton.getInstance(SubableSingleton.class);
		Assert.assertEquals(20, instance.getCount());
	}

	@Test
	public void testSubableSingletonSerialize3() throws Exception {
		SubableSingleton instance = SubableSingleton
				.getInstance(SubableSingleton.class);
		instance.doSomething();

		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(
				new File("testSubableSingletonSerialize.jser")));
		oos.writeObject(instance);

		ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
				new File("testSubableSingletonSerialize.jser")));
		SubableSingleton instance2 = (SubableSingleton) ois.readObject();

		Assert.assertEquals(instance, instance2);

	}

	@Test
	public void testSubableSingletonSerialize4() throws Exception {
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
				new File("testSubableSingletonSerialize.jser")));
		SubableSingleton instance2 = (SubableSingleton) ois.readObject();

		Assert.assertEquals(100, instance2.getCount());
		instance2.setCount(20);

		ois = new ObjectInputStream(new FileInputStream(new File(
				"testSubableSingletonSerialize.jser")));
		instance2 = (SubableSingleton) ois.readObject();

		Assert.assertEquals(20, instance2.getCount());
	}

	@Test
	public void testSubableSingletonSerialize5() throws Exception {
		SubableSingleton instance = SubableSingleton
				.getInstance(SubSingleton.class);
		SubableSingleton instance2 = SubableSingleton
				.getInstance(SubSingleton.class);
		instance.doSomething();
		instance2.doSomething();

		SubableSingleton.serialize("testSubableSingletonSerialize5.jser");
		SubableSingleton.antiSerialize("testSubableSingletonSerialize5.jser");

		Assert.assertEquals(instance,
				SubableSingleton.getInstance(SubSingleton.class));
		Assert.assertEquals(instance2,
				SubableSingleton.getInstance(SubSingleton.class));

		Assert.assertEquals(100, instance2.getCount());
	}

	@Test
	public void testSerializableSingletonInOneJvm() throws IOException,
			ClassNotFoundException {
		Assert.assertEquals(SerializableSingleton.getInstance(),
				SerializableSingleton.getInstance());
		SerializableSingleton singleton = SerializableSingleton.getInstance();
		singleton.doSomething();
		System.out.println(singleton);

		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		ObjectOutputStream oos = new ObjectOutputStream(baos);
		oos.writeObject(singleton);

		ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
		ObjectInputStream ois = new ObjectInputStream(bais);
		SerializableSingleton clone = (SerializableSingleton) ois.readObject();
		System.out.println(clone);
		Assert.assertEquals(singleton, clone);

		clone.doSomething();
	}

	/**
	 * 在同一个jvm中,序列化后再反序列化对单件来说是无效的,jvm中始终使用的是最初创建的那个单件实例
	 * 
	 * @throws Exception
	 */
	@Test
	public void testSerializableSingleton2InOneJvm() throws Exception {
		Assert.assertEquals(SerializableSingleton2.getInstance(),
				SerializableSingleton2.getInstance());
		SerializableSingleton2 singleton = SerializableSingleton2.getInstance();
		singleton.doSomething();
		Assert.assertEquals(SerializableSingleton2.getInstance().getCount(),
				100);

		SerializableSingleton2.serialize("SerializableSingleton2.jser");
		// 序列化后改变实例数据
		SerializableSingleton2.getInstance().setCount(30);
		// 反序列化,实际上并未使用反序列化出来的实例,而是继续使用原来的实例,因为在同一个Jvm中
		SerializableSingleton2.antiSerialize("SerializableSingleton2.jser");
		// 因此这里的值是30,不是序列化时候的100
		Assert.assertEquals(SerializableSingleton2.getInstance().getCount(), 30);

		// 改变实例数据
		SerializableSingleton2.getInstance().setCount(20);

		// 再反序列化
		SerializableSingleton2.antiSerialize("SerializableSingleton2.jser");
		Assert.assertEquals(SerializableSingleton2.getInstance().getCount(), 20);
	}

	/**
	 * 在另一个jvm中启动反序列化<br>
	 * 注意:请先执行测试testSerializableSingleton2InOneJvm,再执行该测试
	 * 
	 * @throws Exception
	 */
	@Test
	public void testSerializableSingleton2NotInOneJvmRead() throws Exception {
		// 反序列化,并创建单件实例,此后在该jvm中将一直使用该实例
		SerializableSingleton2.antiSerialize("SerializableSingleton2.jser");
		// 因此这里的值是序列化时候的100
		Assert.assertEquals(SerializableSingleton2.getInstance().getCount(),
				100);

		// 改变实例数据
		SerializableSingleton2.getInstance().setCount(20);

		// 再反序列化
		SerializableSingleton2.antiSerialize("SerializableSingleton2.jser");
		Assert.assertEquals(SerializableSingleton2.getInstance().getCount(), 20);
	}

}


分享到:
评论

相关推荐

    [创建型模式] head first 设计模式之单件模式(Singleton)

    Head First 设计模式是一本非常受欢迎的设计模式书籍,通过直观易懂的方式讲解了23种设计模式,包括单例模式在内的创建型、结构型和行为型模式。阅读这本书籍可以帮助开发者更好地理解和运用设计模式,提高代码质量...

    Head First 设计模式 (五) 单件模式(Singleton pattern) C++实现

    单件模式(Singleton pattern)是设计模式中的一种结构型模式,它的主要目的是确保一个类只有一个实例,并提供一个全局访问点。这种模式常用于系统中需要频繁创建和销毁的对象,如日志服务、线程池或者数据库连接等...

    HeadFirst设计模式PPT

    《HeadFirst设计模式》是一本深受开发者欢迎的书籍,它以独特、易理解的方式介绍了软件设计中的重要概念——设计模式。设计模式是经验丰富的开发者在解决常见问题时总结出的最佳实践,它们为软件设计提供了可复用的...

    head first 设计模式 高清完整版 pdf

    《Head First设计模式》是一本深受开发者喜爱的经典书籍,它以独特、生动的方式讲解了设计模式这一核心的软件工程概念。设计模式是经验丰富的开发者在解决常见问题时总结出的最佳实践,它们为软件设计提供了可复用的...

    HeadFirst设计模式JAVA版源码

    《HeadFirst设计模式JAVA版源码》是一份深入学习设计模式的重要资源,它基于流行的编程语言Java,旨在帮助开发者理解并应用设计模式于实际项目中。设计模式是软件工程中的重要概念,它代表了在特定场景下解决问题的...

    Head First设计模式 源代码

    《Head First设计模式》是一本深受开发者喜爱的设计模式学习书籍,它以易懂、生动的方式讲解了23种经典的设计模式。源代码是书中理论知识的实践体现,可以帮助读者更深入地理解并应用这些模式。这里我们将围绕这些...

    Head First设计模式和HeadFirst in java 源码以及23种设计模式关系图

    总的来说,这个压缩包包含的资源可以帮助你深入理解设计模式,通过《HeadFirst设计模式》和《HeadFirst in Java》的源码,你可以学习到如何在实际项目中应用这些模式。而UML类图则提供了直观的视角,便于你把握设计...

    Head First 设计模式 源代码

    《Head First 设计模式》是一本非常受欢迎的软件设计书籍,它以易懂且生动的方式介绍了23种经典的GOF设计模式。这本书的源代码包含了书中所有示例的实现,对于学习和理解设计模式有着极大的帮助。源代码的下载使得...

    HeadFirst设计模式源代码

    《HeadFirst设计模式源代码》是一本面向程序员的深度学习设计模式的书籍,它通过直观易懂的方式将复杂的概念转化为生动的图像和有趣的例子,帮助读者深入理解并掌握设计模式。设计模式是软件工程中的一种最佳实践,...

    headfirst设计模式

    《Head First设计模式》是一本深受开发者欢迎的设计模式教程,以其独特的视觉呈现方式和易于理解的语言,让初学者也能快速掌握设计模式的核心概念。这本书深入浅出地介绍了23种GOF(GoF,Gamma、Erich、Johnson、...

    head first 设计模式 PDF电子书下载

    《Head First 设计模式》是一本深受欢迎的设计模式书籍,由Eric Freeman、Elisabeth Robson、Bert Bates和Kathy Sierra合著。这本书以其独特的视觉呈现方式和易理解的教学方法,深受程序员们的喜爱,尤其是那些希望...

    HeadFirst设计模式英文版

    这些模式包括但不限于单例模式(Singleton)、工厂方法模式(Factory Method)、抽象工厂模式(Abstract Factory)、建造者模式(Builder)、原型模式(Prototype)、适配器模式(Adapter)、装饰器模式(Decorator...

    Head First Design Patterns 英文版 Head First设计模式

    《Head First Design Patterns》是一本深入浅出介绍设计模式的图书,由Eric Freeman、Elisabeth Freeman、Bert Bates和Kathy Sierra联合编写。本书结合认知科学原理和神经生物学研究,采用引导式教学方法,通过故事...

    《Head.First设计模式》书中源代码(Java语言)

    《HeadFirst设计模式》是设计模式领域的一本经典著作,以其独特的教学方式和生动的插图深受初学者喜爱。这本书通过实例和互动式的学习方法,深入浅出地讲解了23种经典的设计模式。源代码作为理论知识的实践部分,...

    head first 设计模式 中文版带书签

    根据提供的文件信息,“Head First设计模式中文版带书签”主要涉及的是软件工程中的设计模式这一核心主题。设计模式是在软件开发过程中针对特定问题而形成的最佳实践或解决方案模板,能够帮助开发者解决常见的设计...

    Head First设计模式官方原码

    《Head First设计模式》是一本深受开发者欢迎的设计模式学习书籍,以其独特的教学方式,通过丰富的图解和幽默的语言,帮助读者深入理解设计模式的核心概念。这本书的官方源码提供了书中所讲解的每个设计模式的实际...

    head first 设计模式

    《Head First设计模式》(中文版)共有14章,每章都介绍了几个设计模式,完整地涵盖了四人组版本全部23个设计模式。前言先介绍这本书的用法;第1章到第11章陆续介绍的设计模式为Strategy、Observer、Decorator、...

    HeadFirst设计模式.rar

    《HeadFirst设计模式》这本书是软件开发领域的一本经典之作,尤其对于初学者而言,它以易懂且生动的方式介绍了设计模式这一核心概念。设计模式是面向对象编程中的一种最佳实践,是解决常见问题的经验总结,是软件...

    Head.First设计模式_PDF

    第1章到第11章陆续介绍的设计模式为Strategy、Observer、Decorator、Abstract Factory、Factory Method、Singleton、Command、Adapter、Facade、Template Method、iterator、Composite、State、proxy。最后三章比较...

Global site tag (gtag.js) - Google Analytics