设计模式-观察者模式

1. OOA and OOD

1.1 模拟程序

  • 小孩在睡觉
  • 醒来后要求吃东西
    • 思路1
      一个睡着的小孩(Kid), 一个人(Parent)专门看着这个小孩醒没醒(thread)。
    • 代码实现
   package com.alan.planA;
   class Child implements Runnable {
       private boolean flag;
   
       public boolean isWakenUp() {
           return flag;
       }

       public void run() {
           try {
               Thread.sleep(5000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           flag = true;
       }
   }

   class Dad implements Runnable {
       private Child c;
   
       public Dad(Child c) {
           this.c = c;
       }

       public void run() {
           while (!c.isWakenUp()) {
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
           feed(c);
       }

       private void feed(Child c2) {
           System.out.println("feed child.");
       }   
   }

   public class FeedChildSimulator {
       public static void main(String[] args) {
           Child c = new Child();
           Dad d = new Dad(c);
           Thread thread1 = new Thread(d);
           Thread thread2 = new Thread(c);
           thread1.start();
           thread2.start();
       }
   }
  • 思路1的方式可行,但会无端的耗费太多的CPU资源,如果是一个人看孩子的话,那基本上没有时间干其他的事情了。所以,引申出思路2。
  • 思路2
    监听孩子醒了这件事情。主动监听会消耗CPU资源。最好的设计就是在小孩胳膊上绑一绳,他一醒过来,自动拉一下那个绳子就可以了,这时候Parent过来喂他就可以了。反客为主。
  • 代码实现
package com.alan.planB;
class Child implements Runnable {
   private boolean flag;
   private Dad d;
   
   public Child(Dad d) {
       this.d = d;
   }
   
   public void isWakenUp() {
       flag = true;
       d.feed(this);
   }

   public void run() {
       try {
           Thread.sleep(5000);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       isWakenUp();
   }
}

class Dad {
   public void feed(Child c2) {
       System.out.println("feed child.");
   }
}

public class FeedChildSimulator {
   public static void main(String[] args) {
       Dad d = new Dad();
       Child c = new Child(d);
       Thread thread = new Thread(c);
       thread.start();
   }
}
  • 思路三
    不同醒了的方式,他爸爸处理的方式也应该是不同的。事情的发生是包含着一些具体情况的。当孩子醒了的时候,应该把事情的具体情况(事件<事情的发生时间,地点等>)告诉他爸爸。该怎么做,用什么样的设计方法。
  • 代码实现
package com.alan.planC;

class WakenUpEvent {
    private long time;
    private String location;
    private Child source;

    public WakenUpEvent(long time, String location, Child source) {
        super();
        this.time = time;
        this.location = location;
        this.source = source;
    }

    public long getTime() {
        return time;
    }

    public void setTime(long time) {
        this.time = time;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }

    public Child getSource() {
        return source;
    }

    public void setSource(Child source) {
        this.source = source;
    }

}

class Child implements Runnable {
    private Dad d;

    public Child(Dad d) {
        this.d = d;
    }

    public void isWakenUp() {
        d.actionToWakenUp(new WakenUpEvent(System.currentTimeMillis(), "bed", this));
    }

    public void run() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        isWakenUp();
    }
}

class Dad {
    public void actionToWakenUp(WakenUpEvent wakenUpEvent) {
        // 根据时间,地点,事件源来具体做出处理
        System.out.println("feed child.");
    }
}

public class FeedChildSimulator {
    public static void main(String[] args) {
        Dad d = new Dad();
        Child c = new Child(d);
        Thread thread = new Thread(c);
        thread.start();
    }
}
  • 如果此时,当小孩醒了,除了他爸爸要做出响应之外,他爷爷,他奶奶...也要做出响应,应该如何是好,按照思路3的方式,小孩手上的线会越来越多。这说明思路3的这种设计也是不好的,接下来就引出思路4。
  • 思路4
    将变化的事件进行抽象,将绳子都按顺序绑在一个棍子上,将监听所有孩子醒了的这件事情的所有监听者做成一个集合。
  • 代码实现
package com.alan.planD;

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

class WakenUpEvent {
    private long time;
    private String location;
    private Child source;

    public WakenUpEvent(long time, String location, Child source) {
        super();
        this.time = time;
        this.location = location;
        this.source = source;
    }

    public long getTime() {
        return time;
    }

    public void setTime(long time) {
        this.time = time;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }

    public Child getSource() {
        return source;
    }

    public void setSource(Child source) {
        this.source = source;
    }

}

class Child implements Runnable {
    private List wakenUpListeners = new ArrayList();

    public void addWakenUpListener(WakenUpListener wul) {
        wakenUpListeners.add(wul);
    }
    
    public void isWakenUp() {
        for (WakenUpListener l : wakenUpListeners) {
            l.actionToWakenUp(new WakenUpEvent(System.currentTimeMillis(), "bed", this));
        }
    }

    public void run() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        isWakenUp();
    }
}

class Dad implements WakenUpListener {
    public void actionToWakenUp(WakenUpEvent wakenUpEvent) {
        // 根据时间,地点,事件源来具体做出处理
        System.out.println("feed child.");
    }
}

class GrandFather implements WakenUpListener {
    public void actionToWakenUp(WakenUpEvent wakenUpEvent) {
        // 根据时间,地点,事件源来具体做出处理
        System.out.println("hug child.");
    }
}

/**
 * 实现这个接口的类,一定会监听WakenUp醒过来的这件事 
 */
interface WakenUpListener {
    // 对这样的事件进行响应,事件本身和事件源脱离 ,灵活度最高
    public void actionToWakenUp(WakenUpEvent wakenUpEvent);
}

public class FeedChildSimulator {
    public static void main(String[] args) {
        Dad d = new Dad();
        GrandFather gf = new GrandFather();
        Child c = new Child();
        c.addWakenUpListener(d);
        c.addWakenUpListener(gf);
        
        Thread thread = new Thread(c);
        thread.start();
    }
}

  • 基于JDK中Observer接口和Observable类实现观察者模式
package design.pattern.behavior.observer;

import java.util.Observable;
public class ConcreteSubject extends Observable {
    private int state;

    public int getState() {
        return state;
    }

    public void setState(int state) {
        this.state = state;
        this.setChanged();
        this.notifyObservers();
    }
}

package design.pattern.behavior.observer;
import java.util.Observable;
import java.util.Observer;

public class MyObserver implements Observer {
    
    private int state;
    
    public int getState() {
        return state;
    }

    public void setState(int state) {
        this.state = state;
    }

    public void update(Observable o, Object arg) {
        state = ((ConcreteSubject)o).getState();
    }
}

package design.pattern.behavior.observer;

public class Client {
    public static void main(String[] args) {
        MyObserver o1 = new MyObserver();
        MyObserver o2 = new MyObserver();
        MyObserver o3 = new MyObserver();
        
        System.out.println(o1.getState());
        System.out.println(o2.getState());
        System.out.println(o3.getState());
        
        ConcreteSubject s = new ConcreteSubject();
        s.addObserver(o1);
        s.addObserver(o2);
        s.addObserver(o3);
        
        s.setState(3000);
        
        System.out.println("state changed...");
        System.out.println(o1.getState());
        System.out.println(o2.getState());
        System.out.println(o3.getState());
    }
}

1.2 弄清楚用户到底要什么, 具体什么要求。

  • 甲方: 我说:“我要一房子”。
  • 乙方: 上去直接把房子盖了出来。正正方方的。
  • 甲方: 我说我要的是一碉堡。
  • 乙方: 此时要哭的心都有了。
    此时的设计一定会完蛋,因为这是你以为的房子,不是客户想要的。在真正设计一个软件系统的时候,最重要的事情是什么啊?
    • 弄明白客户的具体需求到底是什么样子的!
      你要的房子要建成一个什么样子的呢?
    • 注意:越是钻到技术中拔不出来的人,这方面越是欠缺。当你的代码量有一定量的时候,你会注意到,要做好一个软件系统,首先你要保证第一点,就是你在开头上不能错。开头错了就是南辕北辙了。所以最重要的是弄明白客户的要求到底是什么。弄明白别人要什么的这个过程叫做分析。
    • 分析(要做什么)是弄明白客户要干什么,要弄明白你的软件系统要实现的功能是什么
    • 设计(这个功能怎么实现): 要达成同一功能,会有不同的方法。
    • 确定功能 > 用啥样的结构(合适的,具体问题具体分析)去实现这个功能。
    • 灵活一点,再灵活一点。
    • 设计模式刚开始可以往死里学,熟悉招式,知道其使用场景;下一个层次是想办法把招式穿起来,灵活运用;最高境界就是设计模式的名字忘了,让我设计,我依然可以设计的很到位。心中无招,手中也无招。但是以上都是建立在每一招你都非常熟悉的基础之上。
    • 以后在遇到问题时候,首先要想清楚问题的本身你有没有搞清楚。如果没有弄清楚,首先不要一下子就钻到技术层面去。确定需求的时候用什么语言其实已经不重要了。
    • 所以,一个真正的软件项目,首先第一步要做的是确定这个需求。怎么确定需求?客户不一定明白自己想要什么?婚姻的不幸就是由小小需求的不确定慢慢积累起来的。
    • 面向对象设计:
      怎样从需求中探索出我所需要的类,需不需要加各种各样的辅助类,到底哪些是实体类,哪些是管理的类,哪些是工具类。需不需要由各种的线程,需要什么样的架构等等。
      实体类(属性):找需求文档中的名词。
      方法:动词。
      一个类的属性进行只让自己读写。
    • 在软件的设计过程中,如果仅考虑到当前,这个设计会没有弹性,不具备可扩展性。预想到会有变化可以设计得出来,否则设计不出来。透彻的理解需求,想想需求未来会发生什么样子的变化。所以预想不到未来的需求,你的设计就做不到位,这是很重要的一点。假如说,预想的需求过了头了,也不行,叫过度设计(over design)。在一段时间内预想到一定时间的变化就够了,不要想太多了。设计功能的时候要想到你将来的扩展点在什么地方。
    • 将变化的事件进行抽象。
    • 做到Main方法不变,读配置文件的形式。
    • AWT
    • 用设计模式,是有成本的
    • 企业开发中实现需求,在有限时间完成功能最重要。
    • 先将功能实现,然后重构(refactor)

你可能感兴趣的:(设计模式-观察者模式)