论坛首页 Java企业应用论坛

再谈面向对象的思维方法

浏览 9480 次
该帖已经被评为精华帖
作者 正文
   发表时间:2004-03-25  
这篇文章本来是回贴给Robbin的《面向对象的思维方法》的,后来想想把它单独提出来再讨论讨论可能会比较有意思,所以就又加了些内容,把它作为根帖发了吧。

Robbin的发mail的例子很好,我们就还是拿它来讨论吧。

引用

举个例子,要发广告邮件,广告邮件列表存在数据库里面。


那么实现的过程肯定是这样的:

1. 连接数据库,取邮件地址列表。
2. 遍历邮件列表,设定新邮件(Address, title, body)
3. 调用本机的qmail的sendmail命令发送邮件。

作为一个面向对象的程序员,在写代码之前我主要要考虑什么呢?
1. 怎么封装对象
2. 怎么降低耦合度
3. 怎么实现可重用,比如不从数据库读,而改从文件读怎么处理。再比如不用Qmail,改用Rmail怎么处理。

考虑了以上的问题后,我会这么实现:

1. 首先定义一个Mail DataObject
public class MailDTO {
    private String strAddress;
    private String strTitle;
    private String strBody;

    public MailDTO(); {super();;}

    public MailDTO(String strAddress, String strName, String strType); {
        setAddress(strAddress);;
        setName(strName);;
        setType(strType);;
    }

    public String getAddress(); {return this.strAddress;}
    public void setAddress(String strAddress); { this.strAddress = strAddress;}

    public String getTitle(); {return this.strTitle;}
    public void setTitle(String strTitle); { this.strTitle = strTitle;}

    public String getBody(); {return this.strBody);;}
    public void setBody(String strBody); { this.strBody); = strBody);;}
}


2. 定义ListAddress接口
public interface ListAddress {
    public Iterator getListAddress();
}


3. 定义ListAddress接口的实现DbListAddress
public class DbListAddress implements ListAddress {
    public Iterator getListAddress(); {
        //blablabla
    }
}

以后你可以加FileListAddress, 只要implements ListAddress

4. 定义SendJunk接口
public interface SendJunk {
    public void sendJunk();;
}


5. 定义JunkData类
public class JunkData {
    protected MailDTO mdto;

    public void setMailDTO(MailDTO mdto); {
        this.mdto = mdto;
    }
}


6. 定义Qmail的SendJunk的实现
public class QmailSendJunk extends JunkData implements SendJunk {
    public void sendJunk(); {
        //blablabla
    }
}


这里要注意的是它 extends JunkData, implements SendJunk,所以用的时候得这么写:
SendJunk sj = new QmailSendJunk();;
sj.setMailDTO(mdto);;
sj.sendJunk();;


同样,以后你可以加Rmail, Smail, Tmail......只要extends JunkData implements SendJunk

7. 控制程序
SendJunk sj = new QmailSendJunk();;
ListAddress la = new DbListAddress();;

MailDTO mdto = new MailDTO();;
mdto.setTitle("good news");;
mdto.setBody("blablabla");;

Iterator it = la.getListAddress();;
while (it.hasNext();); {
    String strAddress = (String);it.next();;
    mdto.setAddress(strAddress);;

    sj.setMailDTO(mdto);;
    sj.sendJunk();;
}


这里要说的有两点:
1. 以后要改写的就是添加ListAddress和SendJunk这两个接口的实现,然后把控制程序的第1,2行换成相应的class。
2. 一个小Tip,尽量避免在while里面用new,否则如果你的iterator很长,系统消耗会很大。
   发表时间:2004-03-25  
总结的好, 支持。
0 请登录后投票
   发表时间:2004-03-25  
bruce 写道
总结的好, 支持。

总结的不好,不支持
0 请登录后投票
   发表时间:2004-03-26  
我写那篇文章的时候是从入门的角度来考虑的,所以没有想那么深入和细致,谢谢你的总结,以后改写这篇文章的时候就可以加入这部分内容,并且注明引用自xanada。

不过我想在你的总结基础上再加点东西,也许就更完美些:

SendJunk sj = new QmailSendJunk();; 
ListAddress la = new DbListAddress();; 


考虑到这两行可以继续修改。之所以抽象出接口是了为了重用,不过此时抽换了接口实现类仍然要修改代码,应该写一个工厂类用来获得接口实现类,而不是直接硬编码。

SendJunk sj = JunkFactory.getSendJunk();;
ListAddress la = ListAddress.getListAddress();;


然后在工厂类方法里面根据某些条件,例如类搜索的顺序,或者读配置文件,或者读命令行传入的参数来决定load哪种接口实现类。这样抽换接口实现类,就不必改代码了。
0 请登录后投票
   发表时间:2004-03-26  
在下初来,很喜欢这里的气氛。希望Robbin你的论坛能够越办越好,牛人越聚越多。。。
0 请登录后投票
   发表时间:2004-03-27  
Gamble ,Gamble never  changes
0 请登录后投票
   发表时间:2004-03-27  
public class QmailSendJunk extends JunkData implements SendJunk {
    public void sendJunk(); {
        //blablabla
    }
} 


谈谈我的看法, 首先extends代表一种 is a 的关系, 尽管此处可以这样使用, 但如果说QmailSendJunk is a JunkData, 看起来QmailSendJunk中就一个sendJunk()方法, 所以这样的is a 关系不太合适。 我觉得此处可以使用bridge模式, 把JunkData看做bridge模式的抽象部分(叫abstraction), 把动作sendJunk()看做bridge模式的implementation部分。

随便举一个例子, 不一定贴切, 假设 JunkData 就是一种Data, 它有XML格式和HTML格式。所以abstraction这边的父类为Data, 它的两个子类为XMLData, HTMLData, implementation这边的父类为processMail, 它有两个子类有Qmail和Sendmail两种方式, 所以可以有四种组合: XMLData+Qmail, XMLData+Sendmail, HTMLData+Qmail, HTMLData+Sendmail. 我们可以通过了客户程序处任意指定这四种方式中的几种。

或者, 你可不可以直接用组合的方式,这样就不会是parent-chid的tightly coupling relationship了, 象这样:
public class QmailSendJunk JunkData implements SendJunk {
  private JunkData myJunkData = new JunkData();; 
  
 public QmailSendJunk(MailDTO mdto);{
     myJunkData.setMailDTO(mdto);;
  }  

  public void sendJunk(); {
        //blablabla
    }
} 


在重构中, 如果想保证接口不变,可以考虑proxy和decorator模式,其实它们也是在内部应用组合方式,接口要改变但以前的类内部不改动,可以考虑adaptor模式,它也是组合方式的应用。如果以前类的结构已经定下来,不好再改,但需要扩充类的功能,可以考虑使用visitor模式。这个模式是我最头痛的模式:(, 不过是越来越清楚了。

Robbin,说到的应是一种简单工厂模式, 通过传参数来决定不同类的实例化, 但如果可能的话, 我更愿意把产品抽象出来, 做一种工厂模式或抽象工厂模式, 好处就是代码以后的改动要比简单工厂模式少,这样所有的类的实例化推迟到客户端,是一种真正的面象对象的编程, 当然不好的就是比简单工厂麻烦。
0 请登录后投票
   发表时间:2004-04-02  
为啥要一个JunkData类呢?而且还让QmailSendJunk 去继承它?
这样子让程序变得难以理解了。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics