设计模式之观察者模式

设计模式之观察者模式

首先假设目前有这样的一个应用场景,你正在设计一款角色对战游戏,每一个角色都有自己的一些属性,此处简化为:

  • 血量hp
  • 魔法值magicValue
  • 攻击速度speed
  • 武器装备等级attackLevel

现在的需求是开发人员需要在游戏界面的多处显示这些数据,比如角色头顶HeadPanel,屏幕左上角统计控件SummaryPanel以及右下角角色操作块OperationPanel。其中:

  • HeadPanel会显示角色的血量 以及魔法值
  • SummaryPanel会显示角色的血量魔法值攻击速度 以及武器装备等级
  • OperationPanel会显示角色的攻击速度装备等级

当角色的属性发生变化(比如被攻击掉血,或是捡到装备武器升级)的时候,这些属性的值也应该及时更新到这些控件中。


准备工作

根据上面的场景,我们可以先创建一些类出来。

public class Role {
    private Integer id;
    private String name;
    private Integer hp;
    private Integer magicValue;
    private Integer speed;
    private Integer attackLevel;

	// 省略对应的setter和getter
}

class HeadPanel{
    private Integer hp;
    private Integer magicValue;
    // 省略更多特有的属性

    // 省略对应的setter和getter
    
    public void update(hp,magicValue){
        // ...
    }
}

class SummaryPanel{
    private Integer hp;
    private Integer magicValue;
    private Integer speed;
    private Integer attackLevel;
    // 省略更多特有的属性
    
    // 省略对应的setter和getter
    
    public void update(hp,magicValue,speed,attackLevel){
        // ...
    }
}

class OperationPanel{
    private Integer speed;
    private Integer attackLevel;
    // 省略更多特有的属性

    // 省略对应的setter和getter
    
    public void update(speed,attackLevel){
        // ...
    }
}

根据场景需求分析来看,想要数据的任何更改都及时推送到指定的控件,最简单的做法是在Role中直接依赖这些空间类,然后当Role中的属性发生变化,即对应setter被调用时,直接在setter内部进行数值更改。

代码实现为:

public class Role {
    private String id;
    private String name;
    private int hp;
    private int attackLevel;
    private int speed;
    private int magicValue;

    //=======================
    private HeadPanel headPanel;
    private OperationPanel operationPanel;
    private SummaryPanel summaryPanel;

    public HeadPanel getHeadPanel() {
        return headPanel;
    }

    public void setHeadPanel(HeadPanel headPanel) {
        this.headPanel = headPanel;
    }

    public OperationPanel getOperationPanel() {
        return operationPanel;
    }

    public void setOperationPanel(OperationPanel operationPanel) {
        this.operationPanel = operationPanel;
    }

    public SummaryPanel getSummaryPanel() {
        return summaryPanel;
    }

    public void setSummaryPanel(SummaryPanel summaryPanel) {
        this.summaryPanel = summaryPanel;
    }

    //==========================

    public Role() {
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    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;
        headPanel.setHp(hp);
        summaryPanel.setHp(hp);
    }

    public int getAttackLevel() {
        return attackLevel;
    }

    public void setAttackLevel(int attackLevel) {
        this.attackLevel = attackLevel;
        headPanel.setAttackLevel(attackLevel);
        summaryPanel.setAttackLevel(attackLevel);
    }

    public int getSpeed() {
        return speed;
    }

    public void setSpeed(int speed) {
        this.speed = speed;
        operationPanel.setSpeed(speed);
        summaryPanel.setSpeed(speed);
    }

    public int getMagicValue() {
        return magicValue;
    }

    public void setMagicValue(int magicValue) {
        this.magicValue = magicValue;
        operationPanel.setMagicValue(magicValue);
        summaryPanel.setMagicValue(magicValue);
    }
}

这显然是可以实现现阶段的产品需求的。但是也充满了代码的坏味道:

  • 在Role的setter方法里进行组件值的更新,违反了单一职责原则

  • 如果角色的属性增加,比如增加角色等级level ,此时必须要更改很多地方的参数,这是典型的坏味道

  • 三个panel虽然属性不太一致,但是整体逻辑一致,代码重复率很高

  • 如果现在增加了新的组件来监控角色panel属性,则必须更改Role已有的代码,将新的组件直接作为属性写到Role中,违反了开闭原则


在未来的时间内,角色类可能会增加或是删除一些属性,panel类也可能加入或删除对一些属性的监控,因此将监控数据使用参数的形式传入update 函数并不是一个好的方式,更好的方式是:直接将整个Role实例作为参数,panel类根据自己的需求来选择属性进行展示。在有些人看来,这可能会导致程序违反了迪米特法则(最少知道原则),但是它却让整个代码在update()参数传递时具有很高的可扩展性。这是可以接受的,执行效率也不会变低。

此时update 方法在三个panel之间完全一致,可以考虑为其抽象出一个借口Updateable

interface Updateable{
    void update(Role role);
}

所有panel实现该接口,然后实现自己的update 方法。

此时可以考虑换一种数据结构来存储Role中panels,它们实现了共同的接口,则可以在Role中使用list来存储它们,这样最大的好处是:可以动态的添加或删除panel。

public class Role {
    private String id;
    private String name;
    private int hp;
    private int attackLevel;
    private int speed;
    private int magicValue;

    //=======================
    private List<Updateable> updateableList = new ArrayList<>();


    public void addPanel(Updateable panel) {
        updateableList.add(panel);
    }

    public void removePanel(Updateable panel) {
        updateableList.remove(panel);
    }

    public void updatepanels() {
        for (Updateable updateable : updateableList) {
            updateable.update(this);
        }
    }
    //==========================

