TMaize Blog

观察者模式 单例模式

观察者模式

有时被称作发布/订阅模式,观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

场景

将一个系统分割成一个一些类相互协作的类有一个不好的副作用,那就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便。观察者就是解决这类的耦合关系的。

模式中的角色

代码实现

抽象的被观察者

public interface Watched {
	
	public Watched addWatcher(Watcher watcher);

	//public void addWatcher(Watcher watcher);

	public void removeWatcher(Watcher watcher);

	public void notifyWatchers();
}

具体的被观察者

public class ConcreteWatched implements Watched {
	
	private List<Watcher> watchers = new ArrayList<>();

	@Override
	public Watched addWatcher(Watcher watcher) {
		watchers.add(watcher);
		return this;
	}

	@Override
	public void removeWatcher(Watcher watcher) {
		watchers.remove(watcher);
	}

	@Override
	public void notifyWatchers() {
		
		//采用Watcher代理具体的观察者,只对被观测者提供Watcher接口方法里面的方法
		for (Watcher watcher:watchers) {
			watcher.update();
		}
	}

}

抽象的观察者

public interface Watcher {

	public void update();
	
}

具体的的观察者

public class User implements Watcher{
	
	String name;
	
	private User() {

	}

	public User(String name) {
		this.name = name;
	}

	@Override
	public void update() {
		System.out.println(name+"收到推送...");
	}
	
}

测试类

public class Test {
	public static void main(String[] args) {
		ConcreteWatched concreteWatched = new ConcreteWatched();
		
		User u1 = new User("KangKang");
		User u2 = new User("XiaoMing");
		concreteWatched.addWatcher(u1).addWatcher(u2);
		
		concreteWatched.notifyWatchers();
		
	}
}

总结

观察者模式在于目标角色、观察者角色之间的通信,有两个版本。

一种情况便是目标角色在发生变化后,仅仅告诉观察者角色“我变化了”,观察者角色如果想要知道具体的变化细节,则就要自己从目标角色的接口中得到。这种模式被很形象的称为:拉模式——就是说变化的信息是观察者角色主动从目标角色中“拉”出来的。

还有一种方法,那就是我目标角色“服务一条龙”,通知你发生变化的同时,通过一个参数将变化的细节传递到观察者角色中去。这就是“推模式”——管你要不要,先给你啦。

这两种模式的使用,取决于系统设计时的需要。如果目标角色比较复杂,并且观察者角色进行更新时必须得到一些具体变化的信息,则“推模式”比较合适。如果目标角色比较简单,则“拉模式”就很合适。

单例模式

对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务。而且只有一个实例能够减少内存占用,还可以防止实例存在多个会引起的程序逻辑错误。

单例模式是设计模式中最简单的形式之一。这一模式的目的是使得类的一个对象成为系统中的唯一实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。并且这些应用都或多或少具有资源管理器的功能。

单例模式实现的方法较多,但是基本的就两种,懒汉模式和饿汉模式

懒汉模式

懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。

public class Singleton {
	
	private static Singleton singleton = null;
	
	//屏蔽构造方法
	private Singleton(){}
	
	//获得单例,存在线程安全问题
	public static Singleton getInstance(){
		if(singleton==null){
			singleton = new Singleton();
		}
		return singleton;
	}

	//....
}

对于上面的实现会存在线程安全问题,不过可以使用synchronized关键字来修饰getInstance(),但是又会带来性能上的影响.可以通过静态内部类来解决这个问题

public class Singleton {

	private static class LazyHolder {
		private static final Singleton INSTANCE = new Singleton();
	}

	private Singleton() {}

	public static final Singleton getInstance() {
		return LazyHolder.INSTANCE;
	}
}

饿汉模式

饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,所以饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题。但是不管之后会不会使用这个单例,都会占据一定的内存,相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成。

public class Singleton {
	
	private final static Singleton singleton = new Singleton();
	
	//屏蔽构造方法
	private Singleton(){}
	
	//获得单例
	public static Singleton getInstance(){
		return singleton;
	}

	//....
}