d作者d语言中组件式编程
D编程语言中组件编程
作者:沃尔特.布莱特(Walter Bright)
 我们一直在尝试编写可重用软件.作者35年的编程经验,但可重用的很少.复制/粘贴不算,我在某处可能错过了,其他程序员也经常有同样感觉.不是缺少实践,也不是现在比过去强,应该有深入理解.
 有问题的样儿:抽象有漏洞,依赖其他代码,组件太具体(对A适用,但对B不适用),要回到问题:
什么是组件?
● 不仅仅是可重用软件,有很多非真正组件的可重用代码库.
 ● 组件有预定义接口.这样,即使单独开发的,也可交换,添加和组合他们.
 ● 多数库暴露接口,但连接至其他库,要求助手.
 那么,
什么是通用组件接口?
● 读输入
 ● 处理输入
 ● 写输出
 即使程序不是,但子系统适用该模型,伪码表示:
源 => 算法 => 汇
或这样:
源 => 1算法 => 2算法 => 汇
在联操的文件和过滤命令行模型上见过.
 ● 非常成功和强大
 ● 按"文件接口"的对接C
 ● 文件既是源又是汇
 ● 算法是"过滤器"
 ● 管道连接它们
 ● 甚至还有伪文件系统优势
缺点
● 进化而非设计
 ● 按字节流对待数据
 ● 对随机访问算法,不适用
 ● 但它显示了组件是什么,及分发.
看我代码
    void main(string[] args) 
    { 
        string pattern = args[1]; 
        while (!feof(stdin)) 
        { 
            string line = getLine(stdin); 
            if (match(pattern, line)) 
                writeLine(stdout, line); 
        } 
    } 
它不像源 => 算法 =>汇.进入旋涡而非组装厂.这样更好:
  void main(string[] args) 
  { 
      string pattern = args[1]; 
      stdin => byLines => match(pattern) => stdout; //输入,按行,匹配,输出
  } 
下一个设计
● C++面向对象,未导致更好组件编程
 ● C++ iostreams,通过重载>>运算符,开始看起来像源 => 算法 =>汇,但仍然是读/写文件.
 ● 许多成功的C++库,但它们不是这里讨论的组件.
 ● 革命性的C++迭代器和算法,STL标准模板库:不仅仅是文件,算法,通用接口,编译成高效代码:
   for (i = L.begin(); i != L.end(); ++i) 
       ... 用(*i)干活 ... 
仍像循环,然后有std::for_each(),std::transform(),但不能组合,因为迭代器要成对出现.
回到绘图板
● 源:流,容器,生成器
 ● 算法:过滤,映射,缩减,排序
 ● 汇:流,容器
| 源 | 算法 | 汇 | 
|---|---|---|
| file | sort | file | 
| tree | filter | tree | 
| array | map | array | 
| socket | reduce | socket | 
| list | max | list | 
| iota | search | |
| 随机数 | 奇数,数单词 | 
总结要求
● 可组合性
 ● 支持强大封装
 ● 生成工业品质高效代码
 ● 自然语法:源=>算法=>汇
 ● 可同未知类型一起用
输入区间要求
 
● 是否有可用数据?bool为空;
 ● 读当前输入数据:E front;.
 ● 步进至下一数据:void popFront();.
输入区间非类型
 
● 它是概念.
 ● 需要3个原语:empty,front,popFront
 从stdin读符:
    private import core.stdc.stdio; 
    struct StdinByChar {//三个原语
        @property bool empty() { 
            if (hasChar)
                return false; 
            auto c = fgetc(stdin); 
            if (c == EOF) 
                return true; 
            ch = cast(char)c; 
            hasChar = true; 
            return false; 
        } 
        @property char front() {  return ch;  } 
        void popFront() { hasChar = false;  } 
      private: 
        char ch; 
        bool hasChar; 
      }                                  
从stdin读,写至stdout:
       for (auto r = StdinByChar(); 
           !r.empty; 
           r.popFront())
       {//注意原语.
          auto c = r.front; //这里
          fputc(c, stdout); 
       } 
加点语言神奇:
    foreach (c; StdinByChar())
       fputc(c, stdout); 
       //注意,这里无类型
前向区间
加了个保存属性:
    @property R save; 
(R为前向区间类型) 
返回位置副本,而非数据.原与副本都可独立遍历区间.单链表是典型例子.归并排序用前向区间.
双向区间
加了个属性与方法:
    @property E back; 
    void popBack(); 
类似front和popFront,但反向工作.双链表是典型,UTF-8和UTF-16也是双向编码.
随机访问区间
加上:
    E opIndex(size_t I); 
用[]来索引数据,并加了2个选项:
 1,BidirectionalRange(双向区间)的@property size_t length;长度属性,可为无穷.
 2,无穷的ForwardRange(前向区间).对无穷区间,空的总为假.
汇(输出区间)
有个(put)放方法:
    void put(E e); 
放E类型的e进区间.
输出区间写入标准输出
 
struct StdoutByChar { 
    void put(char c) { 
        if (fputc(c, stdout) == EOF)
            throw new Exception("标出错误"); 
    } 
} 
回忆早先:
    foreach (c; StdinByChar())
        fputc(c, stdout); 
用输出区间,变成:
    StdoutByChar r; 
    foreach (c; StdinByChar())
        r.put(c); 
甚至可正确处理错误!它从输入复制到输出.复制:
void copy(ref StdinByChar source, 
          ref StdoutByChar sink) { 
    foreach (c; source)
        sink.put(c); 
} 
只要有StdinByChar和StdoutByChar类型,可复制/粘贴至其他类型:
用模板
void copy(Source, Sink)(ref Source source, 
                        ref Sink sink) { 
    foreach (c; source)
        sink.put(c); 
} 
解决了通用问题,但接受任意输入类型,而导致灾难,加上约束:
Sink copy(Source, Sink)(ref Source source, 
                        ref Sink sink 
    if (isInputRange!Source && 
        isOutputRange!(Sink, ElementType!Source 
{ 
    foreach (c; source)
        sink.put(c); 
    return sink; 
} 
这是我们第一个算法!(返回是为了可组合.)
当前状态
 StdinByChar source; 
 StdoutByChar sink; 
 copy(source, sink); 
加上统调(UFCS):func(a,b,c),可写成:a.func(b,c),现在这样:
    StdinByChar source; 
    StdoutByChar sink; 
    source.copy(sink); 
过滤:
    int[] arr = [1,2,3,4,5]; 
    auto r = arr.filter!(a => a < 3); 
    writeln(r); 
输出:
    [1, 2] 
映射:
    int[] arr = [1,2,3,4,5]; 
    auto r = arr.map!(a => a * a); 
    writeln(r); 
输出:
    [1, 4, 9, 16, 25] 
化简:
    int[] arr = [1,2,3,4,5]; 
    auto r = arr.reduce!((a,b) => a + b); 
    writeln(r); 
输出:
          15 
连在一起:
 import std.stdio; 
 import std.array; 
 import std.algorithm; 
void main() { 
    stdin.byLine(KeepTerminator.yes)
    map!(a => a.idup). 
    array. 
    sort. 
    copy(stdout.lockingTextWriter());
 } 
还需要特征:
 ● 处理错误的异常
 ● 模板函数
 ● 模板约束
 ● UFCS(统调)
 ● 区间是概念,而不是类型
 ● 内联,自定义,优化
 ● 限定
 ● 推导类型
 ● 元组
结论
● 组件是可重用代码的一种方式
 ● 组件是需要传统和语言支持
 ● D的许多高级特征可结合来支持组件
 ● 在文件和过滤,流及stl的早期成功基础上构建.
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号