总结,记录  
日历
<2007年7月>
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234
统计
  • 随笔 - 18
  • 文章 - 1
  • 评论 - 5
  • 引用 - 0

导航

常用链接

留言簿(1)

随笔分类(5)

随笔档案(2)

收藏夹(1)

Web Sites

开一扇门

搜索

  •  

积分与排名

  • 积分 - 2745
  • 排名 - 3642

最新评论

阅读排行榜

评论排行榜

 
    常听说这么一句话(大意是这样):不必可以去套用设计模式,如果按照面向对象的基本原则编程,自然是优雅的设计,即使没有刻意使用模式,设计也会近乎于模 式。开始感觉有一点玄,但在看了《C#设计模式纵横谈》视频后,觉得有所收获。下面,就参考视频的内容,尝试着写这么一个过程:根据面向对象的一般原则对设计进行重构,逐渐演化出观察者模式。
涉及的面 向对象设计原则:单一职责原则、封装变化、面向接口编程、依赖倒置原则、开闭原则。

1.发布订阅模型:



         
               
假如有需求如下:

银行需要把帐户的如汇款、转账或取款等操作通知用户,途径包括手机短信、 email等。如图所式。

自然地,我们可以这样做:

public class ATM
 {
     BankAccount bankAccount;
     
     
public void process()
     {
          
//bankAccount...
         this.sendEmail(userEmail);
         
this.sendPhone(phoneNumber);
     }
 
     
private void sendEmail(String userEmail)
     {
         
//
     }

    
private void sendMobile(String phoneNumber)
     {
         
//
     }
 }

ATM机的 process()方法在处理完业务逻辑后,由email和phone通知用户。

2.初步重构

好像有bad smells,恩,根据单一职责原则。新增Email类和Phone类,并把相关业务逻辑改到BankAccount类完 成。于是我们的代码可以这样:


public class ATM
{
    BankAccount bankAccount;
    
    
    
public void process()
    {
          
//
          bankAccount.withDraw();
    }
 
 }

public class BankAccount 
{
    Email email;
    Mobile mobile;

    
public void withDraw()
    {
         
//
         email.sendEmail(userEmail);
         mobile.sendMobile(phoneNumber);
    }
}

public class Email
{
    
public void sendEmail(String userEmail)
    {
    }
}

public class Mobile
{
    
public void sendMobile(String phoneNumber)
    {
    }
}

下面是代码的UML图:




3.拥抱变化

这个解决方案有问题吗?可能没有问题。它实 现了我们的需求:在帐户有操作变动的时候,通知Email和Mobile去发送信息给用户。但这样设计就足够了吗?可能足够了,可能还不够。
考虑如下两种情况:
1.在很长一段时间里,订阅方式很稳定,比如系统只通过邮件和手机短信进行信息订阅,那么这个实现没 有太大问题;
2.在近一两年或更短的时间,更多的订阅方式将会源源不断地被加进来:比如可以登录官方网站等等,那这个实现就有问题: 再看一下我们的UML图,类BankAccount依赖于Email和Mobile类!就是说,如果需要添加新的订阅方式ATM类的process()方 法势必要重新设计!

于是我们的BankAccount类不得不变成:

public class  BankAccount
{
    Email email;
    Mobile mobile;
    Web web;

    
public void withDraw()
    {
          
//
         email.sendEmail(userEmail);
         mobile.sendMobile(phoneNumber);
         web.sendWeb(webSite);
    }
 
 }

如果还有另一种方式,那么process()方法就又会需要加入:otherSubscribe.send...();等方法,另外 如果订阅类的接口(这里指sendEmail等方法)发生变化,BankAccount的withDraw()方法也必须有相应的变化!这当然是种灾难。 我们必须改变这种情况。
先解决遗留问题:第一种情况:订阅方式相对稳定的情况下呢?不改动会产生灾难吗?
个人认 为:不会。比如某个系统信息只通过手机短信订阅,那就没有必要太在意这个问题。考虑周全一点不好吗,如果将来有类似需求呢?小心过度设计!为了将来可能出现需求而进行的预先设计并不太好。有需求,才有设计。

现在来看解决之道:

运用面向对象的思想,抽象出问题所在。BankAccount类依赖于 Email类和Mobile类,而Email和Mobile是具体的类,ATM依赖于具体的类了,而且还不止一个!回忆一下依赖倒置原则:具体应该依赖于抽象,底层模式应该依赖于高层模式那怎么实现依赖倒置原则呢?面向对象编程中有一条总的原则:封装变化。如何实现封装变化?需要我们这样:面向接口编程

