在日常生活中嗜憔,觀察者模式與我們息息相關(guān)木缝,就比如愛打游戲的我在操控虛擬角色時便锨,這個角色有hp和mp,在游戲中會有一些對應(yīng)的面板來展示hp和mp氨肌,這個時候其實就是利用了觀察者模式鸿秆,具體是怎么實現(xiàn)的呢?請往下看怎囚。
就拿我最先接觸到的游戲的傳奇來說卿叽,一般的,在游戲中恳守,展示人物hp和mp有多個地方
假設(shè)游戲角色被怪物只因打了一下考婴,此時三個地方需要發(fā)生變化,角色上方血條催烘,圓盤血條沥阱,和數(shù)字血條。那我們?nèi)绾卧O(shè)計呢伊群?
不考慮其他因素考杉,最簡單的設(shè)計就是當(dāng)人物掉血時,改變?nèi)齻€地方舰始,代碼如下:崇棠、
class Role {
private String name;
private Integer hp;
private Integer mp;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getHp() {
return hp;
}
public void setHp(Integer hp) {
this.hp = hp;
// 改變?nèi)宋镅縣p
System.out.println("血條更新hp為:"+hp);
System.out.println("球型更新hp為:"+hp);
System.out.println("面板更新hp為:"+hp);
}
public Integer getMp() {
return mp;
}
public void setMp(Integer mp) {
this.mp = mp;
}
}
class Monster {
public void attact(Role r){
r.setHp(r.getHp()-10);
}
}
public class Test {
public static void main(String[] args) {
Role role = new Role();
role.setName("qiansion");
role.setHp(100);
role.setMp(100);
Monster monster = new Monster();
monster.attact(role);
}
}
輸出如下:
血條更新hp為:100
球型更新hp為:100
面板更新hp為:100
血條更新hp為:90
球型更新hp為:90
面板更新hp為:90
看似沒有什么問題,但是現(xiàn)在變化來了丸卷。產(chǎn)品經(jīng)理需要再添加一個顯示血量的地方
那么就需要在以前的代碼中枕稀,繼續(xù)新添加代碼,這樣不好
- 不符合開閉原則
-
就是上面代碼本身耦合性就太高
鑒于以上問題,我們需要重新設(shè)計一下整個模型萎坷,在之前的代碼中凹联,面板,球型哆档,血條顯示都是在人物的類中蔽挠,現(xiàn)在可以將他們抽取出來。等人物hp發(fā)生了變化虐呻,然后通知各個組件改變相應(yīng)的數(shù)據(jù)象泵。而兩個類之間傳遞數(shù)據(jù)只有兩種模式就是 pull 和 push寞秃,在本例中斟叼,明顯push更為方便(因為pull需要各個組件不停的詢問人物血條是否變化,內(nèi)耗過大且實時性較差)春寿,當(dāng)人物血條發(fā)生變化朗涩,人物主動的push數(shù)據(jù)到其他組件。而其他組件就等著人物來push绑改,所以我們可以稱這些組件為“觀察者”
現(xiàn)在谢床,我們可以著手重新設(shè)計Role類了,首先思考Role血量發(fā)生變化厘线,他需要通知到各個觀察者們识腿,那么Role類就必須知道觀察者都有哪些,所以需要一個集合去存放這些觀察者們造壮,相應(yīng)的渡讼,也需要一些操作觀察者的方法,當(dāng)然最重要的還是通知方法耳璧。
觀察者們除了關(guān)注人物血條的變化成箫,還得接受數(shù)據(jù)(人物當(dāng)前血量),面板旨枯,球型蹬昌,血條都需要接收這個數(shù)據(jù),所以可以抽出一個接受數(shù)據(jù)的接口攀隔,讓三個組件去實現(xiàn)皂贩。
綜上,代碼如下
class Role {
private String name;
private Integer hp;
private Integer mp;
private List<Observer> observers = new ArrayList<Observer>();
public void addObserver(Observer obj){
observers.add(obj);
}
public void removeObserver(Object obj){
observers.remove(obj);
}
public void notifyObservers(){
for (Observer observer : observers) {
observer.update(hp);
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getHp() {
return hp;
}
public void setHp(Integer hp) {
this.hp = hp;
notifyObservers();
}
public Integer getMp() {
return mp;
}
public void setMp(Integer mp) {
this.mp = mp;
}
}
interface Observer{
// 需要一個方法昆汹,來接受主體發(fā)來的新數(shù)據(jù)
void update(int hp);
}
class Panel implements Observer{
@Override
public void update(int hp) {
System.out.println("面板血條更改為:"+ hp);
}
}
class BallPanel implements Observer{
@Override
public void update(int hp) {
System.out.println("球形血條更改為:"+ hp);
}
}
class HeadPanel implements Observer{
@Override
public void update(int hp) {
System.out.println("頭部血條更改為:"+ hp);
}
}
class Monster {
public void attact(Role r){
r.setHp(r.getHp()-10);
}
}
public class Test {
public static void main(String[] args) {
Role role = new Role();
role.setName("qiansion");
role.setHp(100);
role.setMp(100);
Panel panel = new Panel();
BallPanel ballPanel = new BallPanel();
HeadPanel headPanel = new HeadPanel();
role.addObserver(panel);
role.addObserver(ballPanel);
role.addObserver(headPanel);
Monster monster = new Monster();
monster.attact(role);
}
}
輸出如下:
面板血條更改為:90
球形血條更改為:90
頭部血條更改為:90
跟之前的代碼相比明刷,明顯耦合度降低了,符合了單一職責(zé)筹煮。產(chǎn)品經(jīng)理如果需要新添加一個展示血條的地方遮精,那么可以新建一個類實現(xiàn)Observer
接口,并將該類添加到role的觀察者列表中。
現(xiàn)在仍然存在一個缺點:
目前主體Role只會把自己的hp廣播給所有的觀察者本冲,那么如果想也把mp一起廣播呢准脂?勢必要違反開閉原則!檬洞!而且游戲業(yè)務(wù)經(jīng)常變化狸膏,導(dǎo)致Role類的屬性越來越多,難道每次多一個屬性添怔,都要修改Observer的update 方法嗎湾戳?
顯然,上面的方法過于簡漏广料,在Observer的update方法里直接把Role對象傳進去即可解決砾脑,組件想展示什么就能夠展示什么
class Role {
private String name;
private Integer hp;
private Integer mp;
private List<Observer> observers = new ArrayList<Observer>();
public void addObserver(Observer obj){
observers.add(obj);
}
public void removeObserver(Object obj){
observers.remove(obj);
}
public void notifyObservers(){
for (Observer observer : observers) {
observer.update(this);
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getHp() {
return hp;
}
public void setHp(Integer hp) {
this.hp = hp;
notifyObservers();
}
public Integer getMp() {
return mp;
}
public void setMp(Integer mp) {
this.mp = mp;
}
@Override
public String toString() {
return "Role{" +
"name='" + name + '\'' +
", hp=" + hp +
", mp=" + mp +
'}';
}
}
interface Observer{
// 需要一個方法,來接受主體發(fā)來的新數(shù)據(jù)
void update(Role r);
}
class Panel implements Observer{
@Override
public void update(Role r) {
System.out.println("面板血條更改為:"+ r);
}
}
class BallPanel implements Observer{
@Override
public void update(Role r) {
System.out.println("球形血條更改為:"+ r);
}
}
class HeadPanel implements Observer{
@Override
public void update(Role r) {
System.out.println("頭部血條更改為:"+ r);
}
}
class Monster {
public void attact(Role r){
r.setHp(r.getHp()-10);
}
}
public class Test {
public static void main(String[] args) {
Role role = new Role();
role.setName("qiansion");
role.setHp(100);
role.setMp(100);
Panel panel = new Panel();
BallPanel ballPanel = new BallPanel();
HeadPanel headPanel = new HeadPanel();
role.addObserver(panel);
role.addObserver(ballPanel);
role.addObserver(headPanel);
Monster monster = new Monster();
monster.attact(role);
}
}
輸出如下:
面板血條更改為:Role{name='qiansion', hp=90, mp=100}
球形血條更改為:Role{name='qiansion', hp=90, mp=100}
頭部血條更改為:Role{name='qiansion', hp=90, mp=100}
目前每當(dāng)主體Role的狀態(tài)發(fā)生變化艾杏,就算主體有很多個屬性韧衣,也不會影響代碼
修改之后也帶來一些問題,作為一個接口购桑,Observer接口中的update方法畅铭,居然出現(xiàn)了具體類名!如此勃蜘,Observer就只能觀察Role這個類了硕噩,觀察不了別的類的對象了,不容易拓展缭贡。
繼續(xù)修改
class Role {
private String name;
private Integer hp;
private Integer mp;
private List<Observer> observers = new ArrayList<Observer>();
public void addObserver(Observer obj){
observers.add(obj);
}
public void removeObserver(Object obj){
observers.remove(obj);
}
public void notifyObservers(){
for (Observer observer : observers) {
observer.update();
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getHp() {
return hp;
}
public void setHp(Integer hp) {
this.hp = hp;
notifyObservers();
}
public Integer getMp() {
return mp;
}
public void setMp(Integer mp) {
this.mp = mp;
}
@Override
public String toString() {
return "Role{" +
"name='" + name + '\'' +
", hp=" + hp +
", mp=" + mp +
'}';
}
}
interface Observer{
// 需要一個方法炉擅,來接受主體發(fā)來的新數(shù)據(jù)
void update();
}
class Panel implements Observer{
private Role r;
public Panel(Role r){
this.r = r;
}
@Override
public void update() {
System.out.println("面板血條更改為:"+ r);
}
}
class BallPanel implements Observer{
private Role r;
public BallPanel(Role r){
this.r = r;
}
@Override
public void update() {
System.out.println("球形血條更改為:"+ r);
}
}
class HeadPanel implements Observer{
private Role r;
public HeadPanel(Role r){
this.r = r;
}
@Override
public void update() {
System.out.println("頭部血條更改為:"+ r);
}
}
class Monster {
public void attact(Role r){
r.setHp(r.getHp()-10);
}
}
public class Test {
public static void main(String[] args) {
Role role = new Role();
role.setName("qiansion");
role.setHp(100);
role.setMp(100);
Panel panel = new Panel(role);
BallPanel ballPanel = new BallPanel(role);
HeadPanel headPanel = new HeadPanel(role);
role.addObserver(panel);
role.addObserver(ballPanel);
role.addObserver(headPanel);
Monster monster = new Monster();
monster.attact(role);
}
}
輸出如下:
面板血條更改為:Role{name='qiansion', hp=90, mp=100}
球形血條更改為:Role{name='qiansion', hp=90, mp=100}
頭部血條更改為:Role{name='qiansion', hp=90, mp=100}
修改后,主體不再主動推送數(shù)據(jù)匀归,從push的方式修改為 push + pull的方式坑资,主體只是通知觀察者“我變了”,觀察者收到消息穆端,就自己去看主體哪兒變了袱贮,巧妙之處就在于此,因為這種推+拉的模式体啰,主體中有觀察者攒巍,觀察者中有主體。再以上代碼中還可以做一些抽象荒勇,Role類中的addObserver和removeObserver等都可以抽象為接口柒莉,這就是最完整的觀察者模式
interface Subject {
void addObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
class Role implements Subject{
private String name;
private Integer hp;
private Integer mp;
private List<Observer> observers = new ArrayList<Observer>();
@Override
public void addObserver(Observer observer){
observers.add(observer);
}
@Override
public void removeObserver(Observer observer){
observers.remove(observer);
}
@Override
public void notifyObservers(){
for (Observer observer : observers) {
observer.update();
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getHp() {
return hp;
}
public void setHp(Integer hp) {
this.hp = hp;
notifyObservers();
}
public Integer getMp() {
return mp;
}
public void setMp(Integer mp) {
this.mp = mp;
}
@Override
public String toString() {
return "Role{" +
"name='" + name + '\'' +
", hp=" + hp +
", mp=" + mp +
'}';
}
}
interface Observer{
// 需要一個方法,來接受主體發(fā)來的新數(shù)據(jù)
void update();
}
class Panel implements Observer{
private Role r;
public Panel(Role r){
this.r = r;
r.addObserver(this);
}
@Override
public void update() {
System.out.println("面板血條更改為:"+ r);
}
}
class BallPanel implements Observer{
private Role r;
public BallPanel(Role r){
this.r = r;
r.addObserver(this);
}
@Override
public void update() {
System.out.println("球形血條更改為:"+ r);
}
}
class HeadPanel implements Observer{
private Role r;
public HeadPanel(Role r){
this.r = r;
r.addObserver(this);
}
@Override
public void update() {
System.out.println("頭部血條更改為:"+ r);
}
}
class Monster {
public void attact(Role r){
r.setHp(r.getHp()-10);
}
}
public class Test {
public static void main(String[] args) {
Role role = new Role();
role.setName("qiansion");
role.setHp(100);
role.setMp(100);
Panel panel = new Panel(role);
BallPanel ballPanel = new BallPanel(role);
HeadPanel headPanel = new HeadPanel(role);
Monster monster = new Monster();
monster.attact(role);
}
}