博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

颠覆传统-面向对象的设计思想(牛刀小试)

Posted on 2007-08-09 17:45  我是程序员  阅读(3577)  评论(10编辑  收藏  举报
在今天的随笔中,我将会通过一个实例来说明如何具体的进行面向对象设计。
  • 案例
    现在需要为一个正式运营的系统开发一套日志处理工具,这个工具的目的是收集分散在各个地方的系统执行日志信息,并且按照一定的规则将这些日志信息合并到同一个日志中。以下是这个系统的网络拓扑图:

    目前最迫切需要处理的日志信息分别为:位于负载均衡服务器上的日志信息(命名为LogA,格式为Squid格式的文本文件)、位于两台Web服务器上的日志信息(分别命名为IIS_Log1和IIS_Log2,格式都为IIS日志格式的文本文件)。需要合并的日志信息包含上述的日志信息,但是不仅限於上述的日志信息。日志合并的结果保存为Squid格式的文本文件,以方便使用第三方的日志分析工具进行分析。
  • 相关背景
    • 负载均衡服务器日志内容(部分):请求的时间(格林威治时间)、请求的Url、请求源IP(客户IP)、重定向的地址(重定向到哪台Web服务器)
    • Web服务器日志内容(部分):请求的时间(本地时间)、请求的Url,请求源IP(负载均衡服务器IP)
    • 监控服务器每五分钟请求一次负载均衡服务器(请求的页面是固定的),用于判定服务器运行是否正常(可以使用这个请求作为对齐标志来匹配日志信息)
    • 负载均衡服务器的日志顺序与IIS日志的顺序不一定匹配,例如在负载均衡服务器上的日志顺序为RequestA、RequestB,在IIS日志中的顺序有可能为RequestA、RequestB也有可能为RequesB、RequestA。
  • 补充说明
      1. 假定日志的匹配算法已经解决。
      2. 日志的处理结果需要保存到三个日志文件中:匹配的、未匹配以及被拦截的(负载均衡服务器拦截的),这三个日志文件的的格式不一定相同。
  • 案例行为(Act)整理
    • 日志处理活动图(日志处理的活动顺序不一定相同)

    • 行为(Act)分析
    根据上述的日志处理活动图,我们可以很简单甚至是很随意的的确定日志处理过程中的几个潜在的关键活动:拷贝日志文件、匹配日志条目,输出日志条目。经过初步的筛选,我们就可以发现“考虑日志文件”这个活动与日志处理这个大的目标之间的关系不是很密切,可以简单的认为是日志处理的前置条件,可以暂时不考虑,那么现在需要重点考虑的活动是“匹配日志条目”和“输出日志条目”。好,根据上面的分析,我们绘制出如下的处理流程图:

我们现在根据处理示意图来仔细考虑一下,这个示意图是否能够很好的描述日志处理活动?是不是感觉缺失了一环?没错,就是缺少了日志行读取的行为!好,现在我们补充上日志读取的这一环,新的处理流程图如下:

    好的,至少这个处理流程在表面上可以跑的通了。好的,我们现在再来考察流程图中每一个处理(Process)在问题域中的原子性或者说层次。其中读取日志条目这个处理过程,没有什么问题,可以简单的描述为一个方法,例如ReadLogRow(我认为判断一个处理在问题域中是否是原子性的,只需要判断这个处理是否可以描述为一次方法调用);对于输出日志条目这个处理过程,也没有问题,也可以简单的描述为一个方法调用,例如WriteLogRow;但是对于匹配日志条目这个处理过程,似乎没有办法描述为一个方法调用。
    简单的分析一下,发现要实现匹配日志条目这个处理过程至少需要包含以下两个动作序列:合并两个IIS日志,将IIS日志与负载均衡服务器日志合并,并且合并日志条目这个处理过程包含了读取日志条目的处理过程。也就是说从逻辑层次角度看来,匹配日志条目的层次比读取日志条目的层次要高,以下的图示很清楚的说明了这个问题(两个图分别说明了两种不同的处理方式):

    说明:上面的分析都基于一个用例包含的活动的粒度都应该处于同一个逻辑层次 ,与常规的面向对象的分析不同的是我采用的是自顶向下的分析方式,而不是常规的面向对象分析采用的自底向下的分析方式。至于自顶向下与自底向上的分析方式哪个好,是属于一个仁者见仁,智者见智的问题,没有什么好争论的。之所以我会采用这样一个分析方法的原因是为了适应用例的分析方法,通过分析用例的每一个交互(或者说活动)来分析出高层次的行为,然后对行为进行抽象(我的经验是行为抽象为接口,名词抽象为实体),这样做有以下几个好处:
  • 能够很自然的贴合用例,便于跟踪需求与设计之间的关系
  • 自顶向下的分析方式符合大多数人的习惯
  • 抽象的层次清楚,易于理解和使用
  • 抽象了行为很自然的符合面向对象的依赖导致原则
  • 基于实际需求场景的行为分析,容易保证抽象符合单一职责原则
    我们从上面的图示中很容易的抽象出最高层次(就当前的活动)行为是“输入日志”和“输出日志”,抽象的依据可以看下面这幅示意图。

    提取抽象的过程其实就是建模的过程,我们建立一个模型之后,也需要跟写代码一样需要进行测试,对于模型的测试可以通过各种场景(在面向对象中就是用例)来进行测试,看模型是否能够通过所有的应用场景。下面为场景的测试例子(在本例中使用时序图做为测试方法):

    在测试通过之后就可以很容易的做设计了,以下为类设计的代码(如果熟悉设计模式的话,可以看出来这个地方引入了装饰模式):
 1 /// <summary>
 2 /// 描述日志列的信息
 3 /// </summary>
 4 public class LogField
 5 {
 6 }
 7 
 8 /// <summary>
 9 /// 描述了日志行的信息
10 /// </summary>
11 public class LogRow
12 {
13 }
14 
15 /// <summary>
16 /// 日志源
17 /// </summary>
18 public interface ILogSource
19 {
20     /// <summary>
21     /// 源日志列定义信息
22     /// </summary>
23     System.Collections.Generic.ICollection<LogField> Fields;
24 
25     /// <summary>
26     /// 当前的日志行
27     /// </summary>
28     LogRow CurrentRow;
29 
30     /// <summary>
31     /// 读取日志行
32     /// </summary>
33     /// <returns>读取结束的时候返回False</returns>
34     bool ReadLogRow();
35 }
36 
37 /// <summary>
38 /// 日志输出
39 /// </summary>
40 public interface ILogOutput
41 {
42     /// <summary>
43     /// 目标日志的定义信息
44     /// </summary>
45     System.Collections.Generic.ICollection<LogField> Fields;
46 
47     /// <summary>
48     /// 写入日志信息
49     /// </summary>
50     /// <param name="row"></param>
51     void WriteLogRow(LogRow row);
52 }
    根据日志合并的要求需要引入以下的几个类:

 1 /// <summary>
 2 /// 日志合并日志源,用于处理两个日志之间的Union
 3 /// </summary>  4 public class LogUnionSource : ILogSource
 5 {
 6 }
 7 
 8 /// <summary>
 9 /// 日志Merge日志源,用于处理两个日志源之间的Merge
10 /// </summary>
11 public class LogMergeSource : ILogSource
12 {
13 }
    对于日志字段数据的处理可以将逻辑放入到不同的字段类中。
    由于这一阵子事情比较多,暂时就写到这里了。
相关随笔:
  1. 颠覆传统-面向对象的设计思想(序章)
  2. 颠覆传统-面向对象的设计思想(序章续)
另外补充一点:
使用地方的处理代码,从以下的代码可以看出这样设计和容易利用IOC框架或者配置驱动:
 1 //创建IIS合并日志源
 2 dim iisLogSource As LogUnionSource = new LogUnionSource ( new SimpleTextLogSource( IIS1 ),new SimpleTextLogSource( IIS2 ) )
 3 
 4 //创建合并日志源
 5 dim arrayLogSource as LogMergeSource = new LogMergeSource( iisLogSource, new SimpleTextLogSource( 防火墙日志 ))
 6 
 7 //创建日志输出
 8 dim logOutput as SimpleTextLogOutput = new SimpleTextLogOutput( FileName& nbsp;);
 9 
10 //写入日志
11 while arrayLogSource.ReadLogRow
12    
13    //写入日志条目
14    logOutput.WriteLogRow( arrayLogSource.CurrentRow )
15 end while