观察者模式
有时被称作发布/订阅模式,观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
场景
将一个系统分割成一个一些类相互协作的类有一个不好的副作用,那就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便。观察者就是解决这类的耦合关系的。
-
当一个抽象模型有两个方面, 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
-
当对一个对象的改变需要同时改变其它对象, 而不知道具体有多少对象有待改变。
-
当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之, 你不希望这些对象是紧密耦合的。
模式中的角色
-
抽象被观察者角色(Subject/Watched):把所有对观察者对象的引用保存在一个集合中,每个被观察者角色都可以有任意数量的观察者。被观察者提供一个接口,可以增加和删除观察者角色。一般用一个抽象类和接口来实现。
-
抽象观察者角色(Observer/Watcher):为所有具体的观察者定义一个接口,在得到主题的通知时更新自己。
-
具体被观察者角色(ConcreteSubject):在被观察者内部状态改变时,给所有登记过的观察者发出通知。具体被观察者角色通常用一个子类实现。
-
具体观察者角色(ConcreteObserver):该角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。通常用一个子类实现。如果需要,具体观察者角色可以保存一个指向具体主题角色的引用。
代码实现
抽象的被观察者
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;
}
//....
}