    public Role() {
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    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;
        updatepanels();
    }

    public int getAttackLevel() {
        return attackLevel;
    }

    public void setAttackLevel(int attackLevel) {
        this.attackLevel = attackLevel;
        updatepanels();
    }

    public int getSpeed() {
        return speed;
    }

    public void setSpeed(int speed) {
        this.speed = speed;
        updatepanels();
    }

    public int getMagicValue() {
        return magicValue;
    }

    public void setMagicValue(int magicValue) {
        this.magicValue = magicValue;
        updatepanels();
    }
}

现在其实已经算是采用了观察者模式。在本例中,所有的panel有一个共同的特性是:观察Role的数据,如果发生变化,则更新自己的界面。

程序实现的逻辑为:Role角色一旦发现自己变化了,就给所有的观察者发数据,发的数据就是自身实例。观察者接收到实例之后,从中挑选自己关心的数据在界面上进行更新。

在两者的关系中,Role是被观察的主题SubjectPanelRole的观察者Observer。这就构成了观察者模式里面的两个主题。

使用观察者模式重新构造上面的代码:

public class Role {
    private String id;
    private String name;
    private int hp;
    private int attackLevel;
    private int speed;
    private int magicValue;

    private List<Observer> observerList = new ArrayList<>();
    
    public void registerObserver(Observer panel) {
        observerList.add(panel);
    }

    public void removeObserver(Observer panel) {
        observerList.remove(panel);
    }

    public void updatePanels() {
        for (Observer observer : observerList) {
            observer.update(this);
        }
    }

    public Role() {
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    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;
        updatePanels();
    }

    public int getAttackLevel() {
        return attackLevel;
    }

    public void setAttackLevel(int attackLevel) {
        this.attackLevel = attackLevel;
        updatePanels();
    }

    public int getSpeed() {
        return speed;
    }

    public void setSpeed(int speed) {
        this.speed = speed;
        updatePanels();
    }

    public int getMagicValue() {
        return magicValue;
    }

    public void setMagicValue(int magicValue) {
        this.magicValue = magicValue;
        updatePanels();
    }
}

此时,其实已经可以结束了。但是《Head First 设计模式》给了我们更好的一种方式,使代码的可扩展性进一步增强。代码为:

import java.util.ArrayList;
import java.util.List;

public class Role {
    private String id;
    private String name;
    private int hp;
    private int attackLevel;
    private int speed;
    private int magicValue;

    private List<Observer> observerList = new ArrayList<>();

    public void registerObserver(Observer panel) {
        observerList.add(panel);
    }

    public void removeObserver(Observer panel) {
        observerList.remove(panel);
    }

    public void updatePanels() {
        for (Observer observer : observerList) {
            observer.update();
        }
    }

    public Role() {
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    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;
        updatePanels();
    }

    public int getAttackLevel() {
        return attackLevel;
    }

    public void setAttackLevel(int attackLevel) {
        this.attackLevel = attackLevel;
        updatePanels();
    }

    public int getSpeed() {
        return speed;
    }

    public void setSpeed(int speed) {
        this.speed = speed;
        updatePanels();
    }

    public int getMagicValue() {
        return magicValue;
    }

    public void setMagicValue(int magicValue) {
        this.magicValue = magicValue;
        updatePanels();
    }
}


interface Observer {
    void update();
}

abstract class Panel implements Observer {
    private Role role;

    public Role getRole() {
        return role;
    }

    public void setRole(Role role) {
        this.role = role;
    }
}

class HeadPanel extends Panel{
    private int hp;
    private int attackLevel;

    public int getHp() {
        return hp;
    }

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

    public int getAttackLevel() {
        return attackLevel;
    }

    public void setAttackLevel(int attackLevel) {
        this.attackLevel = attackLevel;
    }

    @Override
    public void update() {
        System.out.println("更新HeadPanel");
    }
}

class OperationPanel extends Panel{
    private int speed;
    private int magicValue;

    public int getSpeed() {
        return speed;
    }

    public void setSpeed(int speed) {
        this.speed = speed;
    }

    public int getMagicValue() {
        return magicValue;
    }

    public void setMagicValue(int magicValue) {
        this.magicValue = magicValue;
    }

    @Override
    public void update() {
        System.out.println("更新OperationPanel");
    }
}

class SummaryPanel extends Panel {
    private int hp;
    private int attackLevel;
    private int speed;
    private int magicValue;

    public int getHp() {
        return hp;
    }

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

    public int getAttackLevel() {
        return attackLevel;
    }

    public void setAttackLevel(int attackLevel) {
        this.attackLevel = attackLevel;
    }

    public int getSpeed() {
        return speed;
    }

    public void setSpeed(int speed) {
        this.speed = speed;
    }

    public int getMagicValue() {
        return magicValue;
    }

    public void setMagicValue(int magicValue) {
        this.magicValue = magicValue;
    }

    @Override
    public void update() {
        System.out.println("更新SummaryPanel");
    }
}

public class Main {
    public static void main(String[] args) {
        Role role = new Role();

        Panel panel1 = new HeadPanel();
        panel1.setRole(role);

        Panel panel2 = new OperationPanel();
        panel2.setRole(role);

        Panel panel3 = new SummaryPanel();
        panel3.setRole(role);

        role.registerObserver(panel1);
        role.registerObserver(panel2);
        role.registerObserver(panel3);

        role.setHp(23);
        role.setMagicValue(12);
    }
}
//输出为:
//	更新HeadPanel
//	更新OperationPanel
//	更新SummaryPanel
//	更新HeadPanel
//	更新OperationPanel
//	更新SummaryPanel
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值