如前方所述,ELite是一种披着命令式语法外也的函数式语言,同时,ELite也是一种面向对象的语言。在ELite中,您不仅可以无缝的引用Java中的类,也完全可以在ELite中定义自己的对象。
以下从面向对象语言的几个基本特征简单展开介绍一下。
在ELite中声明一个对象是非常简单的,其关键词为 class,在对象内部,可以拥有数据(变量)与函数(方法),以下是一个简单示例。
上述代码定义了一个对象 Employee,它拥有两个属性,分别是 name 与 salary。
对象定义好以后,我们可以声明上述对象的一个实例,使用关键词:new
> require "../sample/employee.xel"; > define emp = new Employee(); > emp.name = "Kevin"; Kevin > emp.salary = 5000; 5000
例 38.3. 改进后的employee.xel
class Employee {
define name;
define salary;
Employee(name, salary) {
this.name = name;
this.salary = salary;
}
}
上述代码中,我们使用了另一个关键词“this”,此处的this同Java语言非常类似。
> require "../sample/employee.xel";
> define emp = new Employee("Kevin", 5000);
> emp.name;
Kevin
> emp.salary;
5000
>
例 38.4. 另一种写法的employee.xel
class Employee {
define name;
define salary;
define Employee = {name, salary =>
this.name = name;
this.salary = salary;
}
}
只是上述写法,与我们传统意义上理解的对象的构造函数不太形象,因此,我们并不推荐您这样做,在这里,笔者只是想向你重复:Lambda表达式是ELite的理论支撑。
与Java语法同样类似的还有:如果我们没有指定构造函数,则对象默认拥有一个空参数的构造函数。
例 38.5. 声明函数的employee.xel
class Employee {
define name;
define salary;
Employee(name, salary) {
this.name = name;
this.salary = salary;
}
void adjust(percent) {
salary = salary * (1 + percent);
}
}
> require "../sample/employee.xel";
> define emp = new Employee("Kevin", 5000);
> emp.adjust(0.1);
> emp.salary
5500.0
>
在ELite中,针对类成员变量、类函数方法,可以通过“public”、“protected”、“private”等关键词修饰,其含义与Java语法保持一致。在ELite中,没有package的概念,因此,如果变量或函数没有修饰符,则默认为“public”。
在ELite中定义的对象,可以继承某个基类,与Java类似的是:ELite只支持单根继承。在下例中,我们实现了三个拥有继承关系的类:
例 38.6. inheritance.xel
class Art {
Art() {
print("Art constructor");
}
}
class Drawing extends Art {
Drawing() {
print("Drawing constructor");
}
}
class Cartoon extends Drawing {
Cartoon() {
print("Cartoon constructor");
}
}
我们在交互式窗口中测试一下:
> require "../sample/inheritance.xel"; > define c = new Cartoon(); Cartoon constructor Drawing constructor Art constructor > c.getClass(); class org.operamasks.el.eval.closure.DefaultClosureObject > c instanceof Object true > c is Cartoon true > c is Drawing true > c is Art true >
ELite中的对象,也可以继承自Java类。下例中,MyThread继承自java.lang.Thread:
我们现在可以执行此线程:
> require "../sample/thread.xel"; > new MyThread().start(); > MyThread is running...
同样的,ELite中的对象,可以实现一个或多个接口,但需要提醒您的是:在ELite中,您无法定义自己的接口,更确切的说法是:您根本无需定义自己的接口(在多态章节中会给您剖析原因),但您可以实现某个或多个Java中的interface。将上述线程的例子改写一下:
例 38.8. interface.xel
class MyRunnable implements Runnable {
void run() {
print("MyRunnable is runing");
}
}
调用代码如下:
> require "../sample/interface.xel"; > define r = new MyRunnable(); > new Thread(r).start(); > MyRunnable is runing
多态(Polymorphism)是面向对象语言的核心特征之一,同时,也是函数式编程的重要特征。但在面向对象语言中的多态,与函数式编程中的多态是有不同色彩不同含义的。我们一般称面向对象语言中的多态为“包容性多态”(Inclusion Polymorphism),而把函数式编程中的多态称为“参数型多态”(Parametric polymorphism)。那么,这两者有什么差异呢?
在面向对象的语言如Java中,假设有一个方法,它接受的参数类型为A 对象(此处是指A Class),那么,我们传递给此方法的参数必须是A对象本身(此处是指A Instance)或者它的子类;如果参数类型是B接口,那么,传递的参数必须是实现B接口的对象,换言之,形参的类型必须能够包容实参的类型(形参是实参的超集),而在方法体内,是把实参当成形参类型来对待的,因此,我们把这种多态称之为“包容性多态”。
但在函数式编程中,则不一样了,它在函数的形参声明中,并不需要指定任何类型,换言之,传递进来的实参可以是任意类型,唯一的前提是:在函数体内,解释器会把实参当成指定的形参来用,只要实参能够“满足”这种调用即可。
说起来有些困惑。在Ruby中,有一个戏谑化的术语:duck type,可以加深您的理解。所谓duck type是这样定义的:如果对象能够像鸭子那样行走,像鸭子那样呱呱叫,那么解释器会很高兴的把它当作鸭子来对待。
举一个简单的例子,假设我们现在要绘制不同类型的图形,在Java中,我们可能会这样定义:
例 38.9. Java示例代码
package demo;
public interface Figure {
public void draw();
}
package demo;
public class Rectangle implements Figure {
public void draw() {
System.out.println("Rectangle drawing...");
}
}
package demo;
public class Circle implements Figure {
public void draw() {
System.out.println("Circle drawing...");
}
}
public class Painter {
public static void main(String[] args) {
Painter painter = new Painter();
painter.paint(new Rectangle());
painter.paint(new Circle());
}
public void paint(Figure f) {
f.draw();
}
}
我们可以看到,在Java中,为了对不同的形状进行绘制,我们首先会抽象出来一个接口:Figure,它有一个方法draw,然后,针对此接口,我们有不同的实现,分别是Rectangle和Circle。并且,在 paint 方法中,它只能接受实现了 Figure 接口的对象实例。
如果我们把上述例子用ELite改写一下,我们可能会这样做:
例 38.10. polymorphism.xel
class Circle {
public void draw() {
print("Circle drawing...");
}
}
class Rectangle {
public void draw() {
print("Rectangle drawing...");
}
}
define paint(f) { f.draw() };
paint(new Rectangle());
paint(new Circle());
运行效果如下:
> require "../sample/polymorphism.xel"; Rectangle drawing... Circle drawing... >
我们可以看到,在ELite中,我们并没有为 Rectangle与Circle这两个对象声明一个通用的接口,相反,在paint函数中,我们假定传递进来的参数都有 draw 方法,因此,只要传递进来的参数符合这个条件,解释器就能够正常运行。这就是所谓的 duke type:传递进来的对象,看起来象鸭子(拥有draw方法),我们就假定它是鸭子。
事实上,ELite的这种参数型多态,较面向对象的包容性多态,拥有较多的便利。
再举一个简单的例子:在Java中的java.lang.Math对象中,有取绝对值的abs函数,在Math中是这样定义的:
例 38.11. java.lang.Math中的部分代码
public static int abs(int a) {
return (a < 0) ? -a : a;
}
public static long abs(long a) {
return (a < 0) ? -a : a;
}
public static float abs(float a) {
return (a <= 0.0F) ? 0.0F - a : a;
}
public static double abs(double a) {
return (a <= 0.0D) ? 0.0D - a : a;
}
为了能够对int、long、float、double等不同类型的参数进行取绝对值操作,我们需要定义四个方法,以处理不同的参数类型。但在ELite中,只需要定义一个方法即可:
> define abs(x) { x < 0 ? -x : x};
> abs(-1);
1
> abs(-1.0);
1.0