Loading

观察者模式

观察者模式

简介

​ 观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。

优点:

  1. 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。
  2. 目标与观察者之间建立了一套触发机制。

缺点:

  1. 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
  2. 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。

模式的结构与实现

​ 实现观察者模式时要注意具体目标对象和具体观察者对象之间不能直接调用,否则将使两者之间紧密耦合起来,这违反了面向对象的设计原则。

观察者模式的主要角色如下:

  1. 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
  2. 具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
  3. 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
  4. 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。

没有观察者的日子

设想一个业务场景,在一个战斗游戏中,角色有HP,HP在多个位置显示,比如属性面板,属性球,角色上分状态条。

class Role {
    private String name;
    private int hp;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getHp() {
        return hp;
    }

    public void setHp(int hp) {
        this.hp = hp;
    }
}

class Monster {
    public void attack(Role role) {
        System.out.println(role.getName() + "受到10点伤害");
        role.setHp(role.getHp() - 10);
        System.out.println("属性面板:" + role.getHp());
        System.out.println("属性球:" + role.getHp());
        System.out.println("状态条:" + role.getHp());
    }

}

public class Problem {
    public static void main(String[] args) {
        Role role = new Role();
        role.setName("张三");
        role.setHp(100);

        Monster monster = new Monster();
        monster.attack(role);
    }
}

缺陷:

  1. 此时,业务的变化来了,在游戏中,多了一个组件来显示角色的状态(姓名+HP),此时,如果要达到之前一处变其他都变的效果的话,就只能在以前的代码中添加新的代码,这样违反了开闭原则
  2. 同时,上述的代码也违反了单一职责原则,所有状态组件的信息都在一个方法中,如果此时需要重新绘制某一个组件的形状,那么就又要修改以前的代码。

上面的问题,可以用观察者模式来解决。

观察者模式

推导1

将各个组件独立出去,一个组件变为一个类,且实现一个他们共有的接口,用来展示状态的变化。

在subject(Role)中添加一个集合,用来存放观察者(各种状态栏),当subject中的hp变化以后,就向集合中的观察者发起通知,告知他们也变化自己的状态属性。

class Role {
    private String name;
    private int hp;
    // 用来存放组件的观察者
    private List<Observer> list = new ArrayList();

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getHp() {
        return hp;
    }

    public void setHp(int hp) {
        this.hp = hp;
        // hp每变化一次,就要通知list集合中的所有组件
        notifyObserve();
    }

    public void addObserver(Observer observer) {
        list.add(observer);
    }

    public void removeObserver(Observer observer) {
        list.add(observer);
    }

    public void notifyObserve() {
        for (Observer observer : list) {
            observer.updateHp(hp);
        }
    }
}

class Monster {
    public void attack(Role role) {
        System.out.println(role.getName() + "受到10点伤害");
        role.setHp(role.getHp() - 10);


    }

}

interface Observer {
    // 接受主体,传来的新数据
    public void updateHp(int hp);
}

class HeadPanel implements Observer {
    @Override
    public void updateHp(int hp) {
        System.out.println("属性面板:" + hp);
    }
}

class BallPanel implements Observer {
    @Override
    public void updateHp(int hp) {
        System.out.println("属性球:" + hp);
    }
}

class Panel implements Observer {
    @Override
    public void updateHp(int hp) {
        System.out.println("状态条:" + hp);
    }
}

// =============添加新面板========
class ReactPanel implements Observer{
    @Override
    public void updateHp(int hp) {
        System.out.println("矩形面板:"+hp);
    }
}


public class ObserverPattern {
    public static void main(String[] args) {
        // 三个观察者
        Panel panel = new Panel();
        HeadPanel headPanel = new HeadPanel();
        BallPanel ballPanel = new BallPanel();
        // 需求中添加的新面板
        ReactPanel reactPanel=new ReactPanel();

        // 主体
        Role role = new Role();
        role.setName("张三");
        role.setHp(100);

        role.addObserver(panel);
        role.addObserver(headPanel);
        role.addObserver(ballPanel);
        role.addObserver(reactPanel);

        Monster monster = new Monster();
        monster.attack(role);
    }
}

优点:

  1. 当我们添加一个新的面板要显示数据时,就不会违反开闭原则。
  2. 因为每个面板都被隔离到了不同的类中,也就符合了单一职责。

缺点:

  1. 目前只会把hp广播给所有的观察者,但有多个状态也想广播呢,势必就违反了开闭原则。
  2. 在游戏业务中,会出现更多的玩法,Role属性会越来越多,难道每次添加一个属性,都要修改update方法吗?

