论坛首页 Java版 企业应用

[研究]从功利主义的角度,看Observer模式与AOP的实现

浏览 5110 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
最后更新时间:2004-04-14
最新出的这期《程序员》,里面有透明的一篇文章《Observer模式实战解析--用AOP实现关注点分离》。我打算干一点比较煞风景的事情,分析代码行数。

所谓分析代码行数,是基于这样一个理念,从功利主义的角度出发,最好的设计,就是在整个软件生命期中,代码行数(包括写出来的代码与修改的代码)最少的设计。比如说,实现一个软件,然后再前前后后修改10次。
方案一,初始代码1000行,每次修改10行,那么方案一的总代码行数就是1000+10*10=1100。
方案二,初始代码2000行,每次修改5行,那么方案二的总代码行数就是2000+5*10=2050。

比较两种方案的效益,我们可以得出简单的结论:当修改次数超过200次之后,方案二的优势才能得到体现。

当然,这样的计算,是过于简化问题了,一方面每次的修改都可能是不一样的,另一方面计算修改行数的算法还没有定论,我这里先提出一个初步的设想。如果将来这个算法成熟了,那么评价设计的优劣,就能够比较客观了。

1、增加一行,修改行数+1
2、删除一行,修改行数+1
3、修改一行,修改行数+1
4、移动一行,修改行数+1
5、增、删、改、移连续的n行,修改行数+1
6、修改一行的字符数,超过一行字符数的50%,修改行数+2
7、能够借助重构工具自动完成一次重构,修改行数+1
8、增、删、改、移的注释(必须的注释),修改行数+0.5
9、所有修改行数相加后,再乘以修改文件数=总修改行数

一个好的设计,就是在可以预见的未来的各种修改需求下,各次的总修改行数相加再加初始代码行数,能够尽可能的少。

现在开始讨论透明的那个例子:一个用户管理组件UserManger,提供三种功能,registerUser,userLogin,unregisterUser。当用户注册或者注销的时候,发送JMS消息,当用户登录的时候,记LOG。然后修改的需求是:当用户登录时,增加手机短消息通知。

首先我写出一行最直观的方案
[code:1]//第一种设计,最直观的方案
class UserManager{
public void registerUser(User u){
//TODO:注册一个新用户
//发送JMS消息
IService jmss=ServiceManager.getService("JMS");
jmss.sendMessage(/*一些传递的参数*/);
}

public User userLogin(String UserName,String Password){
User u=CheckUser(UserName,Password);
IService jmss=ServiceManager.getService("LOG");
jmss.sendMessage(/*一些传递的参数*/);
return u;
}

public void unregisterUser(User u){
//TODO:注销一个用户
IService jmss=ServiceManager.getService("JMS");
jmss.sendMessage(/*一些传递的参数*/);
}
}

public interface IService {
public void sendMessage(/*一些传递的参数*/);
}

public class JMSService implements IService{
//TODO:各种与JMS相关的代码
public sendMessage(/*一些传递的参数*/){
//TODO发送消息。
}
}

public class LogService implements IService{
//TODO:各种与Log相关的代码
public sendMessage(/*一些传递的参数*/){
//TODO写Log。
}
}

