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

论面向组合子程序设计方法 之 南无阿弥陀佛

阅读更多
其实,前面我还忘了提一个非常重要的基本组合子:singleton。
这里补充提一下:
class SingletonComponent implements Component{
  private final Component c;
  private Object val;
  public Class getType();{
    return c.getType();;
  }
  public synchronized Object create(Dependency dep);{
    if(val!=null); return val;
    val = c.create(dep);;
    return val;
  }
  public synchronized Class verify(Dependency dep);{
    if(val!=null); return val.getClass();;
    else return c.verify(dep);;
  }
} 

代码没什么可说的,就是最简单的singleton模式。

用这个组合子,我们可以对任意的Component做singleton。

下面接着说monad。

有了bind,很多的功能都可以自然推演出来了。

比如我们前面用来刁难pico的那个例子,甚至,为了更强调复杂性,我们可以给B和A再另外增加一些参数,这些参数要求从容器解析(毕竟,我们之所以需要容器,就是为了自动解析一些依赖关系,要是全部依赖关系都hard-code,意义就不大了):
void A createA();{
  B b = new B(...);;
  return new A(b,b, ...);;
}

用bind,我们的思路可以是这样:
1。用B的构造函数生成一个Component。
2。这个Component生成一个对象,
3。这个产生的对象被传递给一个对应A的Component当作参数。这一步可以用bind来搞定。


Component b_component = Components.ctor(B.class);;
return new BoundComponent(b_component, new Binder();{
  public Component bind(Object b);{
    final Component arg = Components.value(b);;
    return new WithArgument(
      new WithArgument(a, 0, arg);,
    1, arg);;
  }
});;


Components.value(Object)是我们写的一个对ValueComponent的封装静态函数。
为了避免总写冗长的new SomeComponent(...),我们把一些常用的基本Component都写成名字较短的静态函数,放在Components类里面。

这样,我们可以写Components.value(obj),而不是new ValueComponent(obj)。
要是觉得敲键盘还是麻烦,你甚至可以创建一个Components对象cc。然后到处用这个对象:
cc.value(obj)。舒服些了吧?


从上面的例子,我们可以看到,那个直接创建对象的createA函数中的两个步骤,在我们高阶的Component中也被分为两部。
而在两个步骤之间的信息传递(那个b变量,从第一个步骤取得,然后在第二个步骤使用),则被用bind操作实现了。

到这,也许我们该伸伸懒腰了。舒服地往椅子背上一靠,说:“啊。终于干完了!我可以用高阶逻辑来模拟任何直接硬编码创建对象的逻辑了”。

这话倒也没错,有了bind,我们不再被局限于“构造函数注射”,“setter注射”,“静态工厂注射”等寥寥几个注射方式;我们甚至可以对所谓的ioc type嗤之以鼻:“什么type1, type2?不过是我们可以处理的无数种情况中的几种特例而已!”。
我们可以处理if-else,可以处理循环,递归,任何可以直接用java写出来的对象创建方式,我们都可以在高阶逻辑上得到对应的组合版本,只要我们有足够的原子组合子。(所谓原字组合子,不过是:FunctionComponent, BeanComponent,ValueComponent几种)

比如,对应于:

X createX();{
  A a = A.instance(...);;
  if(a.isX(...););{
    return new X(...);;
  }
  else{
    return new Y(a, ...);.getX(...);;
  }
}

这里,所有的省略号都代表可能需要从容器解析的参数。使用高阶Component对象而不是直接调用createX()函数的一个原因,就是我们想要把依赖解析隐藏起来并且集中灵活地配置和管理。


对此,我们可以写成:
Component a_component = Components.static_method(A.class, "instance");;
return new BoundComponent(a_component, new Binder();{
  public Component bind(final Object a);{
    final Component isx_component = Components.method(a, "isX");;
    return new BoundComponent(isx_component, new Binder();{
      public Component bind(Object isx);{
        final Boolean v = (Boolean);isx;
        if(v.booleanValue(););{
          return Components.ctor(X.class);;
        }
        else{
          final Coponent y_component = 
           new WithArgument(Components.ctor(Y.class);, 0, 
              Components.value(a););;
          return new BoundComponent(y_component, new Binder();{
            public Component bind(Object y);{
              return Components.method(y, "getX");;
            }
          });;
          
        }
      }
    });;
  }
});;

稍微有点绕,如果你到此有点糊涂的话,请重温一下前面的简单的bind的例子,只要体会了bind的具体意义,上面的代码不过是几层bind的嵌套。


好,如果你理解了bind,那么应该能够看懂上面的这段代码了。它其实就是那个createX函数的严格翻译。

功能确实很强大了,就是这代码写起来这个啊!对比一下createX和这个高阶版本吧。我发现如果我多看几眼这个所谓的"co"的代码,我简直都要吐!如果说createX这个函数的代码是正常人说话,那么这个高阶代码就是唐僧念经:“南无阿弥陀佛,南无阿弥陀佛,南无阿弥陀佛...”,天啊!


