- 浏览: 127864 次
- 性别:
- 来自: 北京
最新评论
-
C_J:
有必要这么鸡冻咩?
编写自己的ClassLoader知识点 -
jason61719:
你这不是说了等于没说吗……就解析个loadClass(),谁不 ...
编写自己的ClassLoader知识点 -
jiming:
tedeyang 写道很好的改进,不过话说回来,mybatis ...
开源,从关注产品社区做起(ibatis3.x的最近一个issue展示) -
C_J:
独爱Java 写道好像实际用处并不是很大,只是多了解了有这个东 ...
Java内存模型笔记 -
独爱Java:
好像实际用处并不是很大,只是多了解了有这个东西而已。。。
Java内存模型笔记
今天突然想到C++的IOC框架,看到我认为不错的文章.
在C++中使用IoC及DSM框架
自Web和Java横空出世以来,软件开发技术,概念以及架构的演变翻新有点让人眼花缭乱,甚至应接不暇。然而,恪守技术含量至上遗风的C++铁杆精英们似乎是其中的另类,与标新立异摈弃繁文褥节的Java,Ruby新生代之间俨如隔世。不用说DSM,就连IoC这个起源于 C++年代甚至C++土壤的概念和方法虽然在墙外其他晚辈语言部落中广受青睐,在C++红墙内却反而遭长期冷落。IoC和DSM技术虽然看似简单无比平淡无奇遍地都是,甚至被很多C++大老们嗤之以鼻(甚至斥责为狗皮膏药),但却能大大提升C++软件开发的效率及质量,而且能彻底简化和净化很多繁琐丑陋甚至危险的C++原始解决方案。比如,由IBM领头忽悠的一个所谓“服务组件架构”SCA(Service Component Architecture)的厂商标准,其C++组件容器的参考实现(RI)虽由IBM几个老大全时操刀,历时两年却仍然困难重重地蹒跚在Apache孵化项目阶段。不仅如此,这个沿用传统思维和方法的参考实现使用起来也相当繁琐,甚至危险(用户将被迫采用无类型验证系统的C-style cast),并且有很多极不自然甚至丑陋的限制(比如对线程模型的特殊要求)(参见 SCA considered harmful 一文)。与之对比,如果采用IoC和DSM技术去实现同样的SCA C++组件容器,一个初级菜鸟程序员却能在几天甚至几小时内以区区数百行浅显易懂的代码轻而易举地大功告成,且其结果还远胜于IBM老大们呕心沥血打造的参考版(易用,类型安全,清除所有不合理限制)(参见 SCA as a DSM 一文)。此类事半功倍的例子在使用IoC和DSM框架的开发中屡见不鲜。其开发效率一个数量级以上的跳跃改进也绝非天方夜谭。70年代关系数据库及SQL技术的引入就使数据库应用的开发效率提高了近两个数量级。
1. IoC
IoC字面上的意思是“控制反转”(Inversion of Control)。然而其具体含义五花八门的说法却很容易让人一头雾水。这些说法往往是过多地关注IoC表面的甚至是字面的含义,却忽略了IoC被用来解决的实质问题(也无视了这个概念的历史和使用现状)。Martin Fowler 就把IoC阐述甚至更名为“依赖注入”(Dependency Injection )设计模式。而IoC鼻祖之一 Stefano Mazzocchi 却指出Martin Fowler这个忽悠了一大批人的说法实际上是不得要领(Martin Fowler文章中���码例子确实非常误导)。所以,这里并不急于给IoC下一个教条定义,而是从IoC的实质目的开始探讨。
IoC 的概念是Michael Mattson在1996年一篇讨论面向对象框架(Object Oriented Frameworks)的文章中提出的。面向对象设计及编程(OOD/OOP)的基本思想简单地说就是把复杂软件系统分解成通过接口相互合作的对象。这些对象类的内部实现之间并不互相牵扯,因而降低了问题的复杂性,且可独立灵活地被重用和扩展。自然而然,经典面向对象的编程语言(如C++,Java)的侧重点就是提供语言机制来方便并简化这种基于对象类的分解,重用和扩展。然而,一个软件系统的开发效率,可扩展性,以及部署维护升级的灵活性等并不完全由其模块化分解的程度和抽象的优劣所决定。很大程度上,将这些分散部件有效地组装成一个紧密合作的整体更是决定该软件项目成���及其产品系统优劣的关键。
以支持对象分解为己任的经典面向对象语言(如C++,Java)并没有引入超越传统命令式语言(imperative language)以外的系统组装部署和配置手段(当然,Java 5,C#现在都开始往这方面添料)。因而,虽然它们能够有效地应付底层子系统的拼装和连接,但在进行大范围基于组件(既高层业务层模块)层次的相应作业时就显得简陋,死板,冗长和低效。比如,在使用各种std的IO流类,STL容器类以及boost库类这些底层模块类时,采用语言本身的机制(自动变量或用 new算符)直截了当地实例化这些被使用类的对象就尽善尽美了。但在使用高层业务模块时,为了避免对其具体实现类的依赖,人们不得不叠床架屋对语言机制进行额外的手工包装。应运而生的是一系列处理所谓管线逻辑(plumbing logic,既非业务逻辑)的设计模式,比如factory,builder,directory,adaptor,singleton, configuration/property manager,factory的manager甚至manager的manager等等。遗憾的是,一个世纪以来这些实际上是弥补语言缺陷的权宜之计反而受到狂热追捧而非深入反思。另外,在传统软件的设计和实现中,业务逻辑往往直接调用这些管线逻辑,从而破坏了业务逻辑的简洁性和独立性(比如增加了单元测试的困难)。更重要的是,软件组装部署和配置的逻辑是支离破碎地散落混迹于各个业务逻辑组件中,既不直观(往往是见树不见林)也不灵活(牵一发则可能动全身)。往往使得在宏观结构上理解,维护,修改和扩充一个现有软件要反而难于当初从头开发这个软件。
为解决以上问题,Michael Mattson提出了面向对象框架的IoC设计原则。依照该原则,管线逻辑被转移并集中至软件框架内,业务逻辑模块并不需知道更不必调用组件框架的服务,例如不用关心和调用其factory或lookup其directory或context等。软件的组装部署和配置完全是由管线逻辑框架反过来主动控制业务逻辑模块来安排。Michael Mattson用所谓的好莱坞原则(Hollywood Principle)“别来电(问)我,我会去电(告诉)你”(don't call me, I will call you)形象地比喻了这一设计思想。这个比喻中的“我”指的是负责管线逻辑的组件框架,“你”则是被其调遣配置的一个组件。比如,在一个业务逻辑模块A需要调用另一个业务逻辑模块B的场景中,传统的非IoC的设计(比如EJB2.0)是让A调用管线逻辑(B的factory或某个directory服务)来获得B的引用(或指针)。在IoC框架内,框架不但完成A和B的实例化并保持追踪,而且B的实例引用(或指针)也是由框架主动调用A的接口函数(比如构造及赋值函数等)赋予 A。从而,A的实现可以专著于业务逻辑,而管线细节(比如B的实例化及如何获得其引用或指针)可以让外部框架透明地安排妥当。这种架构完全避免了业务逻辑对具体管线逻辑框架的牵连从而降低了业务逻辑模块的复杂度,但更重要的是集中的组装部署和配置逻辑为提高软件宏观结构的直观性和灵活性铺平了道路(后面还将具体讨论)。
简短概括一下,从概念上说,IoC就是模块化软件组装部署配置框架的一个设计原则。依该原则,业务逻辑模块既不需要处理管线逻辑也可以对外部管线框架本身一无所知(agnostic)。软件的搭接完全由外部管线框架对业务逻辑模块的主动操控来完成。从具体实现上说,除了一些开发工具以外,IoC框架不过是一个封装了必备的管线逻辑及IoC机制的轻型类库(比如PocoCapsule/C++ IoC类库大约是70K)。从使用上说,用户制作好业务逻辑组件(见下面非侵入性与POCO讨论),并将软件组装及部署描述(见后面讨论)提供给IoC框架(作为 IoC框架库函数的���用参量,或直接驱动一系列库函数调用)。IoC容器(被调用的库函数)将参照用户的描述相应地实例化和配置各个组件并将它们搭接为所希望的部署状态。
2. 非侵入性与POCO
对于象C++和Java这类不支持动态类型的语言环境,一个很自然的问题就是业务逻辑组件需要支持什么样的公共接口以使外部IoC框架能对其进行操控。早期的组件框架(比如Apache Avalon,EJB2.0,CORBA组件模型 CCM,JTRS-SCA等)几乎清一色地采用侵入式(invasive)设计,也就是强制规定业务逻辑模块(称作bean)必须与特定的公共组件接口模型兼容,既支持由组件框架定义的用来对组件进行部署配置的公共接口类型或基类(base class)以及进行实例化的所谓home接口。侵入式设计不仅学习和使用繁琐(EJB2.0和CCM均是追求繁琐,以繁琐冒充强大的恶例),也大大地限制了组建框架的开放性和适应性。因为众口难调,所以几乎每一个问题领域均定义了N个自己的组件框架和组件接口模型(比如机器人领域里就至少有10个)。侵入式设计导致组件接口模型只能被其特定的框架所支持,从而形成了各自为政老死不相往来的组件框架孤岛,限制了组件的重用范围以及框架的通用性。大量这类侵入式组件框架以及与之相应的上下左右整个配套开发体系(如果侥幸有的话)均仅仅是在小范围内被采用,并以高成本在低水平上无谓地被重复开发和维护(CCM就是这方面最恶名昭著的例子)。
因此,现代IoC框架大都采用非侵入式(non-invasive)设计,也就是不对组件接口模型(即接口及函数签名)做任何规定。换句话说,非侵入的 IoC框架一视同仁地支持任何组件接口模型,包括已经被定义的和还未被定义的模型,也包括标准组织定义的或用户自定义的模型。这些组件接口模型可以采用(或不采用)任何公共或自定义接口,模板(template)或基类(也可以根本不是C++对象,比如C/C++函数),可以采用任何(合理的)实例化或回收手段和部署配置函数,包括构造或析构函数,各种自定义 factory/pool/directory lookup函数,以及各种全局或成员函数等(参见开源C++非侵入式IoC框架项目 PocoCapsule/C++ IoC的介绍 )。在非侵入的C++ IoC组件框架中,因为所有组件无论其接口模型的新旧美丑高矮胖瘦均被一视同仁地按平头百姓对待,故均被统称为“平庸C++对象”(Plain Old C++ Object )或POCO(相应于Java中的“平庸Java对象”POJO)。
3. 软件组装及部署描述
在使用IoC框架的C++软件开发过程中,管线模块及管线衔接代码转移并集中至IoC框架内,业务逻辑组件(高层模块或库类)的开发只需关注业务逻辑本身。软件的搭接由IoC容器(或库类)按照用户提供的软件组装及部署描述(assembly and deployment description)完成。因为由分散组件搭接成的软件呈树状或更广义的图状结构,所以,软件组装及部署描述就是对这种树图结构模型的描述。这种描述通常有三种表达(编写)形式,既指令代码(code),元数据(metadata),和用户数据(data)。
虽然经典面向对象语言(比如C++和Java)可以有效地按程序步骤构造树图状数据结构,但对结构整体的“模型描述”却苍白无力。在C++中,要搭建一个树图结构的代码不外乎就是一步步地去调用类似allocNode(),addNode(),wireNodes()等等函数。树图结构模型在这种指令式的(imperative)“步骤描述”中荡然无存。对于管线逻辑比较简单的应用(例如底层或子模块),结构模型并不至关重要甚至是多此一举,而步骤描述或干脆抛弃IoC框架也许更直接了当。然而对于管线逻辑较复杂的应用,对结构搭接具体步骤描述所引入的复杂性则与使用IoC框架抽象���线逻辑的原始初衷背道而驰。
所谓元数据表述就是以编程语言的元数据结构来描述软件的管线结构模型。其实质就是利用传统指令性编程语言中类(class)结构定义的声明式语法来表达管线结构。比如,如果一个结构有10个节点(组件),按此方法就是让用户定义一个包含10个相应成员函数的类(class)并辅助以相应注释(annotation)标识管线连接。进行软件搭接时,IoC框架通过反射机制来解读这个类的结构并将其看做组件搭接的管线结构描述。这种方法,看似提供了一种声明式的(declarative)模型描述,实际上则属于一种牵强附会甚至是生搬硬套的kludge,除了能满足“只使用编程语言本身来表述管线逻辑”这一教义心态之外不具任何正面意义。
IoC 框架中有效灵活自然直观的结构描述形式恰恰就是被大牛们鄙视为恶俗的用户文本数据描述形式。无论大牛们对这种C++和Java语法机制以外的方法如何深恶痛绝,都不得不面对下面一个尴尬的窘境。C++和Java这类被他们(比如红帽Jboss的首席科学家)奉为万能银弹的编程语言中并不提供对树图结构整体具体实例的有效描述手段。因为这类语言的目的仅仅是提供对象类的包装抽象机制,而并不是提供具体多对象系统整体部署结构的模型表述方法(更不要说对各种模型之间变换,甚至变换的变换的声明式描述)。
4. 基于XML的组装及部署描述
主流IoC框架(甚至很多传统非IoC组件框架如EJB2.0和CCM)中用户文本数据形式的组装及部署描述大都是基于XML。XML的设计目的恰恰就是提供对树图结构的声明式描述。对用户来说,XML标准及技术成熟稳定,已被普遍采用和支持(各种XML解析器和工具满天飞)。另外,XML框架内具备完善的声明式结构转换技术(XSLT,XQuery),为从底层通用IoC组件部署描述提高到“针对问题域的特定建模”DSM(domain specific modeling)准备���了理想平台。最重要的是,与SQL类似,XML是种���编程菜鸟都大呼容易的简单直观技术,这就使得很多领域专家能够对组件构成的系统进行搭建和部署。
下面以开源项目PocoCapsule/C++ IoC框架 中的一个具体例子来介绍这个方法。这个例子的完整代码以及文档在PocoCapsule源代码包 和安装包 中均可找到,也可以在http://www.pocomatic.com/docs/cpp-examples/basic-ioc/gps 上在线浏览。
这个例子中所要搭建部署的是一个如下图所示包含定时触发器(tick generator),GPS定位器(gps locator),导航���示器(navigate display)三个组件的GPS系统。
PocoCapsule容器支持所谓POCO(既前面所说的“平庸C++对象”),从而对组件接口模型几乎没有任何限制。这三个组件的基类TickGen,GPSLocator和NavDisplay都是由用户自己在Interfaces.h 中如下定义的:
这些组件的具体实现类TickGenImpl,GPSLocatorImpl和NavDisplayImpl则大致定义如下(忽略所有与组装不相关细节)。
这些组件基类和具体实现类的定义以及它们的编译连接(动态或静态库均可)等开发制作方式与一般C++应用模块没任何区别,完全不需考虑IoC容器。它们甚至可以是IoC时代以前已由第三方制作好,不提供源代码的模块。在这个例子中,除了需要用到这些组件实现类的构造算子原型以外省略掉了其他所有不需要关心的实现细节。
接下来,可以用PocoCapsule的XML语法来描述这个GPS应用。PocoCapsule采用与Spring Framework尽量相近的XML文本格式(schema,定义在poco-application.context.dtd 中),并针对C++语言特征进行了扩充。比如,将bean实例化后的setter调用普遍化为任何IoC调用。在这个文本格式中,一个POCO组件的实例将被声明为一个<bean>元素,包含其构造算子的参数(<method-arg>),实例化后的IoC (<ioc>),以及IoC方法的参量(也是<method-arg>)等子孙元素。比如,这个例子中的GPS应用结构就可以由下面一段XML声明(见setup.xml ):
这段XML声明简单直观地表达了三个组件实例以及它们之间的互相衔接结构。可以形象化地将它用相应的C++形式表示如下:
虽然这两种描述看似表达同样的概念,但实际上他们有本质区别。C++版本表达的是构造这个应用的具体先后步骤,因此实际上必须重新修改上面C++代码行的次序才能让程序正常编译和工作。而XML版本表达的则是一种结构,而并非构造这个结构的步骤。结构中各<bean>节点的实例化次序与它们在 XML表述中的先后次序无关,而是由IoC容器根据用户声明的节点属性(比如lazy-init的值)以及衔接时依赖关系的先后来决定。
至此,用户仅需要将这个XML描述以文件或字符串形式交给PocoCapsule/C++ IoC容器(既以文件名或XML字符串为参量调用PocoCapsule/C++ IoC库函数,见main.C ),让其自动组装部署所描述���应用。关于PocoCapsule详细的使用及工作机制描述可参阅其入门教材 ,用户手册 ,及代码实例 。
5. DSM和模型变换
前面一节例子中用到的XML文本格式是由PocoCapsule容器支持的核心格式(core schema)。因为XML在IoC框架中用于描述基于组件应用的结构模型,所以这个格式也称为建模格式。该建模格式与其他IoC容器所采用的XML格式大同小异。这种格式有直观易学,格式定义紧凑,普遍适用等优点。然而,这些这些具有双刃剑特征的优点也意味着相应的缺点,诸如直接引用组件编程接口函数签名,表述力和抽象度低,容易造成冗长的声明及低级错误。比如,前面GPS例子中,XML模型描述直接涉及了有两个参量的TickGenImpl的构造算子。从该模型描述中,人们无从判断这两个参量的目的和意义。如果该模型描述声明了不匹配的参量类型,用户可以侥幸地得到IoC容器异常报告(虽然可能难于解读)。但如果这两个参量类型声明无误,而它们的数值在无意间被相互颠倒了(因为恰好是同一类型),那么用户就只能听天由命了。以setter函数取代多参量的构造或工厂函数来完成组件配置看似避免了这一问题,但实际上不但可能破坏无侵入(non-invasive)原则,而且还可能使模型描述更加冗长。同样,如果试图增强核心格式以减少这些缺点,则会牺牲其相应的优点。众多的重型组件框架(如EJB,CCM等)采用的庞大XML描述格式和令人畏惧的 UML2.0及XMI就是前车之鉴。
解决这个鱼和熊掌两难问题的一个有效方法是反其道而行之,干脆不去寻求一个能青菜萝卜一刀切“为所有人解决所有问题”的建模格式。而是提供一个���放平台,允许用户根据自己的特定需要决定取舍制定最佳建模格式,也就是所谓“针对问题域特定建模”DSM(domain specific modeling)。比如(参见PocoCapsule中dsm-gps例子 ),用户可以为自己要描述的GPS专门量身定制一个DSM格式gps-device.dtd 。按照这个DSM格式,前面例子中的GPS系统可以被重新描述如下(参见setup.xml ):
与支持“通用目的建模”GPM(general purpose modeling)的核心格式(poco-application.context.dtd )比较,这个DSM格式(gps-device.dtd)不但抽象度高,而且定义更紧凑。在其模型描述中完全不涉及组件接口具体函数签名,甚至连组件间具体的连接也被藏于幕后。而其配置参数的含义则一目了然。
如前所说,XML框架中的XSLT和XQuery已经为支持这个DSM格式准备好了现成的平台。这个平台提供了以声明方式描述模型之间互相转换。因而,用户在定义了一个DSM后只需要再提供该DSM格式至另一个建模格式(比如IoC框架的核心建模格式)的转换XSLT描述。那末,集成了XSLT转换器的 IoC框架(比如PocoCapsule/C++ IoC and DSM框架 )将会按照指定的转换描述将一个由新定义的DSM描述的结构转换为由另一个DSM定义的结构,直至递归到该IoC框架的核心格式结构。不仅建模格式可以被转换,而且模型转换的XSLT描述本身的格式也可以被转换(甚至转换的转换的转换),以简化转换描述的设计。这就是所谓的“高阶转换”HOT(higher order transformation )。关于IoC框架中DSM模型转换的进一步描述可参阅PocoCapsule DSM入门教材,用户手册 ,及代码实例 。
一个非侵入(non-invasive)IoC框架一视同仁地支持任何组件接口模型。DSM又使其能轻而易举地支持任何用户或标准组织定义的模型描述格式。因此,一个IoC+DSM框架实际上是一个非常有效灵活的框架的框架。它让菜鸟可以轻松且高质量地实现很多组件框架,包括本文开始说的服务组件架构 SCA(参阅),以及软件无线电SDR的JTRS-SCA核心框架(CF)组件架构(参阅),各种机器人软件组件架构(参阅),CORBA组件架构(参阅),等等如下图所述的在PocoCapsule中提供的DSM框架。
1. IoC
IoC字面上的意思是“控制反转”(Inversion of Control)。然而其具体含义五花八门的说法却很容易让人一头雾水。这些说法往往是过多地关注IoC表面的甚至是字面的含义,却忽略了IoC被用来解决的实质问题(也无视了这个概念的历史和使用现状)。Martin Fowler 就把IoC阐述甚至更名为“依赖注入”(Dependency Injection )设计模式。而IoC鼻祖之一 Stefano Mazzocchi 却指出Martin Fowler这个忽悠了一大批人的说法实际上是不得要领(Martin Fowler文章中���码例子确实非常误导)。所以,这里并不急于给IoC下一个教条定义,而是从IoC的实质目的开始探讨。
IoC 的概念是Michael Mattson在1996年一篇讨论面向对象框架(Object Oriented Frameworks)的文章中提出的。面向对象设计及编程(OOD/OOP)的基本思想简单地说就是把复杂软件系统分解成通过接口相互合作的对象。这些对象类的内部实现之间并不互相牵扯,因而降低了问题的复杂性,且可独立灵活地被重用和扩展。自然而然,经典面向对象的编程语言(如C++,Java)的侧重点就是提供语言机制来方便并简化这种基于对象类的分解,重用和扩展。然而,一个软件系统的开发效率,可扩展性,以及部署维护升级的灵活性等并不完全由其模块化分解的程度和抽象的优劣所决定。很大程度上,将这些分散部件有效地组装成一个紧密合作的整体更是决定该软件项目成���及其产品系统优劣的关键。
以支持对象分解为己任的经典面向对象语言(如C++,Java)并没有引入超越传统命令式语言(imperative language)以外的系统组装部署和配置手段(当然,Java 5,C#现在都开始往这方面添料)。因而,虽然它们能够有效地应付底层子系统的拼装和连接,但在进行大范围基于组件(既高层业务层模块)层次的相应作业时就显得简陋,死板,冗长和低效。比如,在使用各种std的IO流类,STL容器类以及boost库类这些底层模块类时,采用语言本身的机制(自动变量或用 new算符)直截了当地实例化这些被使用类的对象就尽善尽美了。但在使用高层业务模块时,为了避免对其具体实现类的依赖,人们不得不叠床架屋对语言机制进行额外的手工包装。应运而生的是一系列处理所谓管线逻辑(plumbing logic,既非业务逻辑)的设计模式,比如factory,builder,directory,adaptor,singleton, configuration/property manager,factory的manager甚至manager的manager等等。遗憾的是,一个世纪以来这些实际上是弥补语言缺陷的权宜之计反而受到狂热追捧而非深入反思。另外,在传统软件的设计和实现中,业务逻辑往往直接调用这些管线逻辑,从而破坏了业务逻辑的简洁性和独立性(比如增加了单元测试的困难)。更重要的是,软件组装部署和配置的逻辑是支离破碎地散落混迹于各个业务逻辑组件中,既不直观(往往是见树不见林)也不灵活(牵一发则可能动全身)。往往使得在宏观结构上理解,维护,修改和扩充一个现有软件要反而难于当初从头开发这个软件。
为解决以上问题,Michael Mattson提出了面向对象框架的IoC设计原则。依照该原则,管线逻辑被转移并集中至软件框架内,业务逻辑模块并不需知道更不必调用组件框架的服务,例如不用关心和调用其factory或lookup其directory或context等。软件的组装部署和配置完全是由管线逻辑框架反过来主动控制业务逻辑模块来安排。Michael Mattson用所谓的好莱坞原则(Hollywood Principle)“别来电(问)我,我会去电(告诉)你”(don't call me, I will call you)形象地比喻了这一设计思想。这个比喻中的“我”指的是负责管线逻辑的组件框架,“你”则是被其调遣配置的一个组件。比如,在一个业务逻辑模块A需要调用另一个业务逻辑模块B的场景中,传统的非IoC的设计(比如EJB2.0)是让A调用管线逻辑(B的factory或某个directory服务)来获得B的引用(或指针)。在IoC框架内,框架不但完成A和B的实例化并保持追踪,而且B的实例引用(或指针)也是由框架主动调用A的接口函数(比如构造及赋值函数等)赋予 A。从而,A的实现可以专著于业务逻辑,而管线细节(比如B的实例化及如何获得其引用或指针)可以让外部框架透明地安排妥当。这种架构完全避免了业务逻辑对具体管线逻辑框架的牵连从而降低了业务逻辑模块的复杂度,但更重要的是集中的组装部署和配置逻辑为提高软件宏观结构的直观性和灵活性铺平了道路(后面还将具体讨论)。
简短概括一下,从概念上说,IoC就是模块化软件组装部署配置框架的一个设计原则。依该原则,业务逻辑模块既不需要处理管线逻辑也可以对外部管线框架本身一无所知(agnostic)。软件的搭接完全由外部管线框架对业务逻辑模块的主动操控来完成。从具体实现上说,除了一些开发工具以外,IoC框架不过是一个封装了必备的管线逻辑及IoC机制的轻型类库(比如PocoCapsule/C++ IoC类库大约是70K)。从使用上说,用户制作好业务逻辑组件(见下面非侵入性与POCO讨论),并将软件组装及部署描述(见后面讨论)提供给IoC框架(作为 IoC框架库函数的���用参量,或直接驱动一系列库函数调用)。IoC容器(被调用的库函数)将参照用户的描述相应地实例化和配置各个组件并将它们搭接为所希望的部署状态。
2. 非侵入性与POCO
对于象C++和Java这类不支持动态类型的语言环境,一个很自然的问题就是业务逻辑组件需要支持什么样的公共接口以使外部IoC框架能对其进行操控。早期的组件框架(比如Apache Avalon,EJB2.0,CORBA组件模型 CCM,JTRS-SCA等)几乎清一色地采用侵入式(invasive)设计,也就是强制规定业务逻辑模块(称作bean)必须与特定的公共组件接口模型兼容,既支持由组件框架定义的用来对组件进行部署配置的公共接口类型或基类(base class)以及进行实例化的所谓home接口。侵入式设计不仅学习和使用繁琐(EJB2.0和CCM均是追求繁琐,以繁琐冒充强大的恶例),也大大地限制了组建框架的开放性和适应性。因为众口难调,所以几乎每一个问题领域均定义了N个自己的组件框架和组件接口模型(比如机器人领域里就至少有10个)。侵入式设计导致组件接口模型只能被其特定的框架所支持,从而形成了各自为政老死不相往来的组件框架孤岛,限制了组件的重用范围以及框架的通用性。大量这类侵入式组件框架以及与之相应的上下左右整个配套开发体系(如果侥幸有的话)均仅仅是在小范围内被采用,并以高成本在低水平上无谓地被重复开发和维护(CCM就是这方面最恶名昭著的例子)。
因此,现代IoC框架大都采用非侵入式(non-invasive)设计,也就是不对组件接口模型(即接口及函数签名)做任何规定。换句话说,非侵入的 IoC框架一视同仁地支持任何组件接口模型,包括已经被定义的和还未被定义的模型,也包括标准组织定义的或用户自定义的模型。这些组件接口模型可以采用(或不采用)任何公共或自定义接口,模板(template)或基类(也可以根本不是C++对象,比如C/C++函数),可以采用任何(合理的)实例化或回收手段和部署配置函数,包括构造或析构函数,各种自定义 factory/pool/directory lookup函数,以及各种全局或成员函数等(参见开源C++非侵入式IoC框架项目 PocoCapsule/C++ IoC的介绍 )。在非侵入的C++ IoC组件框架中,因为所有组件无论其接口模型的新旧美丑高矮胖瘦均被一视同仁地按平头百姓对待,故均被统称为“平庸C++对象”(Plain Old C++ Object )或POCO(相应于Java中的“平庸Java对象”POJO)。
3. 软件组装及部署描述
在使用IoC框架的C++软件开发过程中,管线模块及管线衔接代码转移并集中至IoC框架内,业务逻辑组件(高层模块或库类)的开发只需关注业务逻辑本身。软件的搭接由IoC容器(或库类)按照用户提供的软件组装及部署描述(assembly and deployment description)完成。因为由分散组件搭接成的软件呈树状或更广义的图状结构,所以,软件组装及部署描述就是对这种树图结构模型的描述。这种描述通常有三种表达(编写)形式,既指令代码(code),元数据(metadata),和用户数据(data)。
虽然经典面向对象语言(比如C++和Java)可以有效地按程序步骤构造树图状数据结构,但对结构整体的“模型描述”却苍白无力。在C++中,要搭建一个树图结构的代码不外乎就是一步步地去调用类似allocNode(),addNode(),wireNodes()等等函数。树图结构模型在这种指令式的(imperative)“步骤描述”中荡然无存。对于管线逻辑比较简单的应用(例如底层或子模块),结构模型并不至关重要甚至是多此一举,而步骤描述或干脆抛弃IoC框架也许更直接了当。然而对于管线逻辑较复杂的应用,对结构搭接具体步骤描述所引入的复杂性则与使用IoC框架抽象���线逻辑的原始初衷背道而驰。
所谓元数据表述就是以编程语言的元数据结构来描述软件的管线结构模型。其实质就是利用传统指令性编程语言中类(class)结构定义的声明式语法来表达管线结构。比如,如果一个结构有10个节点(组件),按此方法就是让用户定义一个包含10个相应成员函数的类(class)并辅助以相应注释(annotation)标识管线连接。进行软件搭接时,IoC框架通过反射机制来解读这个类的结构并将其看做组件搭接的管线结构描述。这种方法,看似提供了一种声明式的(declarative)模型描述,实际上则属于一种牵强附会甚至是生搬硬套的kludge,除了能满足“只使用编程语言本身来表述管线逻辑”这一教义心态之外不具任何正面意义。
IoC 框架中有效灵活自然直观的结构描述形式恰恰就是被大牛们鄙视为恶俗的用户文本数据描述形式。无论大牛们对这种C++和Java语法机制以外的方法如何深恶痛绝,都不得不面对下面一个尴尬的窘境。C++和Java这类被他们(比如红帽Jboss的首席科学家)奉为万能银弹的编程语言中并不提供对树图结构整体具体实例的有效描述手段。因为这类语言的目的仅仅是提供对象类的包装抽象机制,而并不是提供具体多对象系统整体部署结构的模型表述方法(更不要说对各种模型之间变换,甚至变换的变换的声明式描述)。
4. 基于XML的组装及部署描述
主流IoC框架(甚至很多传统非IoC组件框架如EJB2.0和CCM)中用户文本数据形式的组装及部署描述大都是基于XML。XML的设计目的恰恰就是提供对树图结构的声明式描述。对用户来说,XML标准及技术成熟稳定,已被普遍采用和支持(各种XML解析器和工具满天飞)。另外,XML框架内具备完善的声明式结构转换技术(XSLT,XQuery),为从底层通用IoC组件部署描述提高到“针对问题域的特定建模”DSM(domain specific modeling)准备���了理想平台。最重要的是,与SQL类似,XML是种���编程菜鸟都大呼容易的简单直观技术,这就使得很多领域专家能够对组件构成的系统进行搭建和部署。
下面以开源项目PocoCapsule/C++ IoC框架 中的一个具体例子来介绍这个方法。这个例子的完整代码以及文档在PocoCapsule源代码包 和安装包 中均可找到,也可以在http://www.pocomatic.com/docs/cpp-examples/basic-ioc/gps 上在线浏览。
这个例子中所要搭建部署的是一个如下图所示包含定时触发器(tick generator),GPS定位器(gps locator),导航���示器(navigate display)三个组件的GPS系统。
PocoCapsule容器支持所谓POCO(既前面所说的“平庸C++对象”),从而对组件接口模型几乎没有任何限制。这三个组件的基类TickGen,GPSLocator和NavDisplay都是由用户自己在Interfaces.h 中如下定义的:
class EventListener {
public: virtual ~EventListener() {}
};virtual void refresh() = 0; class EventEmitter { public: virtual ~EventEmitter() {}
};virtual void subscribe(EventListener*) = 0; class TickGen : public EventEmitter { public: virtual void start() = 0;
};class GPSLocator : public EventEmitter, public EventListener { public: virtual int get_pos_x() = 0;
};virtual int get_pos_y() = 0; class NavDisplay : public EventListener { public:
};
|
这些组件的具体实现类TickGenImpl,GPSLocatorImpl和NavDisplayImpl则大致定义如下(忽略所有与组装不相关细节)。
class TickGenImpl : public TickGen
{ ...
public:TickGenImpl(int cnt, int interval);
};... class GPSLocatorImpl : public GPSLocator
{ ...
public:
GPSLocatorImpl();
...
};
class NavDisplayImpl : public NavDisplay { ...
public:NavDisplayImpl(GPSLocator* loc) ;
}; ... |
这些组件基类和具体实现类的定义以及它们的编译连接(动态或静态库均可)等开发制作方式与一般C++应用模块没任何区别,完全不需考虑IoC容器。它们甚至可以是IoC时代以前已由第三方制作好,不提供源代码的模块。在这个例子中,除了需要用到这些组件实现类的构造算子原型以外省略掉了其他所有不需要关心的实现细节。
接下来,可以用PocoCapsule的XML语法来描述这个GPS应用。PocoCapsule采用与Spring Framework尽量相近的XML文本格式(schema,定义在poco-application.context.dtd 中),并针对C++语言特征进行了扩充。比如,将bean实例化后的setter调用普遍化为任何IoC调用。在这个文本格式中,一个POCO组件的实例将被声明为一个<bean>元素,包含其构造算子的参数(<method-arg>),实例化后的IoC (<ioc>),以及IoC方法的参量(也是<method-arg>)等子孙元素。比如,这个例子中的GPS应用结构就可以由下面一段XML声明(见setup.xml ):
...
<poco-application-context> ... <bean class="TickGenImpl" lazy-init="false"> <method-arg type="short" value="10"/> <method-arg type="short" value="1"/> <ioc method="subscribe"> <method-arg ref="gps-locator"/> </ioc> <ioc method="start"/> </bean> <bean id="gps-locator" class="GPSLocatorImpl"> <ioc method="subscribe"> <method-arg ref="nav-display"/> </ioc> </bean> <bean id="nav-display" class="NavDisplayImpl"> <method-arg ref="gps-locator"/>
</bean> </poco-application-context> |
这段XML声明简单直观地表达了三个组件实例以及它们之间的互相衔接结构。可以形象化地将它用相应的C++形式表示如下:
TickGenImpl* tick_gen = new TickGenImpl(10, 1);
tick_gen->subscribe(gps_locator); tick_gen->start(); ... GPSLocatorImpl* gps_locator = new GPSLocatorImpl; gps_locator->subscribe(nav_display); ... NavDisplayImpl* nav_display = new NavDisplayImpl(gps_locator); |
虽然这两种描述看似表达同样的概念,但实际上他们有本质区别。C++版本表达的是构造这个应用的具体先后步骤,因此实际上必须重新修改上面C++代码行的次序才能让程序正常编译和工作。而XML版本表达的则是一种结构,而并非构造这个结构的步骤。结构中各<bean>节点的实例化次序与它们在 XML表述中的先后次序无关,而是由IoC容器根据用户声明的节点属性(比如lazy-init的值)以及衔接时依赖关系的先后来决定。
至此,用户仅需要将这个XML描述以文件或字符串形式交给PocoCapsule/C++ IoC容器(既以文件名或XML字符串为参量调用PocoCapsule/C++ IoC库函数,见main.C ),让其自动组装部署所描述���应用。关于PocoCapsule详细的使用及工作机制描述可参阅其入门教材 ,用户手册 ,及代码实例 。
5. DSM和模型变换
前面一节例子中用到的XML文本格式是由PocoCapsule容器支持的核心格式(core schema)。因为XML在IoC框架中用于描述基于组件应用的结构模型,所以这个格式也称为建模格式。该建模格式与其他IoC容器所采用的XML格式大同小异。这种格式有直观易学,格式定义紧凑,普遍适用等优点。然而,这些这些具有双刃剑特征的优点也意味着相应的缺点,诸如直接引用组件编程接口函数签名,表述力和抽象度低,容易造成冗长的声明及低级错误。比如,前面GPS例子中,XML模型描述直接涉及了有两个参量的TickGenImpl的构造算子。从该模型描述中,人们无从判断这两个参量的目的和意义。如果该模型描述声明了不匹配的参量类型,用户可以侥幸地得到IoC容器异常报告(虽然可能难于解读)。但如果这两个参量类型声明无误,而它们的数值在无意间被相互颠倒了(因为恰好是同一类型),那么用户就只能听天由命了。以setter函数取代多参量的构造或工厂函数来完成组件配置看似避免了这一问题,但实际上不但可能破坏无侵入(non-invasive)原则,而且还可能使模型描述更加冗长。同样,如果试图增强核心格式以减少这些缺点,则会牺牲其相应的优点。众多的重型组件框架(如EJB,CCM等)采用的庞大XML描述格式和令人畏惧的 UML2.0及XMI就是前车之鉴。
解决这个鱼和熊掌两难问题的一个有效方法是反其道而行之,干脆不去寻求一个能青菜萝卜一刀切“为所有人解决所有问题”的建模格式。而是提供一个���放平台,允许用户根据自己的特定需要决定取舍制定最佳建模格式,也就是所谓“针对问题域特定建模”DSM(domain specific modeling)。比如(参见PocoCapsule中dsm-gps例子 ),用户可以为自己要描述的GPS专门量身定制一个DSM格式gps-device.dtd 。按照这个DSM格式,前面例子中的GPS系统可以被重新描述如下(参见setup.xml ):
<gps-device>
<tick-generator use=”TickGenImpl” count=”10” interval=”1”/>
<gps-locator use=”GPSLocatorImpl”/>
<navigation-display use=”NavDisplayImpl”/>
</gps-device>.
|
与支持“通用目的建模”GPM(general purpose modeling)的核心格式(poco-application.context.dtd )比较,这个DSM格式(gps-device.dtd)不但抽象度高,而且定义更紧凑。在其模型描述中完全不涉及组件接口具体函数签名,甚至连组件间具体的连接也被藏于幕后。而其配置参数的含义则一目了然。
如前所说,XML框架中的XSLT和XQuery已经为支持这个DSM格式准备好了现成的平台。这个平台提供了以声明方式描述模型之间互相转换。因而,用户在定义了一个DSM后只需要再提供该DSM格式至另一个建模格式(比如IoC框架的核心建模格式)的转换XSLT描述。那末,集成了XSLT转换器的 IoC框架(比如PocoCapsule/C++ IoC and DSM框架 )将会按照指定的转换描述将一个由新定义的DSM描述的结构转换为由另一个DSM定义的结构,直至递归到该IoC框架的核心格式结构。不仅建模格式可以被转换,而且模型转换的XSLT描述本身的格式也可以被转换(甚至转换的转换的转换),以简化转换描述的设计。这就是所谓的“高阶转换”HOT(higher order transformation )。关于IoC框架中DSM模型转换的进一步描述可参阅PocoCapsule DSM入门教材,用户手册 ,及代码实例 。
一个非侵入(non-invasive)IoC框架一视同仁地支持任何组件接口模型。DSM又使其能轻而易举地支持任何用户或标准组织定义的模型描述格式。因此,一个IoC+DSM框架实际上是一个非常有效灵活的框架的框架。它让菜鸟可以轻松且高质量地实现很多组件框架,包括本文开始说的服务组件架构 SCA(参阅),以及软件无线电SDR的JTRS-SCA核心框架(CF)组件架构(参阅),各种机器人软件组件架构(参阅),CORBA组件架构(参阅),等等如下图所述的在PocoCapsule中提供的DSM框架。
发表评论
-
iOS入门(ongoing)
2012-09-13 11:32 1301Record it: The overview of ... -
Stuff about Android
2011-07-09 16:15 1065Foreword: long time ... -
JQuery初体验(Demo)
2011-05-22 13:43 1458Demo:Show <meta content ... -
Java内存模型笔记
2011-04-13 15:48 1538题记: 看到C/C++ ... -
Radiant_The Popular Ruby's CMS Demo篇
2011-04-02 14:49 1241题记: 上篇 记录我第一次安装Rodiant经过和 ... -
Radiant_The Popular Ruby’s CMS安装篇
2011-03-28 00:48 1278题记: 今天第一次参加JE的线下活动,robbin等 ... -
关于Azul 并发垃圾回收器
2011-03-26 14:40 1317题记: 总感觉JE讨论的帖子的东西都比较滞后,所以会 ... -
phpCMS & jQuery是我该做的(阉割了)
2011-02-27 23:02 81WD讲究以plugin挂载为结构,我需要构造一个p ... -
我的玩意:J2ME的Criteria初探
2011-01-20 21:59 1019题记: 前几天跟初中同学聊天,他问我能不能做一个GP ... -
编写自己的ClassLoader知识点
2011-01-13 14:41 1871题记: 看到InfoQ关于ClassLoader的文 ... -
周末好玩,用短信控制你的计算机
2011-01-10 16:34 2986Snapshot: 详情 ... -
About Dock Plugin on Mac
2010-11-21 22:47 1462题记: 第一次接触MAC的开发..... ... -
可变hashcode的隐患和序列化安全
2010-10-25 00:55 1369可变hashcode的隐患 为识别对象,JDK ... -
体验OSGi(helloworld.jar)—富app的热拔插
2010-10-18 23:22 2436记得以前工作的时候,有天direct manager问 ... -
MongoDB on DAO with Java Language
2010-08-26 19:17 1425A Quick Tour Using the Java d ... -
Getting Start on Mongodb
2010-08-26 01:29 1504题记: 最近老和同学聊到non-relational ... -
Java Media Framework本地玩转摄像头
2010-08-04 00:57 17331、简介The JavaTM Media Framework ... -
从WeakLogHandler应用看Java的引用、引用队列
2010-06-14 00:58 1499题记: 前几天讨论到WeakHashMap(这个是个弱引用的 ... -
《重构》读书笔记
2010-05-09 00:05 1045Martin Fowler于2003年出版 ... -
RPC之WebServices&RMI&JMS,phprpc框架?(待续)
2010-05-06 22:31 55前段时间写过基本的WebServices,也没再做深入 ...
相关推荐
IAP(In-Application Programming)是该芯片上的一种功能,允许在应用运行过程中更新固件,无需额外的编程设备。本项目主要关注如何使用C和C++语言在STM32F103C8T6上实现IAP引导加载程序。 首先,理解STM32的IAP...
1. **CDU.ioc**:这个文件可能是I/O配置文件,用于描述硬件接口如何与软件通信。在嵌入式系统中,I/O配置通常涉及到设备的输入输出设置,如按钮、LED、传感器等。 2. **.mxproject**:这是一个MATLAB的项目文件,...
STM32F4系列是意法半导体...开发者可以根据`.ioc`和`.mxproject`文件恢复或导入到相应的IDE中,然后在`Src`和`Inc`目录中添加或修改代码,利用`Drivers`中的库进行外设操作,最终实现所需的嵌入式功能。
- `EEG.ioc`:这是CubeMX的项目配置文件,保存了所有外设的配置细节。 - `README.md`:项目的说明文档,可能包含如何编译、运行该项目的指南。 - `Inc`:包含头文件,例如类定义和其他全局声明。 - `MDK-ARM`:...
"Src"中可能有主控程序和其他功能模块的C/C++源码,而“Drivers”则可能包含STM32的HAL库驱动,这些驱动代码用于管理硬件资源,如GPIO、ADC、TIM等,为上层应用提供便利。 最后,“Inc”目录下一般存放头文件,...
在这个项目中,这可能包含C或C++语言编写的嵌入式程序,用于控制硬件设备,如LED的显示逻辑、报警机制等。源代码是实现特定功能的核心,通过编程实现对微控制器的指令。 3. **Inc** 文件夹: 这个文件夹可能包含...
AC6提供了更好的优化和对C++11标准的支持,但可能在语法或选项设置上与AC5有所不同,开发者需要关注这些差异以避免编译错误。 标签"F429 AC6 LWIP"进一步明确了关键组件:STM32F429微控制器、AC6编译器以及LWIP协议...
6. **Inc** - 头文件目录,通常存放C/C++的头文件,这些文件可能包含了OLED显示所需的函数声明和其他相关定义。 根据这些信息,我们可以推测这个实验涉及以下知识点: 1. **OLED显示原理**:了解OLED的工作机制,...
5. `Inc` - 通常包含头文件,是C/C++程序中的接口定义。 6. `MDK-ARM` - 这是Keil μVision的项目文件夹,可能包含源代码、配置文件等。 7. `Src` - 源代码目录,存放项目的主要C或C++源文件。 8. `Drivers` - 驱动...
1. **EX_TOUCH.ioc**:这是STM32 CubeMX的配置文件,扩展名.ioc表示Input/Output Configuration。在这个文件中,用户可能已经配置了触摸屏相关的外设,如STM32的GPIO、ADC或触摸控制器,用于读取触摸输入。 2. **....
- **All in one单工程到分层架构**:早期可能采用All in one的单一工程模式,随着项目发展,逐渐演变为分层架构,包括表现层、业务层、服务层和数据层,以提高代码的可维护性和可扩展性。 - **模块化**:进一步...
2. **LSD4RF-Test2002.ioc**: 这个文件可能是使用某种图形化界面工具(如 IAR Workbench 或 Proteus)创建的一个工程配置文件,其中包含了项目的电路设计和测试配置。LSD4RF 可能是用户自定义的模块或者设备代号,...
5. `Inc`:这个目录存放头文件,.h文件定义了函数原型、结构体、枚举类型等,供源代码中的其他模块引用。头文件通常用于声明函数和全局变量,确保编译时的正确链接。 6. `Drivers`:驱动程序库文件夹,这里包含的是...
1. "USB_CDC.ioc":这是Keil μVision的一个项目配置文件,记录了工程的编译设置、调试器配置等信息。 2. ".mxproject":这是基于Keil的MDK的项目文件,包含了工程的构建设置、源文件组织等信息。 3. "Src":源代码...
首先,我们来看"ucosii_hal.ioc"文件,这是一个IoC(Input/Output Configuration)文件,通常用于配置工程中的各个组件,如外设驱动、中断服务程序等。在uC/OS-III中,这个文件可能包含了一些关于硬件资源分配、中断...
#define IO_RCVALL _WSAIOW(IOC_VENDOR, 1) DWORD dwValue = 1; ioctlsocket(socket, IO_RCVALL, &dwValue); char buffer[65535]; int len = ::recv(socket, buffer, sizeof(buffer), 0); ip_head *ip = (ip_head*...
1. `Test2_ProjectKey.ioc`:这是Cubemx的配置文件,包含了对STM32F103的时钟、GPIO、定时器等配置信息,是整个项目的基石。 2. `.mxproject`:这是Cubemx项目文件,用于保存工程的相关设置,以便于在开发环境中...
- `Src`:源代码目录,包含项目的C/C++源文件。 - `Drivers`:驱动库目录,包含STM32的HAL库或其他驱动代码。 - `MDK-ARM`:可能包含Keil MDK(uVision)的工程文件,用于编译和调试。 - `Inc`:头文件目录,...
1. 《Spring in action》:Spring 框架是 Java 程序员必须学习的,IOC、AOP 是软件开发的基础。 2. 《精通 Spring 2.x: 企业应用开发详解》:提供了 Spring 中的实例和实用情况。 3. 《iBatis 实践》:iBatis 必看...
2. `STM32L071.ioc`:这是STM32CubeMX的项目配置文件,包含了STM32L071微控制器的配置信息,如时钟设置、外设选择等。 3. `.mxproject`:这是Keil MDK工程文件,记录了工程的配置信息,包括源文件、库文件、目标设置...