- A+
Slice介绍
Slice(Specification Language for Ice)是分离对象接口和实现的重要抽象机制。Slice建立了客户和服务器之间的契约,这些契约描述了应用所需的类型及对象接口。这些描述是中立与语言的,所以客户端和服务端可以使用不同的实现语言。
Slice提供编译工具将Slice定义编译为具体语言的实现代码,即客户端的Proxy code和服务端的Skeleton,这样用户只用把注意力放在业务逻辑的处理上。
开发模型
- l服务端和客户端使用相同的开发环境
- 服务端和客户端使用不同的开发环境
命名规范
必须以ice为扩展名,对于大小写不敏感操作系统(如DOS、WINDOWS),扩展名大小写无关;对于大小写敏感操作系统(如Unix),扩展名必须为小写。
文件格式
Slice是一个自由格式语言,你可以任意使用空格、水平和垂直制表符、换页符或换行符编排代码。Slice文件可以是ASCII文本文件或者UTF-8编码文件(with BOM)。但是Slice标示符只能为ASCII字母和数字,非ASCII字符只用出现在注释中。
预处理
- Slice使用C++预处理器进行预处理,所以可以使用常用预处理指令,如#include和宏定义。
- Slice只允许#include出现在文件开始,位于任何Slice定义之前。
- 需要使用其他Slice文件中类型定义时,可以使用#include包含其他Slice文件,防止多重包含的技巧对Slice同样适用。
- 编译器生成代码时,不会生成#include的Slice文件对应的代码,只会编译传入编译器命令行的文件。
- #include推荐用尖括号而不用双引号,因为前者可以在编译时使用-I选项指定搜索路径,而后者对文件的定位方式会随操作系统而变化,常常不能按期望找到文件。
- #include文件包含路径时,只能用/,而不能用\。
注释
/*
* C-style comment
*/
// C++-style comment
关键字
bool enum implements module struct byte execption int Object throws class extends interface out true const false local sequence void dictionary float LocalObject short double idempotent long string
标识符
l标识符只能使用[a-zA-Z0-9_]中字符,并且必须以字母开头,不能以下划线开头或结尾,不能包含连续的下划线。
- 标识符是大小写无关的但是必须保持大小写一致性,例如TimeOfDay和TIMEOFDAY在一个名字作用域内是相同的标识符,但是Slice强制大小写一致性,一旦引入一个标识符,就必须保持大小写一致,否则编译器认为非法。
- 可以使用实现语言(C++、Java…)中的关键字作为标识符,编译器会自动添加前缀,例如switch在C++中会映射为_cpp_switch,在Java中会映射为_switch。应尽量避免这种情况。
- 也可以使用Slice关键字作为标识符,但必须前加\,对于普通标识符也可在前面加\,这种情况加和不加没有区别。
- 有些标识符是保留的,不允许使用:标识符Ice、以Ice(大小写无关)开头的标识符、以Helper、Holder、Prx、Ptr为后缀的标识符。
- lmodule是用于缓解名字冲突的,编译后C++中映射为namespace,Java中映射为package。
- lmodule可以包含任何合法Slice成分,包括其他module。
- lSlice要求所有定义必须嵌入module中,不允许在全局作用域中定义任何东西。lmodule可以重新打开,例如:module ZeroC{
// definitions here…
};
// Possibly in a different source file:
module ZeroC
{
// more definitions here…
};
基本数据类型
User-Defined Types
(1) Enumerations
和C++语法类似:如enum Fruit {Apple, Pear, Orange};定义了枚举类型Fruit。Slice没有定义枚举值的数值,所以不能假定Orange为2,同时也不能像C++那样指定枚举值,所以不要在枚举值和数值间转换,客户端发送枚举值时直接使用枚举,如Apple。和C++类似,枚举名字不会限制在花括号作用域内,不能重定义相同的名字,例如Apple不能出现在另一个枚举定义中。Slice不允许空枚举。
(2) Structures
Slice支持结构体包含一个或多个命名的任意类型成员,例如:
struct TimeOfDay
{
short hour;
short minute;
short second;
};
和枚举不同,结构体形成名字空间,所以其中的成员名字只需要在结构体中唯一。
结构体内除了命名的类型成员外不能包含其他成员,例如不能在结构体内部再定义结构体(更一般的在Slice中类型不能嵌套定义),结构体的基本类型及枚举成员可以有一个初始值,例如:
struct Location
{
string name;
Point pt;
bool display = true;
string source = “GPS”;
};
(3) Sequences
Sequences是包含可变个数任意相同类型元素的集合。定义方法例如:sequence<Fruit> FruitPlatter;这样就定义了新的序列类型FruitPlatter。在C++中sequence被映射为std::vector。Sequence的一个惯用法是用作可选值,例如某个字段为string类型,我们是无法知道此字段是否有值的,如果将此字段类型定义为sequence<string>,那么就可以用序列为空表明没有值。
(4) Dictionaries
Dictionary用于定义键类型到值类型的映射,从而方便查找,定义方法如:
dictionary<string, string> WeekdaysEnglishToGerman;就定义了类型WeekdaysEnglishToGerman。C++中dictionary被映射为std::map。注意dictionary的键类型只能为整形(包括枚举)、string或只包含整形、string的结构体,值类型可为任意类型。
(5) Constant Definitions and Literals
Slice允许定义基本类型及枚举类型的常量。如:
const bool AppendByDefault = true;
const byte LowerNibble = 0x0f;
const string Advice = “Don’t Panic!”;
Slice字面常量语法和C++是基本一致的。只有少数差别,如不支持用于表明long和unsigned的后缀(l,L,u,U),可用l或L后缀表明扩展精度浮点数。
Interfaces,Operations,and Exceptions
(1)Interface
前面已经讲过Interface的概念,Interface的定义方法如:
Interface Clock
{
TimeOfDay getTime();
void setTime(TimeOfDay time);
};
Interface中除了方法不能包含其他任何东西,Interface可以看作C++中类定义的公有部分或Java interface,而方法可以看作(虚拟)成员函数。
(2)Parameters and return values
方法有一个返回值和0或多个参数,例如:void Fun(int i, out string str);
- 如果方法不返回值,用void表明;
- 参数分输入参数和输出参数,输出参数用out关键字标识,out参数必须位于输入参数之后;
- 参数名不能省略;
- 不支持输入输出参数。
(3) 重载
Slice不支持任何形式的重载。一个接口中的方法名必须不同。
(4) Idempotent Operations
前面讲过Ice使用at-most-once语义,避免方法被执行多于一次,但是有些时候重复执行一个方法是无害的,例如x = 1,重复执行是没有问题的,这时可以将方法定义为idempotent,这样Ice就可以做更多积极的尝试恢复错误,使得应用毫无察觉,提高系统的健壮性和鲁棒性。定义idempotent方法如下所示:
interface Clock
{
idempotent TimeOfDay getTime();
idempotent void setTime(TimeOfDay time);
}
(5) User Execptions
除了Ice runtime定义了一系列异常,用户还可以定义自己的异常,自定义的异常由服务端产生,传递到客户端,异常的定义类似于结构体,但是可以为空。例如:
exception Error{};
execption RangeError
{
TimeOfDay errorTime;
TimeOfDay minTime;
TimeOfDay maxTime;
}
和结构体一样,可以为基本类型和枚举类型成员设置初始值,需要产生异常的方法必须通过异常规范列出,例如:
interface Clock
{
idempotent TimeOfDay getTime();
idempotent void setTime(TimeOfDay time) throws RangeError, Error;
};
方法不能抛出未列在异常规范中的异常,否则Ice运行时会抛出异常;如果方法不抛出异常,只要删除异常规范列表。
异常不能当成第一类数据类型,第一类数据类型也不能当成异常。
(6) Exception Inheritance
异常是可以继承的,例如:
Exception ErrorBase
{
string reason;
}
Exception RuntimeError extends ErrorBase
{
…
};
方法的异常规范列表中可以只列出基类异常,这样方法内可以抛出所有基类和子类异常;异常只支持单继承。
(7) Ice Run-Time Exceptions
(8) Interface Semantics and Proxies
interface经过编译后会自动生成客户端proxy和服务端骨架,在Slice中interface后跟“*”表示proxy,例如:
module Demo{
exception BadZoneName{…};
interface Clock
{
…
};
interface WorldTime
{
idempotent void addZone(string zoneName, Clock* zoneClock);
idempotent Clock* findZone(string zoneName) throws BadZoneName;
…
};};
“*”表示proxy operator,其左边必须为一个interface。 Clock* 表示Clock接口的代理。上述Slice编译成C++后是:
(8) Interface Semantics and Proxies
namespace IceProxy
{
namespace Demo
{
class Clock : virtual public ::IceProxy::Ice::Object
{…};
class WorldTime : virtual public ::IceProxy::Ice::Object
{…};
}
}
namespace Demo
{
typedef ::IceInternal::ProxyHandle< ::IceProxy::Demo::Clock> ClockPrx;
typedef ::IceInternal::ProxyHandle< ::IceProxy::Demo::WorldTime> WorldTimePrx;
}
上述生成代码为客户端proxy,客户端通过ClockPrx和WorldTimePrx访问服务端对象。
(8) Interface Semantics and Proxies
namespace Demo
{
class Clock : virtual public ::Ice::Object
{…};
class WorldTime : virtual public ::Ice::Object
{
public:
…
virtual void addZone(const ::std::string&, const ::Demo::ClockPrx&, const ::Ice::Current& = ::Ice::Current()) = 0;
virtual ::Demo::ClockPrx findZone(const ::std::string&, const ::Ice::Current& = ::Ice::Current()) = 0;
…
};
}
上述生成代码为服务端skeleton,需要从Clock和WorldTime继承,实现其接口。
(9) Interface Inheriance
接口支持继承,也支持多继承,菱形继承也是允许的。例如:
interface B{/*…*/};
interface I1 extends B {/*…*/};
interface I2 extends B {/*…*/};
interface D extends I1, I2{/*…*/};
接口继承的限制是,多个基类中不能有同名的方法。
所有接口隐式继承于Ice::Object接口,不允许显示从其继承。因此Object可以作为任何接口使用,例如:
interface ProxyStore
{
idempotent void putProxy(string name, Object* o);
idempotent Object* getProxy(string name);
};
(10) Self-Referential Interfaces
由于proxy有指针语义,所以我们可以定义自引用接口,如:
interface Link
{
idempotent SomeType getValue();
idempotent Link* next();
}
(11) Empty Interfaces
空接口是合法的。但大多数时候,空接口表明一种设计错误。
Classes
class可以同时包含数据成员和方法。
(1) Simple Classes
除去可以包含方法,class的定义和struct基本是一样的,除了class可以为空。另外Class还包含如下特性。
(2) class inheritance
class还可以支持继承,使用extends关键字。和接口继承不一样,class只支持单继承,同样是子类不能重定义基类成员。
(3) Class Inheritance Semantics
class和structures一样也是用传值语义,不同的是由于继承的原因,在需要基类参数的地方可以传递子类参数,如果接收者知道子类的静态类型,它会接收到子类型的全部信息,否则会切片为基类类型。通过type ID可以将接收的基类类型转化为实际的运行时子类型。
(4)Classes as Unions
运用上述特性,可以实现Union的语义。
(5) Self-Referential Classes
class可以自引用,例如:
module Test{
class Link
{
int value;
Link next;
};};
编译为C++后得到:
namespace Test
{
class Link;
typedef ::IceInternal::Handle< ::Test::Link> LinkPtr;
class Link : virtual public ::Ice::Object, private IceInternal::GCShared
{
public:
::Ice::Int value;
::Test::LinkPtr next;
};
}
(6) Classes Versus Structures
classes提供如下structures没有的特性:
支持继承、可自引用、可以包含操作、可以实现接口。当然这些必然需要一些代价,如生成代码大小及运行时内存及CPU开销。使用者必须进行权衡。
(7) classes with operations
class中可以包含方法,语法和Interface中相同,但是class中的方法是local的,不会导致远程过程调用。当在服务端和客户端间传递一个class实例时,Ice运行时只会序列化class的数据成员,如果class中包含方法,接收方要提供类工厂在接收方的地址空间中实例化类。
(8) Architectural Implications of classes
使用包含操作的类实际上就使用了客户端本地代码,因此,就无法享受Interface所提供的实现透明性。这意味着包含操作的类只能用在客户端开发环境进行紧密控制的情况,否则不要使用包含操作的类。
(9)Classes Implementing Interfaces
和Java的语法类似,class可以使用implements关键字实现interface,且和继承不同,可以实现多个接口。
(10)Class Inheritance Limitations
同接口继承一样,类不能重定义从基类或接口中继承来的操作或数据成员。
(11)Pass-by-Value versus Pass-by-Reference
和接口一样,类名后跟*,就是传引用,使用的proxy语义,类名直接使用就是传值。
例如:
module Demo{
class A{};
interface Example
{
A * get(); // return a proxy,pass-by-ref
void put(A aa);// pass-by-value
};
};
(12)Passing Interfaces by Value
和类一样,直接出现接口名,就表示传值。
module Family {
interface Child; // Forward declaration
sequence<Child*> Children; // OK
interface Parent {
Children getChildren(); // OK
};
interface Child { // Definition
Parent* getMother();
Parent* getFather();
};
interface Base; // Forward declaration
interface Derived1 extends Base {}; // Error!
interface Base {}; // Definition
interface Derived2 extends Base {}; // OK
};
Type IDs
每个用户自定义Slice类型有一个内部的类型标识,称为type ID。type ID就是类型的权限定名,并总是以::开始,并以::分隔,例如上例中Child接口的type ID是::Family::Child;代理的type ID是相应类型的type ID后加*,例如Child的代理的type ID是::Family::Child*;内建基本类型,如int,bool等的type ID同关键字,例如int就是int,string就是string。
type ID可以用于ice_isA方法,判断运行时类型。
Operations on Objects
Object是slice的关键字,所有interface或class都默认从Object继承,不能显式从Object继承,Object上有一些通用方法:
ice_ping:如果服务器对象不存在或无法到达则抛出异常;
ice_isA:测试目标对象是否支持特定类型;
ice_id:返回type ID;
ice_ids:返回所有支持的type ID;
Local Types
为了访问Ice运行时的某些特性,必须使用其API,为了避免各种不同实现语言各自定义自己的一份API,Ice也用Slice来定义运行时提供的API,这些API就是使用local关键字定义的。任何Slice定义可以使用local关键字,使用local关键字,Slice编译器不会生成相应类型的序列化代码,这意味着local类型不能远程访问因为它不能在客户端和服务器间传输。另外,local接口或类不是从Ice::Object继承,而是从Ice::LocalObject继承。通常很少需要在自己的应用中使用local关键字,只有一个特定是servant locators。
Names and Scoping
(1)Naming Scopes
如下Slice结构会建立一个名字空间:
the global(file) scope
modules
interfaces
classes
structrues
execptions
parameter lists
在同一个名字空间中,标识符必须唯一
(2)Case Sensitivity
仅仅大小写不同的标识符认为是相同的;
一旦定义了某个标识符,就必须保持相同的大小写形式使用它;
标识符不能和关键字仅仅是大小写的差别。
(3)Qualified Names
在一个作用域引用另一个作用域中的名字,可以通过::操作符。
(4)Names In Nested Scopes
内部作用域可以重定义(隐藏)外部作用域的名字。但是形同名字结构不能彼此嵌套,如:
module M {
interface M {/*…*/} // Error!
interface I {
void I(); // Error!
void op(string op); // Error!
};
struct S {
long S; // Error, even if case differs!
};
};
module Outer {
module Inner {
interface Outer { // Error!
};
};
};
(4)Introduced Identifiers
一旦引入一个符号,就不能改变其含义:
module M {
sequence<string> Seq;
interface Bad {
Seq op1(); // Seq and op1 introduced here
int Seq(); // Error, Seq has changed meaning
};
};
全限定名字(以::开始)不引入符号:
module M {
sequence<string> Seq;
interface Bad {
::M::Seq op1(); // Only op1 introduced here
int Seq(); // OK
};
};
限定名只引入名字的第一部分:
module M {
sequence<string> Seq;
interface Bad {
M::Seq op1(); // M and op1 introduced here, but not Seq
int Seq(); // OK
};
};
(5)Name Lookup Rules
先在当前作用域查找,如果找到则使用当前作用域的名字,否则向外层作用域查找,直到全局作用域。如果是全限定名,则总是从全局作用域查找。
Metadata
可以在任何Slice定义前添加元数据,改变编译后产生的代码。一般的格式是[“”, “”, “”……],例如[“java:type:java.util.LinkedList”] sequence<int> IntSeq;表示生成java代码时使用java.util.LinkedList表示sequence。关于所有metadata的说明见附录B。
Serializable Objects
对于Java和.NET,Ice允许本地Java和CLR对象作为参数进行传递。Ice运行时自动序列化和解序列化这些对象。这种机制允许传递Java和CLR对象而不需要相应的Slice定义。
使用方法是:在需要使用这种机制的类型定义前添加加元数据,例如:
[“java:serializable:SomePackage.JavaClass”]
sequence<byte> JavaObj;
interface JavaExample {
void sendJavaObj(JavaObj o);
};
[“clr:serializable:SomeNamespace.CLRClass”]
sequence<byte> CLRObj;
interface CLRExample {
void sendCLRObj(CLRObj o);
};
虽然这种机制带来了一定便利,但是同时也打破了语言透明性,因为一旦采用这种机制,就意味着服务端和客户端必须使用相同语言。
Deprecating Slice Definitions
Slice编译器支持deprecate元数据,例如:
interface Example {
[“deprecated:someOperation() has been \
deprecated, use alternativeOperation() \
instead.”]
void someOperation();
void alternativeOperation();
};
使用deprecate元数据,编译器会生成代码,当应用代码使用了deprecate的特性时,会产生警告信息。
Using the Slice Compilers
针对不同的语言,Ice提供了不同的编译器:
C++:slice2cpp
Java:slice2java
C#:slice2cs
Python:slice2py
Ruby:slice2rb
Objective-C:slice2objc
上述编译器都有着相似的语法:
<compiler-name> [options] file…
常用选项包括:
-DNAME:定义预处理符号NAME
-DNAME=DEF:定义预处理符号NAME,且值为DEF
-IDIR:添加#include的搜索目录
--output-dir DIR:指定生成文件的输出目录
可以一次编译多个源文件,例如:
slice2cpp -I. file1.ice file2.ice file3.ice
Slice Checksums
为了比较服务端和客户端是否遵循相同的接口契约,Ice编译器支持选项(--checksum),生成每个接口的checksum,只要接口没有变化,checksum就没有变化,通过比较客户端和服务端的checksum就可以知道是否遵循相同的契约。
Generating Slice Documentation
使用slice2html可以将Slice定义中的注释直接生成文档。当然注释必须按照一定规则书写。
- 安卓客户端下载
- 微信扫一扫
- 微信公众号
- 微信公众号扫一扫