Linux 思想
4.4.2 接口与引擎
要说设计一个简单应用也要抽象出机制和策略来,那显然太过高瞻远瞩了,有点够不到边儿的感觉。但是这种设计原则依然值得学习和参考。为了能够得着边儿,就放低一些姿态,让接口与引擎分离。
这里所说的接口与面向对象中的interface可不是一回事。这里的接口是指程序与人的交互界面,可以是文本的,也可以是图形的。至于引擎也跟汽车、摩托车之类的没什么关系,它是一个程序的核心算法。
在Linux中这样的例子很常见,最为有名的就是vi和Emacs了。在文本的控制台下,vi 和 Emacs 提供了基于文本的操作界面;在图形环境下,vi 和 Emacs 又能提供基于图形的操作界面。在一些脚本语言的解释器中,这种设计也很常见,比如Python。 当没有指定具体要执行的py文件时,“python”命令会进入一个交互式界面;而指定具体要执行的py文件时,“python”就会执行对应的脚本,并且在脚本结束之后也随之退出。
这样做的最大好处就是可以较少工作量,同时又具备良好的适应能力。因为有关文本编辑或脚本解释的代码只需要写一份就行,程序如何跟人交互可以视当前环境而定。带给用户的最大感受就是这个程序很贴心,永远能满足用户的喜好。
其实这就是现如今特别流行的MVC(模型-视图-控制器)模式。M代表“模型”,在Linux世界里通常被称为“引擎”,模型包含了应用程序专属的数据结构和逻辑;V代表“视图”,是应用程序专属数据结构和逻辑的可视化形式,视图组件一般由模型负责通知更新,并且作出相应变化;C代表“控制器”,处理用户的请求并将它们反馈给模型。在具体实践中,视图和控制器部分的结合,往往比它们与模型的结合更为紧密,有时候就是一体的。
在Linux中,MVC模式的应用比其他任何领域都要普遍,这主要得益于Unix中“只做一件事并做好”的优秀传统,和自身强大易用的IPC(进程间通信)机制。估计好多人在看到这个事实的时候,会非常憎恨自己没有早一点学习Linux或Unix。否则那些什么倒霉的Struts或许就会出自于自己的手下。
4.4.3 不用重新造轮子
这年头程序员们最喜闻乐见的话题,应该就是”重用“了,也就是”不用重新造轮子“。重用,大多是指源代码的重用,于是搞出了各种面向对象的编程方法,甚至总结出了一套”设计模式“。好多教科书也都是这么教导我们的。但是好多人一想到”重用“就头晕,尤其是设计模式。这并不是说设计模式不好,只是学习并能掌握设计模式的确不是一件容易的事儿,必须花费大量的时间和精力去想象和理解,还得有足够的实践机会。要是能够将设计模式运用的炉火纯青、收放自如的话,这年头基本上就会成为被众多粉丝顶礼膜拜的技术大神。
如果真是这样,那么Linux下的程序员就人人都是大神。得益于Linux的策略与机制分离设计,”重用“这事儿就太小儿科了。而且在Linux下的重用,你连代码长什么样都不用管,编好的程序直接重用。如果你还没有忘记前面的两个小节(”万般皆文本“和”四处用脚本“)的话,那么在那个地方所介绍的一些技巧就是干这种事儿的。
尤其是在编写具有GUI界面的工具程序时更是如此。因为很多程序在CLI的文本界面中就已经提供了,比如查看系统进程信息的ps命令。如果你想编写一个GUI界面的类似于Windows的”进程管理器“这样的程序,完全不用了解Linux内部的运行机制和有关的系统API,直接利用ps命令做一个GUI的外壳就成了。如果不只是查看进程,还想关闭一些占用你内存的进程,还有kill命令可以包装。查看磁盘使用率还有du命令,等等······
4.4.4 内在的支持
如果你说用shell做不了图形界面,那没关系。不是还有各种强大的脚本语言吗?比如Python,做个GUI完全不成问题。如果你觉得用Python做GUI有点怪,那不是还有C,甚至C++呢吗?
永远不要忘记Linux是一个支持多线程的操作系统。在Linux中创建进程比创建线程还容易,Linux进程的资源开销也一点不比线程多。进程与线程更为不同的特性是,进程并不总是一个程序的并行执行分支,还可以是另外一个完全独立的程序,甚至是脚本。那么制作一个GUI外壳就非常简单了,直接利用多进程机制启动另外一个具备相关功能的程序或脚本就行了。
我想,现在在你的心中一定是有一个大大的问号的。众所周知的是:进程具有完全独立的内存和资源空间,互相不会干涉。那么两个进程如何才能像线程那样自如的交换数据,能够实现我们想要的GUI包装呢?
别忘了,前面就提到过,Linux自身就提供了一套强大易用的IPC机制。这使得在Linux中,两个进程进行数据交换是及其容易的事情,甚至比线程那种”资源共享同步“还要方便。
Linux提供的IPC机制主要有:信号、管道、IO重定向、共享内存、套接字等多种类型。
这其中信号相对较弱,只能提供一个字长的信息,这就相当于两个人平时各干各的,有事儿时吼一声(^_^这个比喻真形象)。这种方式虽然既简陋又简单,但是效果有时非常好。
比较常用的是管道和IO重定向,前面几个小节就已经多次提到过了。管道只提供单向通信能力,即一侧输入一侧输出。在具体编程中,管道经常与IO重定向结合使用,最常用的技巧就是将一个进程的标准输出重定向给管道的输入端,另外一个进程就能从管道的输出端获取前者的输出结果。
共享内存很适合大块数据的共享,但是否必须要用,应做多方面的考量,因为经常会引起竞争。解决这种竞争不但麻烦,还到处都是陷阱,一不小心就容易犯错误。不到万不得已不建议使用。
套接字可以说是最为强大的灵活的IPC机制了。原本套接字是用于网络通信的,但是那本身就是一种不同进程间的通信,只是这两个进程不在一个机器上罢了。既然不在一个机器上都能通信,那么在一个机器上的自然也不在话下了。
有这么多IPC机制可以选择,总有一款适合你。而且这也提醒你,如果你的程序规模连做”接口和引擎分离“都觉得有些大题小做,那么就可以考虑支持一种IPC,或许在不久的将来就有人能够用得上。其实,只要将结果输出到屏幕上(代表标准输出),那就已经支持了。