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(),但不能组合,因为迭代器要成对出现.

回到绘图板

● 源:流,容器,生成器
● 算法:过滤,映射,缩减,排序
● 汇:流,容器

算法
filesortfile
treefiltertree
arraymaparray
socketreducesocket
listmaxlist
iotasearch
随机数奇数,数单词

总结要求

● 可组合性
● 支持强大封装
● 生成工业品质高效代码
● 自然语法:源=>算法=>汇
● 可同未知类型一起用

输入区间要求

● 是否有可用数据?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); 
} 

只要有StdinByCharStdoutByChar类型,可复制/粘贴至其他类型:

用模板

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的早期成功基础上构建.

posted @   zjh6  阅读(8)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示