`
ajoo
  • 浏览: 454319 次
社区版块
存档分类
最新评论

论面向组合子程序设计方法 之 燃烧的荆棘

阅读更多
唧唧歪歪一大堆。肯定早有人不耐烦了。
"你丫还有没有点实在的东西呀?"
要是我,可能也早就忍不住了。

好,好。我其实并没有忘记前面说的那个logging的例子。卖了这么长时间的关子,除了有想形而上的虚荣心外,也是想给大家多一点时间来嚼一下这个例子,让熟悉OO的朋友肚子里面多少有个腹稿。

下面,我来继续上回书说到的这个logging。

前面列举了那么一大堆乱七八糟的需求,不知道是不是有人和我一样看着这些繁杂的条目闹心。我在做的时候其实只想了五六条需求,就已经开始烦了。何况还有一些暂时不知道如何抉择的几个疑问点。最初Kiss出来的那个logger实现明显不能用了。refactor的价值也有限。

怎么办?智慧果也许给了我们智慧,但是这智慧毕竟是有限的。侧头看去,蓦然看见荆棘里跳动的火焰,那是上帝的光芒么?

好,既然这条路看来比较坎坷,换条路试试看也未尝不可。走这条路的好处在于,我们可以暂时不用理会这些讨厌的“需求”了。CO这个方法论不是以需求为中心,而是从底层的组合子出发,就像小孩子摆弄积木,我们只管发挥自己的想象力,看看我们能摆弄出些什么东西来就好了。


如果一切顺利,我们希望能够跨越瀚海高山,走到流着奶和蜜的土地。所有这些乱七八糟的需求,我们希望他们能够被简单地配置,或者至少能够通过声明式的方法来“声明”,而不是通过过程式的方法来“实现”。

出发之前,鼓舞以下士气先。
前面charon说,这种东西类似公理系统。确实,非常非常类似。不过,门槛并不像这个名字听来那么吓人的高。我们并不需要一下子就完全把完备性考虑清楚,我们不是上帝,没有这么大本事。

类似于xp + refactoring,我们的组合子的建造也一样可以摸着石头过河,走一步看一步的。

比如,假如我的Logger接口定义的时候没有print函数而只有println,那么后面我们会发现在制造打印当前时间的组合子的时候会出现麻烦。不过,没关系,等到意识到我们需要一个print()函数的时候,回过头来refactor也是可以的。


为了说明这点,我们现在假设Logger接口是这样:

interface Logger{
  println(int level, String msg);;
  printException(Throwable e);;
}


好,路漫漫而修远兮,我们就这样按照上帝的昭示上路吧,虽然前路云封雾锁。



首先,任何组合子系统的建立,都起步于最最基本最最简单的组合子。然后再一步步组合。

比如,布尔代数必然起步于true和false;自然数起步于0,1,然后就可以推演出整个系统。

那么,对Logger来说,有0和1吗?

有的。

0就是一个什么都不做的Logger。

class NopLogger implements Logger{
  public void println(int level, String msg);{}
  public void printException(Throwable e);{}
}


(注,不要小看这个什么都不做的Logger,它的作用大着呢。你能想象一个没有0的整数系统吗?)

什么是1呢?一个直接向文件中打印信息的应该就是一个1了。
伪码如下:

class FileLogger implements Logger{
  public void println(int level, String msg);{
    write msg to log file.
  }
  public void printException(Throwable e);{
    write exceptioon to log file.
  }
}

那么,怎么实现这个类呢?

这里,我们遇到一个东西要抉择,文件打开是要关闭的。那么谁负责打开文件?谁负责关闭?是否要在构造函数中new FileOutputStream()?,然后再提供一个close()函数来close这个stream?

答案是:不要。

无论是ioc也好,还是CO的组合方法也好,一个原则就是要尽量保持事情简单。那么,什么最简单?我们此处真正需要的是什么?一个file么?
不,其实一个PrintWriter就足够了。
当然,最终文件必须还是要通过代码打开,关闭。但是,既然反正也要打开文件,我们这里不去管也不会增加什么工作量,对么?那么为什么不乐得清闲呢?
“先天下之忧而忧”这种思想是最最要不得的。


于是,最简单的1应该是这样:

class WriterLogger implements Logger{
  private final PrintWriter writer;
  public void println(int level, String msg);{
    writer.println(msg);;
  }
  public void printException(Throwable e);{
    e.printStackTrace(writer);;
  }
  WriterLogger(PrintWriter writer);{
     this.writer = writer;
  }
}


“哈!还说什么CO。你这不是剽窃ioc pattern吗?”
完了,被抓个现形。还真是ioc pattern。其实,世界上本来没有什么新东西,所谓“方法论”,本来是一种思考方法,而不是说形式上必然和别人不同。不过,说我剽窃,我就剽窃吧。


好。最简单的原子写出来了。希望到现在,你还是会觉得:“介,介有什么呀?”


下面来看组合规则。都可以弄些什么组合规则呢?让我来掰着脚指头数一数。
有一点作为thumb of rule需要注意的:每个组合规则尽量简单,并且只做一件事

1。顺序。把若干个logger对象顺序写一遍,自然是一种组合方式。

class SequenceLogger implements Logger{
  public void println(int level, String msg);{
    foreach(l: loggers);{
      l.println(level, msg);;
    }
  }
  public void printException(Throwable e);{
    foreach(l:loggers);{
      l.printException(e);;
    }
  }
  private final Logger[] loggers;
  SequenceLogger(Logger[] ls);{
    this.loggers = ls;
  }
}



2。逻辑分支。当消息的重要程度等于某一个级别的时候,写logger1,否则写logger2。
class FilteredLogger implements Logger{
  private final Logger logger1;
  private final Logger logger2;
  private final int lvl;
  public void println(int level, String msg);{
    if(level==lvl);logger1.println(level, msg);;
    else logger2.println(level, msg);;
  }
  public void printException(Throwable e);{
    if(lvl==ERROR); logger1.printException(e);;
    else logger2.printException(e);;
  }
}

为了简洁,下面的例子我就不写构造函数了。


3。忽略。当消息的重要程度大于等于某一个值的时候,我们写入logger1,否则写入logger2。
class IgnoringLogger implements Logger{
  private final Logger logger1;
  private final Logger logger2;
  private final int lvl;
  public void println(int level, String msg);{
    if(level>=lvl);logger1.println(level, msg);;
    else logger2.println(level, msg);;
  }
  public void printException(Throwable e);{
    if(lvl<=ERROR); logger1.printException(e);;
    else logger2.printException(e);;
  }
}


其实,2和3本来可以统一成一个组合规则的,都是根据消息重要程度来判断logger走向的。如果我用的是一个函数式语言,我会毫不犹豫地用一个predicate函数来做这个抽象。
可惜,java中如果我要这么做,就要引入一个额外的interface,还要多做两个adapter来处理ignore和filter,就为了节省一个类和几行代码,这样做感觉不是很值得,所以就keep it stupid了。



对了。我说“CO不看需求”了么?如果说了,对不起,我撒谎了。
CO确实不是一个以需求为中心的方法论。它有自己的组合系统要关心。
但是需求也仍然在CO中有反映。我们前面提出的那些需求比如打印系统时间什么的不会被无中生有地实现。
只不过,在CO里面,这些需求不再影响系统架构,而是被实现为一个一个独立的组合子。同时,我们抛开需求之间复杂的逻辑关系,上来直奔主题而去就好了。

4。对exception直接打印getMessage()。

class ErrorMessageLogger implements Logger{
  private final PrintWriter out;
  private final Logger logger;
  public void println(int level, String msg);{
     logger.println(level, msg);;
  }
  public void printException(Throwable e);{
     out.println(e.getMessage(););;
  }
}


5。对了,该处理NeptuneException了。如果一个exception是NeptuneException,打印execution trace。

class NeptuneExceptionLogger implements Logger{
  private final PrintWriter out;
  private final Logger logger;
  public void println(int level, String msg);{
    logger.println(level, msg);;
  }
  public void printException(Throwable e);{
    if(e instanceof NeptuneException);{
      ((NeptuneException);e);.printExecutionTrace(out);;
    }
    else{
      logger.printException(e);;
    }
  }
}


6。对EvaluationException,照猫画虎。
class JaskellExceptionLogger implements Logger{
  private final PrintWriter out;
  private final Logger logger;
  public void println(int level, String msg);{
    logger.println(level, msg);;
  }
  public void printException(Throwable e);{
    if(e instanceof EvaluationException);{
      ((EvaluationException);e);.printEvaluationTrace(out);;
    }
    else{
      logger.printException(e);;
    }
  }
}


7。在每行消息前打印系统时间。
class TimestampLogger implements Logger{
  private final Logger logger;
  public void println(int level, String msg);{
    logger.println(level, new Date();.toString();+": " + msg);;
  }
  public void printException(Throwable e);{
    logger.println(ERROR, new Date();.toString();+": ");;
    logger.printException(e);;
  }
}

这个类其实可以再注射近来一个DateFormat来控制日期格式。但是这不是重点,我们忽略掉了它。
可是,这个类有一个很别扭的地方,在printException中,我们必须要把系统时间单独打印在一行,然后再打印异常信息。

这可不是我们想要的效果。我们本来是想让系统时间出现在同一行的。
怎么办?回头看看,如果Logger接口多一个print函数,一切就迎刃而解了。重构。

class TimestampLogger implements Logger{
  private final Logger logger;
  private boolean freshline = true;
  private void printTimestamp(int level);{
    if(freshline);{
      logger.print(level, new Date();.toString();+": ");;
    }
  }
  public void print(int level, String msg);{
    printTimestamp(level);;
    logger.print(level, msg);;
    freshline = false;
  }
  public void println(int level, String msg);{
    printTimestamp(level);;
    logger.println(msg);;
    freshline = true;
  }
  public void printException(Throwable e);{
    printTimestamp(ERROR);;
    logger.printException(e);;
    freshline = true;
  }
}


当然,前面的一些组合子也都要增加这个print函数。相信这应该不难。就留给大家做个练习吧。


好啦。到现在,我们手里已经有两个基本组合子,7个组合规则。应该已经可以组合出来不少有意义没意义的东西了。

为了避免写一大堆的new和冗长的类名字,我们做一个facade类,来提供简短点的名字,节省我们一些键盘损耗。

class Loggers{
  static Logger nop();{return new NopLogger();;}
  static Logger writer(PrintWriter writer);{
    return new WriterLogger(writer);;
  }
  static Logger writer(OutputStream out);{
    return writer(new PrintWriter(out, true););;
  }
  static Logger filter(int lvl, Logger l1, Logger l2);{
    return new FilteredLogger(lvl, l1, l2);;
  }
  static Logger ignore(...);{...}
  static Logger timestamp(...);{...}
  ...
}


这样,在用这些组合子的时候大概代码能够好看一点吧。


好了,走了这么远,(虽然很多人可能还是觉得根本没看见什么激动人心的东西吧?都是简单得不能再简单的小类,有什么了不起的呢?)。让我们停下脚步,看看这么信马由缰把我们带到了哪里吧。


下一回,我们会试着用这些组合子和组合规则来看看能不能对付前面提出的那些乱七八糟的需求。
分享到:
评论
1 楼 carlos23 2017-11-10  
我想知道用WriterLogger后,里面的writer在外面啥时候关闭合适?不然会内存泄漏吧

相关推荐

    《C#面向对象程序设计》源代码(CS)

    本书以面向对象的软件工程思想为主线,细致深入地讲解了C#语言面向对象程序设计的方法和技巧,内容涵盖面向对象的基本概念、基于接口的设计、泛型程序设计方法、Windows和Web应用开发,以及数据库访问技术。...

    面向对象程序设计C++期末考试题及答案

    很好的一套面向对象程序设计C++期末考试,适合于大学程序设计的各种考试复习

    金旭亮《C#面向对象程序设计》教案_1

    金旭亮《C#面向对象程序设计》教案之1:CSharp程序设计语言与dotNET面向对象程序设计概述。 后继教案将陆续发布,请关注作者的博客更新信息:http://blog.csdn.net/bitfan

    Java与UML面向对象程序设计.pdf

    《Java与UML面向对象程序设计》强调理论和设计相结合,重视对软件开发方法学有指导作用的重要概念。《Java与UML面向对象程序设计》可作为高等学校计算机科学系及软件学院高年级学生和研究生的教科书,也可作为从事...

    c++面向对象程序设计 谭浩强(第二版)习题答案

    C++是由C发展而来的,与C兼容。用C语言写的程序基本上可以不加修改地用于C++。从C++的名字可以看出它是C的超集。C++既可用于面向过程的结构化程序设计,又可用于面向对象的程序设计,是一种功能强大的混合型的程序设计...

    Visual C++面向对象与可视化程序设计 黄维通 课后习题答案程序及debug

    《Visual C++面向对象与可视化程序设计》是黄维通教授编著的一本经典教材,主要介绍了使用Microsoft的Visual C++编程环境进行面向对象程序设计和可视化应用开发的基础知识。这本书深入浅出地讲解了C++语言的核心概念...

    c++面向对象程序设计教程第三版陈维兴课后习题答案

    面向对象编程(Object-Oriented Programming, OOP)是一种编程范式,它以对象作为程序设计的基本单元,强调数据和操作数据的方法紧密结合。本教程第三版在前两版的基础上进行了更新和完善,更适合当前的编程环境和...

    金旭亮《C#面向对象程序设计》教案_2

    金旭亮《C#面向对象程序设计》教案_2:CSharp程序设计语言基础。此教案针对零面向对象基础的学生,包括PDF文档及相关的示例源码,VS2010格式。后续教案将陆续发布,请关注作者博客上的更新信息:...

    C++程序设计(谭浩强)PDF扫描版第3卷(共3卷)

    C++是近年来国内外广泛使用的现代计算机语言,它既支持面向过程的程序设计,也支持基于对象和面问对象的程序设计。国内许多高校已陆续开设了C++程序设计课程。但是由于C++涉及概念很多,语法比较复杂,内容十分广泛...

    金旭亮《C#面向对象程序设计》教案7_9:终结版

    本资源包容金旭亮《C#面向对象程序设计》教案的最后3讲:7 对象集合与对象组合;8 泛型编程;9 对象间的协作与信息交换。包容相关PDF文档及VS2010示例源码。请关注金旭亮博客以获取更多技术资源:...

    多核程序设计PDF

    作者都是长期供职于Intel公司的资深软件工程师和结构师,书中融入了他们自己丰富的软硬件开发经验,可以为面向多核体系结构进行并行程序设计的开发人员提供巨大的帮助。不论对从未接触过并行程序设计的开发人员,...

    IBM PC汇编语言程序设计

    第五、六章叙述循述循环、分支、子程序等基本程序结构以及程序设计的基本方法和技术;第七章为宏汇编技术;第八章说明以中断为主的输入/输出程序设计方法;第九章介绍BIOS和DOS系统功能调用的使用方法;第十~十二章...

    完整版C语言程序设计TXT电子书

    1.5 面向对象的程序设计语言 1.6 C和C++ 1.7 简单的C程序介绍 1.8 输入和输出函数 1.9 C源程序的结构特点 1.10 书写程序时应遵循的规则 1.11 C语言的字符集 1.12 C语言词汇 1.13 Turbo C ...

    8086/8088汇编语言程序设计教程

    基础部分主要以8086/8088微处理器和DOS操作系统为学习平台,介绍汇编语言基础知识、寻址方式、指令系统和程序设计方法。提高部分则是针对80386微处理器及其保护模式下的编程技术和细节。最后,上机实验指导部分为...

    Java程序设计习题集下载

    习题集内容覆盖面广,包括:Java言的基本常识、基本语法、面向对象的基本概念、数组、字符串、异常处理、文件和数据流、图形用户界面设计、小应用程序、线程、编程规范、网络程序设计、多媒体民图形学程序设计以及...

    面向对象编程的小项目,希望读者少走弯路

    1. 封装:封装是面向对象编程的核心原则之一,它将数据和操作数据的方法绑定在一起,隐藏内部实现细节,只对外提供公共接口。通过访问修饰符(如public、private、protected),我们可以控制对类成员的访问权限,...

    钱能- c++程序设计教程习题答案

    总的来说,《钱能-C++程序设计教程习题答案》是学习C++的重要辅助资料,它可以帮助你巩固理论知识,提升编程技能,为你的编程之路打下坚实基础。无论你是自学还是在课堂上学习,都应该充分利用这样的资源,将理论与...

    程序设计初步(2013级-C++程序设计)

    面向过程程序设计是一种编程范式,它是基于过程的,将问题分解为一系列的算法和数据结构,并通过这些过程来解决问题。而算法则是解决问题的一系列步骤,是程序设计中的核心。在C++程序设计中,我们通常会讨论到以下...

    java程序设计之网络编程第二版课后习题答案

    Java程序设计涉及多个方面,包括程序结构、类的使用、网络编程、程序开发流程、JDK环境配置、语言特性和面向对象编程等核心概念。以下是对这些知识点的详细解释: 1. **程序结构**:Java程序由一个或多个类组成,不...

Global site tag (gtag.js) - Google Analytics