推导2

为了解决上述问题,我们可以将观察者中update的参数,修改为主体,每次变化时,subject就将自己的全部属性传给观察者

public void notifyObserve() {
    for (Observer observer : list) {
        observer.updateHp(this);
    }
}

interface Observer {
    // 接受主体,传来的新数据
    public void update(Role role);
}

优点:

  1. 目前每当主体中的状态发生变化时,都会将主体的整个对象全部广播给所有观察者,就算主体有多个属性也不会影响原来的代码。

缺陷:

  • 在Observer接口中,居然出现了具体的类名!这势必就违反了依赖倒置原则,抽象不能依赖于具体,只能依赖于抽象。(JavaBean除外)

推导3

解决抽象依赖于具体的问题,不通过依赖得到主体的全部状态

interface Observer {
    // 接受主体,传来的新数据
    public void update();
}

public void notifyObserve() {
    for (Observer observer : list) {
        observer.update();
    }
}

class HeadPanel implements Observer {
    private Role role;

    public HeadPanel(Role role) {
        this.role = role;
    }

    @Override
    public void update() {
        System.out.println("属性面板:" + role.getHp());
    }
}

public static void main(String[] args) {
    // 主体
    Role role = new Role();
    role.setName("张三");
    role.setHp(100);

    // 三个观察者
    Panel panel = new Panel(role);
    HeadPanel headPanel = new HeadPanel(role);
    BallPanel ballPanel = new BallPanel(role);
    // 需求中添加的新面板
    ReactPanel reactPanel = new ReactPanel(role);

    role.addObserver(panel);
    role.addObserver(headPanel);
    role.addObserver(ballPanel);
    role.addObserver(reactPanel);

    Monster monster = new Monster();
    monster.attack(role);
}

推导4

最终改进,在所有的能用到观察者模式的业务场景中,主体中一定会有add,remove,notify三个方法,这是毋庸置疑的,所有我们可以将这三个方法抽象出来,变为一个抽象类。

interface Subject {
    void addObserver(Observer observer);

    void removeObserver(Observer observer);

    void notifyObservers();
}

class Role implements Subject {
    private String name;
    private int hp;
    // 用来存放组件的观察者
    private List<Observer> list = new ArrayList();

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getHp() {
        return hp;
    }

    public void setHp(int hp) {
        this.hp = hp;
        // hp每变化一次,就要通知list集合中的所有组件
        notifyObservers();
    }

    public void addObserver(Observer observer) {
        list.add(observer);
    }

    public void removeObserver(Observer observer) {
        list.add(observer);
    }

    @Override
    public void notifyObservers() {
        for (Observer observer : list) {
            observer.update();
        }
    }
}

class Monster {
    public void attack(Role role) {
        System.out.println(role.getName() + "受到10点伤害");
        role.setHp(role.getHp() - 10);
    }

}

interface Observer {
    // 接受主体,传来的新数据
    public void update();
}

class HeadPanel implements Observer {
    private Role role;

    public HeadPanel(Role role) {
        this.role = role;
    }

    @Override
    public void update() {
        System.out.println("属性面板:" + role.getHp());
    }
}

class BallPanel implements Observer {
    private Role role;

    public BallPanel(Role role) {
        this.role = role;
    }

    @Override
    public void update() {
        System.out.println("属性球:" + role.getHp());
    }
}

class Panel implements Observer {

    private Role role;

    public Panel(Role role) {
        this.role = role;
    }

    @Override
    public void update() {
        System.out.println("状态条:" + role.getHp());
    }
}

// =============添加新面板========
class ReactPanel implements Observer {

    private Role role;

    public ReactPanel(Role role) {
        this.role = role;
    }

    @Override
    public void update() {
        System.out.println("矩形面板:" + role.getHp());
    }
}


public class ObserverPattern {
    public static void main(String[] args) {
        // 主体
        Role role = new Role();
        role.setName("张三");
        role.setHp(100);

        // 三个观察者
        Panel panel = new Panel(role);
        HeadPanel headPanel = new HeadPanel(role);
        BallPanel ballPanel = new BallPanel(role);
        // 需求中添加的新面板
        ReactPanel reactPanel = new ReactPanel(role);

        role.addObserver(panel);
        role.addObserver(headPanel);
        role.addObserver(ballPanel);
        role.addObserver(reactPanel);

        Monster monster = new Monster();
        monster.attack(role);
    }
}

观察者模式UML图

image-20210321154822342

posted @ 2021-03-21 16:02  nuoxin  阅读(110)  评论(0)    收藏  举报