回顾一下:我们在设计中实现类依赖了具体的类,违反了依赖倒置原则。为了遵循依赖倒置原则,我们采用面向接口编程的方法,从而 实现了面向对象的一条总的原则:封装变化。


看代码:

public interface AccountObserver
{
    
public void upDate(UserAccount userAccount);
}

public class Email implements AccountObserver
{
    
public void upDate(UserAccount userAccount)
    {
    }
}

public class Mobile
{
    
public void upDate(UserAccount userAccount)
    {
    }
}

public class BankAccount 
{
    List 
<AccountObserver> observer = new ArrayList<AccountObserver>;

    
public void withDraw()
    {
         
//
         for (AccountObserver ao : observer)
         {
            ao.upDate(userAccount)
          }
    }
    
    
public void addOberver(AccountObserver accountObserver)
    {
          observer.add(accountObserver);
     }
}

UML图:



现在,BankAccount依赖于interface AccountObserver。Email和Mobile实现AccountObserver接口。通过遵循面向接口编程遵循了依赖倒置原则

4.开闭原则

终于修改好了,我们解决了订阅者变化的问 题。但如果发布者也倾向于变化呢?这就牵涉到面向对象里的另一个原则:开闭原则即:对扩展开放,对修改关闭。具体怎么做呢?通过抽象类,从抽象类继承具体类。
看最终的代码(只写几个关键的方法,全貌可看最后的UML图):

订阅:

public interface AccountObserver
{
    
public void upDate(UserAccount userAccount);
}

public class Email implements AccountObserver
{
    
public void upDate(UserAccount userAccount)
    {
    }
}

public class Mobile implements AccountObserver
{
    
public void upDate(UserAccount userAccount)
    {
    }
}


发布:



public abstract class Subject
{
     List 
<AccountObserver> observer = new ArrayList<AccountObserver>;

    
protected void withDraw()
    {
         
//
         notify();
         
    }
    
    
protected void notify(UserAccount userAccount)
    {
         
for (AccountObserver ao : observer)
         {
            ao.upDate(userAccount)
          }
     }
    
    
protected void addOberver(AccountObserver accountObserver)
    {
          observer.add(accountObserver);
     }

      
protected void deleteOberver(AccountObserver accountObserver)
    {
          observer.remove(accountObserver);
     }

}

public class BankAccount extends Subject
{
    
public void withDraw()
    {
         
//
         for (AccountObserver ao : observer)
         {
            ao.upDate(userAccount)
          }
    }
    
}



看UML图:




5.观察者模式概况




这就是观察者模式了,对比一下官方的UML图,是不是一目了然了呢?
稍作说明(这里的依赖 都是指广义的依赖):
1.被观察者ConcreteSubject继承自Subject抽象类;
2.Subject抽象类依赖于观察者Observer抽象接口;
3.观察者ConcreteObserver实现Observer 接口;
4.观察者ConcreteObserver间接依赖于ConcreteSubject类。
如果要增加具体的观察者,只要再实现Obsever接口即可,而被观察方不需要做任何修改。而如果需要修改被观察者,只要从Subject抽 象类继承即可。
posted on 2007-07-10 00:11 L.X 阅读(1338) 评论(3)  编辑  收藏 所属分类: Design Patterns
评论:
  • # re: Refactoring To Patterns: 观察者模式  Sun Posted @ 2007-07-10 08:40
    我这个好像比较容易看懂
    http://www.blogjava.net/Swing/archive/2007/07/09/128970.html  回复  更多评论   

  • # re: Refactoring To Patterns: 观察者模式  stoneshao Posted @ 2007-07-10 10:04
    不错,写得清晰易懂  回复  更多评论   

  • # re: Refactoring To Patterns: 观察者模式  10:10 Posted @ 2007-07-10 10:28
    thx楼上两位,^_^

    一楼哥们里的blog文章是
    板桥里人 http://www.jdon.com 写的:
    设计模式之Observer
    http://www.jdon.com/designpatterns/observer.htm

    但我觉得反而从重构的角度理解Observer要好一些:设计模式是工具,用来理解面向对象思想,比直接拿来用更有意义。我深以为然。

    当然,我写出学习笔记,很高兴能得到回应和讨论。  回复  更多评论   


只有注册用户登录后才能发表评论。


网站导航:
 
 
Copyright © L.X Powered by: 博客园 模板提供:沪江博客