如果我们真要Combinator-oriented起来,难道要整天写这种蹩脚代码?是不是我们吐啊吐的就会习惯了呢?
pico的各个ComponentAdapter其实倒也就是这么写,可是pico没有bind,你很少需要写这么深的嵌套,甚至很少需要写匿名类。
如果我们把我们的组件系统比喻作pascal语言的话,pico的那些decorator充其量不过是一个dos的批处理,不,远不如批处理灵活,应该也就是一个简单的用户界面上的几个按钮。

那么有没有什么办法来简化语法呢?

倒是有一个想法:
1。把Component从接口变成一个抽象类。然后把一些常用的二元组合,比如bind,比如withArgument,withProperty,比如method,ifelse,都放在这个抽象类里面。这样,

我们就可以避免写:
new SingletonComponent(c),而写c.singleton()。
我们就可以避免写:
new BoundComponent(c1, ...),而写:c1.bind(...)。
可以避免写:
new WithArgument(c, 0, arg),而写:c.withArgument(0, arg)。
可以避免写:
new BoundComponent(c1, new Binder();{
  public Component bind(Object obj);{
    return Components.method(obj, "method");;
  }
});;


而写成:
c1.method("method");;


可以避免写:
new BoundComponent(c1, new Binder();{
  public Component bind(Object obj);{
    if(((Boolean);obj);.booleanValue(););{
      return a;
    }
    else return b;
  }
});;

而写成:
c1.ifelse(a,b);;

等等等等。

这样做,从架构上确实有点损害,我们牺牲了“围绕接口”的原则,而改为围绕抽象类了。

但是,从实际效果考虑,我发现它损失的架构上的美感,远远比不上它带来的编码上的方便程度。谁让我们用的是java呢,世上没有十全十美的事情,就凑合吧。

经过这个改动,上面的对应createX的高阶代码变成:

Component a_component = Components.static_method(A.class, "instance");;
return a_component.bind(new Binder();{
  public Component bind(final Object a);{
    final Component isx_component = Components.method(a, "isX");;
    return isx_component.ifelse(
      Components.ctor(X.class);,
      Components.ctor(Y.class);
        .withArgument(0, Components.value(a););
        .method("getX");
    );;
  }
});;


稍微好些了。而如果我们不需要给Y的构造函数指定参数,那么效果还会更好。
比如对
X createX();{
  A a = A.instance(...);;
  if(a.isX(...););{
    return new X(...);;
  }
  else{
    return new Y(...);.getX(...);;
  }
}

高阶代码会变成:
Component a_component = Components.static_method(A.class, "instance");;
return a_component.method("isX");.ifelse(
    Components.ctor(X.class);,
    Components.ctor(Y.class);
      .withArgument(0, Components.value(a););
      .method("getX");
);;

又简洁了不少。


当然,说实话,如果我们把情况任意复杂化,比如:

Y createY();{
  a = A.createA(...);;
  b = B.createB(a, ...);;
  c = C.createC(a,b,...);;
  return Y.create(a,b,c,...);;
}

要对createY写出高阶对应版本,这bind要嵌套三层,代码无论如何不可能好看了。对此,我们只能耸耸肩说:无能为力了。因为我们这里已经接触到了java语言的底线。

值得欣慰的是,至少:
1。对简单需求,比如pico能够处理的那些,我们的语法并不比pico麻烦。
2。对复杂需求,pico不能处理,而只能通过自己实现ComponentAdapter实现;而我们的co构建出来的系统,在没有剥夺你自己实现Component的前提下,也提供了采用声明式的语法来组合的方式。至于是选择用熟悉的java语法来过程式地自己处理依赖,还是用声明式的高阶逻辑来仍然让系统处理依赖,则是程序员的自由了。

我们推荐,除非对非常复杂的需求,还是用声明式的组合来处理更好。


写到这里,不得不唠叨一些语言了。就象是你也可以在c这个过程语言里面使用一些oo的技巧一样,我们在java这个oo语言里面是可以使用一些co的技巧的。

只不过,缺乏语言上的良好支持,让我们在采用co设计的时候的代价有所增大。如何权衡?是co带来的缺点(不方便调试,运行效率低,语法麻烦)大,还是它带来的好处(灵活应对变化,减少代码数量,方便重用)大,则是一个需要主观经验决定的事情了。



其实,在一个真正支持monad组合子的语言里面,createY会被类似写成这样:
do
  a <- static_method(A.class, "createA");;
  b <- static_method(B.class, "createB");
    .withArgument(0, a);;
  c <- static_method(C.class, "createC");
    .withArgument(0, a);.withArgument(1, b);;
  return (static_method(Y.class, "create");
    .withArgument(0,a);.withArgument(1,b);.withArgument(2,c);;
  );



所有的Binder匿名类会被自动生成。
这叫"do-notation",是haskell里面用来方便处理monad组合子的利器。

在我开发的jaskell语言里面,对do-notation有类似的支持。


题外话:
最近,看到老庄设计的DJ里面说要支持co。我觉得,如果仅仅象java这样的所谓“支持”,那就和用C的函数指针号称支持OO一样无趣了。

一个可以说得上对co有支持的语言,即使不直接支持do-notation,也应该把写匿名类的代价降到和一个lamda函数相接近的程度。
即使我不能写