public class ServiceManager{
//TODO:初始化JMS服务的实例
//TODO:初始化LOG服务的实例
public static IService getService(String ServiceName){
if(ServiceName.equals("JMS")){
return JMSService;
} else if (ServiceName.equals("LOG")){
return LOGService;
} else {
return null;
}
}
[/code:1]

一共54行。
当这个方案需要增加SMS功能时,我们做如下更改:
[code:1]//在第一种设计的基础上修改,登录时,增加一个手机消息
class UserManager{
public void registerUser(User u){
//TODO:注册一个新用户
//发送JMS消息
IService jmss=ServiceManager.getService("JMS");
jmss.sendMessage(/*一些传递的参数*/);
}

public User userLogin(String UserName,String Password){
User u=CheckUser(UserName,Password);
IService jmss=ServiceManager.getService("LOG");
jmss.sendMessage(/*一些传递的参数*/);
IService sms=ServiceManager.getService("SMS");//新增代码
sms.sendMessage(/*一些传递的参数*/);//新增代码
return u;
}

public void unregisterUser(User u){
//TODO:注销一个用户
IService jmss=ServiceManager.getService("JMS");
jmss.sendMessage(/*一些传递的参数*/);
}
}

public interface IService {
public void sendMessage(/*一些传递的参数*/);
}

public class JMSService implements IService{
//TODO:各种与JMS相关的代码
public sendMessage(/*一些传递的参数*/){
//TODO发送消息。
}
}

public class LogService implements IService{
//TODO:各种与Log相关的代码
public sendMessage(/*一些传递的参数*/){
//TODO写Log。
}
}

public class SMSService implements IService{//新增代码
//TODO:各种与SMS相关的代码//新增代码
public sendMessage(/*一些传递的参数*/){//新增代码
//TODO写SMS。//新增代码
}//新增代码
}//新增代码

public class ServiceManager{
//TODO:初始化JMS服务的实例
//TODO:初始化LOG服务的实例
//TODO:初始化SMS服务的实例//新增代码
public static IService getService(String ServiceName){
if(ServiceName.equals("JMS")){
return JMSService;
} else if (ServiceName.equals("LOG")){
return LOGService;
} else if (ServiceName.equals("SMS")){//新增代码
return SMSService;//新增代码
} else {
return null;
}
}
}[/code:1]

修改代码行数(1+1+1+1)×3=12

总修改行数+初始代码=54+12=66

然后我们再来写出Observer方式的实现。
   
最后更新时间:2004-04-14
[code:1]//第二种设计,Observer的方案
public interface ISubject{
public boolean addListener(IListener listener);
public boolean removeListener(IListener listener);
public void broadcast(Message message,String type);
}

public abstract class AbstractService implements ISubject{
private Set _listeners;
public boolean addListener(IListener listener){
return _listeners.add(listener);
}
public boolean removeListener(IListener listener){
return _listeners.remove(listener);
}
public void broadcast(Message message,String type){
for(Iterator it=_listeners.iterator();it.hasNext();){
IListener listener=(IListener)it.next();
if(listener.isType(type)){
listener.sendMessage(message);
}
}
}
}

public class MockUserManager extends AbstractService{
public void registerUser(User u){
//TODO:注册一个新用户
//发送JMS消息
broadcast(/*一些传递的参数*/,"JMS");
}

public User userLogin(String UserName,String Password){
User u=CheckUser(UserName,Password);
broadcast(/*一些传递的参数*/,"LOG");
return u;
}

public void unregisterUser(User u){
//TODO:注销一个用户
broadcast(/*一些传递的参数*/,"JMS");
}
}

public interface IListener {
public void sendMessage(/*一些传递的参数*/);
public boolean isType(String type);
}

public class JMSService implements IListener{
//TODO:各种与JMS相关的代码
public sendMessage(/*一些传递的参数*/){
//TODO发送消息。
}
public boolean isType(String type){
if(type.equals("JMS")){
return true;
} else {
return false;
}
}
}

public class LogService implements IListener{
//TODO:各种与Log相关的代码
public sendMessage(/*一些传递的参数*/){
//TODO写Log。
}
public boolean isType(String type){
if(type.equals("LOG")){
return true;
} else {
return false;
}
}
}

public class InitClass{
public void init(){
MockUserManager um=new MockUserManager();
um.addListener(new JMSService());
um.addListener(new LOGService());
}
}[/code:1]
84行代码

[code:1]//在第二种设计的基础上修改,登录时,增加一个手机消息
public interface ISubject{
public boolean addListener(IListener listener);
public boolean removeListener(IListener listener);
public void broadcast(Message message,String type);
}

public abstract class AbstractService implements ISubject{
private Set _listeners;
public boolean addListener(IListener listener){
return _listeners.add(listener);
}
public boolean removeListener(IListener listener){
return _listeners.remove(listener);
}
public void broadcast(Message message,String type){
for(Iterator it=_listeners.iterator();it.hasNext();){
IListener listener=(IListener)it.next();
if(listener.isType(type)){
listener.sendMessage(message);
}
}
}
}

public class MockUserManager extends AbstractService{
public void registerUser(User u){
//TODO:注册一个新用户
//发送JMS消息
broadcast(/*一些传递的参数*/,"JMS");
}

public User userLogin(String UserName,String Password){
User u=CheckUser(UserName,Password);
broadcast(/*一些传递的参数*/,"LOG");
broadcast(/*一些传递的参数*/,"SMS");//新增代码
return u;
}

public void unregisterUser(User u){
//TODO:注销一个用户
broadcast(/*一些传递的参数*/,"JMS");
}
}

public interface IListener {
public void sendMessage(/*一些传递的参数*/);
public boolean isType(String type);
}

public class JMSService implements IListener{
//TODO:各种与JMS相关的代码
public sendMessage(/*一些传递的参数*/){
//TODO发送消息。
}
public boolean isType(String type){
if(type.equals("JMS")){
return true;
} else {
return false;
}
}
}

public class LogService implements IListener{
//TODO:各种与Log相关的代码
public sendMessage(/*一些传递的参数*/){
//TODO写Log。
}
public boolean isType(String type){
if(type.equals("LOG")){
return true;
} else {
return false;
}
}
}

public class SMSService implements IListener{//新增代码
//TODO:各种与Log相关的代码//新增代码
public sendMessage(/*一些传递的参数*/){//新增代码
//TODO发送SMS。//新增代码
}//新增代码
public boolean isType(String type){//新增代码
if(type.equals("SMS")){//新增代码
return true;//新增代码
} else {//新增代码
return false;//新增代码
}//新增代码
}//新增代码
}//新增代码

public class InitClass{
public void init(){
MockUserManager um=new MockUserManager();
um.addListener(new JMSService());
um.addListener(new LOGService());
um.addListener(new SMSService());//新增代码
}
}[/code:1]
修改代码行数(1+1+1)×3=9
初始代码+总修改行数=84+9=93

我们设未知数X,当84+9X=54+12X时,X=10
也就是说,当我们需要增加的观测者超过10个时,Observer体现出效益。
   
0 请登录后投票
最后更新时间:2004-04-14
写到这里,我必须承认,透明的文章的最关键的部分(AOP实现部分),我没有看懂,也不知道怎么样写出他的代码,以及计算这个方案的修改代码数量。

如果有对AOP的人,能够给出一个代码示例来,我将不胜感谢。
   
0 请登录后投票
最后更新时间:2004-04-14
1、在一个真实运行的系统中,都存在对象从哪里来,放在哪里的问题,这些问题的解决都是需要代码行数的,而这些行数,随着设计方案的不同,也是不同的,这里就简化了。

2、透明的例子里,没有告诉我们,如何用Observer模式实现有选择的广播,因此我加上了type的属性。

3、Massge对象的代码没有加入,因为说不清需要多少行。

4、每个对象都需要import一些类,或者包。这些也应该属于代码行数的计算范围,这里也简化了。否则Observer相对与原始的方式,需要引入更多的类,还会增加更多的代码行数。
   
0 请登录后投票
最后更新时间:2004-04-17
其实透明的这篇文章想要说明的问题也就一点,就是使用AOP可以将组装消息和调用obersver.broadcast()方法分离出业务逻辑代码。这个例子不能减少增加一个新的observer需要写的代码量。它减少的是你在业务逻辑中需要写入组装消息和调用广播方法的代码。而这只是使用AOP带来的一个副作用而已,主要的目的是分离广播这个关注点。
   
0 请登录后投票
最后更新时间:2004-07-25
庄表伟 写道
1、在一个真实运行的系统中,都存在对象从哪里来,放在哪里的问题,这些问题的解决都是需要代码行数的,而这些行数,随着设计方案的不同,也是不同的,这里就简化了。

2、透明的例子里,没有告诉我们,如何用Observer模式实现有选择的广播,因此我加上了type的属性。

3、Massge对象的代码没有加入,因为说不清需要多少行。

4、每个对象都需要import一些类,或者包。这些也应该属于代码行数的计算范围,这里也简化了。否则Observer相对与原始的方式,需要引入更多的类,还会增加更多的代码行数。


行数在这里不重要,重要的是对修改是封闭的,对扩展是开放的,你增加新功能不需要修改原来的代码,可以减少回归测试的开销和减少引入新的Bug的可能,很好的实现对象组合,避免继承爆炸和代码的copy and paste
   
0 请登录后投票
最后更新时间:2004-07-29
[code:1]
class UserManager {
    public User userLogin(){
    }
}
[/code:1]
userLogin() 的命名是过程式的,放在这里感觉很奇怪。
   
0 请登录后投票
最后更新时间:2004-08-03
模式是死的
   
0 请登录后投票
论坛首页 Java版 企业应用

跳转论坛:
JavaEye推荐