a <- createA
b <- createB a


也要能够写成:

createA >>= \a->createB a



组合并不仅仅是几个简单的decorator套起来。真正复杂的co里,不同组合子之间是需要通过bind来通信的。而组合子之间的通信能力才是co强大的根源。
分享到:
评论

相关推荐

    Java面向对象程序设计(第二版)

    不过,我可以基于《Java面向对象程序设计(第二版)》这个标题和标签,来详细说明Java面向对象程序设计中可能涵盖的知识点。 在深入介绍之前,首先需要明确面向对象程序设计(OOP)是一种编程范式,它使用“对象”...

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

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

    面向数据设计的概念Jackson系统开发方法教学目的公开课获奖课件.pptx

    面向数据设计概念是软件设计领域中的一种重要方法论,它强调按照问题数据构造定义一组映射,把问题数据构造转换为问题解程序构造。这种方法论侧重于问题数据构造,把程序构造设计成与问题数据构造一致,不强调模块...

    基于微信小程序的点餐系统设计与实现 毕业论文.docx

    本文档是一篇关于基于微信小程序的点餐系统设计与实现的毕业论文,旨在利用微信小程序这一日益普及的技术,优化餐厅点餐流程,提供便捷的在线点餐服务。论文详细介绍了系统的开发背景、技术选型、需求分析、系统设计...

    潭浩强C++面向对象程序设计

    潭浩强C++面向对象程序设计 很好的一版PPT教程

    Visual C# 2010程序设计教程(教程PPT+源代码)

    Visual C# 2010程序设计教程》详细介绍了Visual C# 2010程序设计的基础知识、基本方法和应用技巧,共分14章,主要内容包括.NET平台与Visual Studio 2010开发环境、C#语言基础及面向对象程序设计、C#程序设计、C# Web...

    趣味C程序设计集锦.pdf

    《趣味C程序设计集锦》作为计算机C程序设计的科普读物与学习C语言程序设计的教学参考书,着眼于应用C程序设计求解问题的基本方法与技巧,提高通过C程序设计解决实际问题的能力。《趣味C程序设计集锦》以各类趣题的C...

    张玉生《C语言程序设计》双色版 C语言程序设计理论教材习题参考答案.pdf

    张玉生编写的《C语言程序设计》双色版是一本针对初学者的C语言理论教材,它包括了C语言的基础知识、语法结构、数据类型、控制结构、函数、指针、数组、字符串等核心技术内容。该教材不仅适合自学,同时也适合作为...

    《GPS应用程序设计》源代码

    《GPS应用程序设计》源代码。本软件是专为《GPS应用程序设计》一书配套发行的。包括: 1、 data_log.c 数据采集程序 2、 rinexout.c RINEX数据格式写入子程序 3、 to_rinex.c 数据格式转换 4、 sav_pos.c 卫星位置...

    MFC Windows程序设计(第2版修订版)--源代码

    本书的作者,jeff prosise,用其无与伦比的技巧向读者讲述了mfc程序设计中的基本概念和主要技术——再次阐释了在32位windows平台上进行了快速的面向对象开发的完美方法。  本书涵盖了以下专题:  事件驱动程序设计...

    JAVA程序设计教程-电子教案

    《JAVA程序设计教程》电子教案是一份全面介绍Java编程语言的教学资源,旨在帮助学习者掌握Java编程的基础知识和高级概念。这份教程涵盖了从简单的语法结构到面向对象编程、异常处理、集合框架、多线程、输入输出流...

    ARM原理与C程序设计

    《ARM原理与C程序设计》是一本面向学习嵌入式系统和ARM处理器技术的书籍,旨在帮助读者深入理解ARM架构并掌握在该架构上进行C程序设计的方法。ARM处理器是目前广泛应用于移动设备、嵌入式系统以及服务器等领域的核心...

    Windows程序设计(第2版)王艳_源代码

    书籍目录: 第1章 Windows程序设计基础   1.1 必须了解的内容   1.2 VC++的基本使用  1.3 本书推荐的编程环境  1.4 代码的风格  第2章 Win32程序运行原理   2.1 CPU的保护模式和Windows系统  2.2 ...

    Delphi7 高效数据库程序设计

    Delphi作为一个强大的面向对象的Windows应用程序开发工具,其集成的VCL(Visual Component Library)库为数据库编程提供了丰富的组件和接口,使得数据库应用的开发变得更加便捷。 首先,Delphi7中的ADO(ActiveX ...

    Intel 汇编语言程序设计第五版源码

    《Intel 汇编语言程序设计第五版源码》是一份深入学习Intel汇编语言的重要资源,对于想要掌握计算机底层工作原理、优化代码性能或进行系统级编程的开发者来说,具有极高的价值。该资源包含了丰富的源码示例,帮助...

    GDI+图形程序设计.zip

    这本书《GDI+程序设计》显然是一个深入探讨GDI+技术的教程,它可能包含了GDI+的基础概念、核心组件、实例解析和实践代码。通过阅读这本书,开发者可以了解到如何利用GDI+来构建图形用户界面,进行图像处理,以及创建...

Global site tag (gtag.js) - Google Analytics