F#探险之旅(四):面向对象编程(中)(转)
2011-01-14 13:00
148 查看
对象表达式(Object Expressions)
F#中的OOP语法很简洁,而对象表达式则正是这种简洁性的核心所在。通过对象表达式,我们可以创建抽象类或接口的轻量级实现,也可以对一个具体的
类进行继承。换言之,可以在实现抽象类或接口,或者继承具体类的同时创建新类型的一个实例。下面来看如何对泛型接口
IComparer<’T>应用对象表达式。
运行结果为
这里演示了实现IComparer的过程,该接口有一个方法Compare,它接受两个参数并通过返回值来表示参数比较的结果。它是泛型接口,这里
的类型参数为string,可以在标识符revStringComparer定义的第二行看到。从标识符的名字也可以了解到,它是将参数颠倒后进行比较,
运行结果的第二行印证了这一点。可以看到“在实现接口的同时返回一个实例”。
看看多重继承的情况。在C#中,一个类不能继承多个类,却可以同时实现多个接口,F#也是一样的。要注意的是,如果同时继承类并实现接口,须将类的部分房子最前面;而且必须在第一个类/接口定义完毕之后才能开始第二个接口的实现。
在对象表达式makeNumberControl中,它继承了Control类,同时也实现了IComparable接口。这里可以看到,在
CompareTo方法中根据控件的Tag值进行比较,在调用Sort方法(ResizeArray即泛型类List<’T>)时会根据
Compare方法对控件进行排序。排序完成后,依次呈现这些控件,如下图:
![](http://www.bianceng.cn/upimg/allimg/100607/10092LM9-0.png)
对象表达式是一种强大的机制,可以帮助我们快速而简洁地将非F#库中的对象引入F#代码。它的缺点则是没法添加额外的属性或方法。这也是在前面的例子中为何使用Tag属性来存放n的值。
定义接口
接口仅包含抽象的方法和属性。它为所有实现它的类定义了一份“契约”。F#中接口的概念与C#中的相同,在此不再赘述。如:
实现接口
实现接口的语法也很简单。仍以上面的接口为例:
运行结果为:
不知你有没有注意到,在调用ChangeState方法前,我们先将impl转换为了MyInterface类型,否则不能调用。这是因为接口在F#中是显式实现的,如果希望不经转换就可以直接调用接口的方法,只能在类中在显式地添加这个方法:-(
类、字段和显式的构造函数
前面我们主要还是使用非F#库中的类,现在来看看如何定义自己的类。
嗯,很明显,这里试图定义一个类,然后创建它的一个实例。不过编译器不允许你这么做,它会告诉你:“error FS0191: No
constructors are available for the type
'EmptyClass'”。没有构造函数?如果你是C#程序员就会觉得奇怪了。事实上,F#没有提供默认的构造函数这种机制,我们必须得手工添加一个,
构造函数的名字总是为new。
另外,默认的构造函数容易使得字段不能正确地初始化,会给程序带来隐患,而在F#中的构造函数必须初始化所有字段。使用val关键字定义字段,在下面的类MyFile中,它拥有两个字段,path和innerFile,两个字段在构造函数内进行初始化。
我们还可以看到,这个类有两个构造函数,也就是说构造函数可以重载。观察构造函数new(p),它有一部分在{}内,这个代码块称为构造函数的初始化块,在这里唯一能做的事情就是初始化字段。如果想做更多的事情,就要在后面加then添加其它代码:
new(p)后面加了as x,这样就可以在后面的代码中引用当前的对象了。then后面的代码首先检查文件是否存在,如果不存在就创建一个新文件。
注意:上面两个类MyFile和MyFile2中的字段都是只读的,如果需要修改它们,可以在字段名字前面添加关键字mutable,如val mutable innerFile;同时它们的访问修饰符都是public,在下一篇文章将介绍如何在类中定义属性。
思考:在函数式编程(下)中,曾介绍过自定义的记录类型,比如
那么这里的couple类和上面的MyFile类主要有哪些区别呢?请出我们的老朋友.NET Reflector来吧。在Reflector中打开编译过的F#程序集可以看到,couple的类型定义为:
这是一个sealed类,这意味着无法继承它,其中的her和him都是只读属性。
而MyFile的定义则是:
这个就跟C#中常规类的定义一致了,其中的path和innerFile都是只读属性。
隐式的类构造(Implicit Class Construction)
除了上面的显式构造函数,F#还支持隐式的类构造语法,这样的语法更为紧凑。它允许在定义类的成员前执行一系列的let值绑定语句,这些绑定属于类的私有部分。
F# Code - 隐式的类构造
我觉得对于前面的(显式的类构造)MyFile类定义,这里的MyOtherFile类当然更为紧凑,看起来在定义类的同时就定义了构造函数,随后
马上初始化了字段,不过这里的innerFile已经是私有的了,所以再添加一个属性InnerFile来公开innerFile字段。
隐式的类构造要比等价的显式类构造代码少很多,但是有时必须要用显式的类构造,比如编写拥有多个构造函数的类的时候。
小结
本文首先介绍了强大的对象表达式机制,通过它,我们可以快速地创建抽象类或接口的轻量级实现;接下来是定义和实现接口;最后介绍了如何创建和实例化一个类,在创建类实例的时候,我们既可以采用显式的构造函数,也可以采用更为紧凑的“隐式的类构造”机制。
注意:本文中的代码均在F# 1.9.4.17版本下编写,在F# CTP 1.9.6.0版本下可能不能通过编译。
F#中的OOP语法很简洁,而对象表达式则正是这种简洁性的核心所在。通过对象表达式,我们可以创建抽象类或接口的轻量级实现,也可以对一个具体的
类进行继承。换言之,可以在实现抽象类或接口,或者继承具体类的同时创建新类型的一个实例。下面来看如何对泛型接口
IComparer<’T>应用对象表达式。
F# Code #light open System open System.Collections.Generic let revStringComparer = { new IComparer<string> with Compare(s1, s2) = let rev (s : string) = new string(Array.rev(s.ToCharArray())) let reversed = rev s1 reversed.CompareTo(rev s2) } let winners = [| "Sandie Shaw"; "Bucks Fizz"; "Dana International"; "Abba" |] print_any winners print_newline() Array.Sort(winners, revStringComparer) print_any winners
运行结果为
Output [|"Sandie Shaw"; "Bucks Fizz"; "Dana International"; "Abba"|] [|"Abba"; "Dana International"; "Sandie Shaw"; "Bucks Fizz"|]
这里演示了实现IComparer的过程,该接口有一个方法Compare,它接受两个参数并通过返回值来表示参数比较的结果。它是泛型接口,这里
的类型参数为string,可以在标识符revStringComparer定义的第二行看到。从标识符的名字也可以了解到,它是将参数颠倒后进行比较,
运行结果的第二行印证了这一点。可以看到“在实现接口的同时返回一个实例”。
看看多重继承的情况。在C#中,一个类不能继承多个类,却可以同时实现多个接口,F#也是一样的。要注意的是,如果同时继承类并实现接口,须将类的部分房子最前面;而且必须在第一个类/接口定义完毕之后才能开始第二个接口的实现。
多重继承的例子
#light open System open System.Drawing open System.Windows.Forms let makeNumberControl(n: int) = { new Control(Tag = n, Width = 32, Height = 16) with override x.OnPaint(e) = let font = new Font(FontFamily.Families.[1], 12.0F) e.Graphics.DrawString(n.ToString(), font, Brushes.Black, new PointF(0.0F, 0.0F)) interface IComparable with CompareTo(other) = let otherCtrl = other :?> Control in let n1 = otherCtrl.Tag :?> int in n.CompareTo(n1) } let numberCtrls = let temp = new ResizeArray<Control>() let random = new Random() for index = 1 to 10 do temp.Add(makeNumberControl(random.Next(100))) temp.Sort() let height = ref 0 temp |> Seq.iter (fun c -> c.Top <- !height height := c.Height + !height) temp.ToArray() let myForm = let temp = new Form() in temp.Controls.AddRange(numberCtrls); temp [<STAThread>] do Application.Run(myForm)
在对象表达式makeNumberControl中,它继承了Control类,同时也实现了IComparable接口。这里可以看到,在
CompareTo方法中根据控件的Tag值进行比较,在调用Sort方法(ResizeArray即泛型类List<’T>)时会根据
Compare方法对控件进行排序。排序完成后,依次呈现这些控件,如下图:
![](http://www.bianceng.cn/upimg/allimg/100607/10092LM9-0.png)
对象表达式是一种强大的机制,可以帮助我们快速而简洁地将非F#库中的对象引入F#代码。它的缺点则是没法添加额外的属性或方法。这也是在前面的例子中为何使用Tag属性来存放n的值。
定义接口
接口仅包含抽象的方法和属性。它为所有实现它的类定义了一份“契约”。F#中接口的概念与C#中的相同,在此不再赘述。如:
F# Code type MyInterface = interface abstract ChangeState : int -> unit end
实现接口
实现接口的语法也很简单。仍以上面的接口为例:
F# Code - 实现接口 type MyImpl = class val mutable state : int new() = { state = 0 } interface MyInterface with member x.ChangeState y = x.state <- y end end let impl = new MyImpl() let inter = impl :> MyInterface let printIntAndNewLine i = print_int i print_newline() let main() = inter.ChangeState 1 printIntAndNewLine impl.state inter.ChangeState 2 printIntAndNewLine impl.state main()
运行结果为:
Output 1 2
不知你有没有注意到,在调用ChangeState方法前,我们先将impl转换为了MyInterface类型,否则不能调用。这是因为接口在F#中是显式实现的,如果希望不经转换就可以直接调用接口的方法,只能在类中在显式地添加这个方法:-(
类、字段和显式的构造函数
前面我们主要还是使用非F#库中的类,现在来看看如何定义自己的类。
F# Code #light type EmptyClass = class end let emptyItem = new EmptyClass()
嗯,很明显,这里试图定义一个类,然后创建它的一个实例。不过编译器不允许你这么做,它会告诉你:“error FS0191: No
constructors are available for the type
'EmptyClass'”。没有构造函数?如果你是C#程序员就会觉得奇怪了。事实上,F#没有提供默认的构造函数这种机制,我们必须得手工添加一个,
构造函数的名字总是为new。
F# Code type EmptyClass = class new() = {} end
另外,默认的构造函数容易使得字段不能正确地初始化,会给程序带来隐患,而在F#中的构造函数必须初始化所有字段。使用val关键字定义字段,在下面的类MyFile中,它拥有两个字段,path和innerFile,两个字段在构造函数内进行初始化。
F# Code #light open System.IO type MyFile = class val path : string val innerFile : FileInfo new() = new MyFile("default.txt") new(p) = { path = p; innerFile = new FileInfo(p) } end
我们还可以看到,这个类有两个构造函数,也就是说构造函数可以重载。观察构造函数new(p),它有一部分在{}内,这个代码块称为构造函数的初始化块,在这里唯一能做的事情就是初始化字段。如果想做更多的事情,就要在后面加then添加其它代码:
F# Code type MyFile2 = class val path : string val innerFile : FileInfo new(p) as x = { path = p; innerFile = new FileInfo(p) } then if not x.innerFile.Exists then let textFile = x.innerFile.CreateText() textFile.Dispose() end
new(p)后面加了as x,这样就可以在后面的代码中引用当前的对象了。then后面的代码首先检查文件是否存在,如果不存在就创建一个新文件。
注意:上面两个类MyFile和MyFile2中的字段都是只读的,如果需要修改它们,可以在字段名字前面添加关键字mutable,如val mutable innerFile;同时它们的访问修饰符都是public,在下一篇文章将介绍如何在类中定义属性。
思考:在函数式编程(下)中,曾介绍过自定义的记录类型,比如
F# Code type couple = { him : string; her : string }
那么这里的couple类和上面的MyFile类主要有哪些区别呢?请出我们的老朋友.NET Reflector来吧。在Reflector中打开编译过的F#程序集可以看到,couple的类型定义为:
Type Infomation public sealed class couple : IStructuralHash, IComparable
这是一个sealed类,这意味着无法继承它,其中的her和him都是只读属性。
而MyFile的定义则是:
Type Infomation public class MyFile
这个就跟C#中常规类的定义一致了,其中的path和innerFile都是只读属性。
隐式的类构造(Implicit Class Construction)
除了上面的显式构造函数,F#还支持隐式的类构造语法,这样的语法更为紧凑。它允许在定义类的成员前执行一系列的let值绑定语句,这些绑定属于类的私有部分。
F# Code - 隐式的类构造
#light open System.IO type MyOtherFile(path) = class let innerFile = new FileInfo(path) member x.InnerFile = innerFile end
我觉得对于前面的(显式的类构造)MyFile类定义,这里的MyOtherFile类当然更为紧凑,看起来在定义类的同时就定义了构造函数,随后
马上初始化了字段,不过这里的innerFile已经是私有的了,所以再添加一个属性InnerFile来公开innerFile字段。
隐式的类构造要比等价的显式类构造代码少很多,但是有时必须要用显式的类构造,比如编写拥有多个构造函数的类的时候。
小结
本文首先介绍了强大的对象表达式机制,通过它,我们可以快速地创建抽象类或接口的轻量级实现;接下来是定义和实现接口;最后介绍了如何创建和实例化一个类,在创建类实例的时候,我们既可以采用显式的构造函数,也可以采用更为紧凑的“隐式的类构造”机制。
注意:本文中的代码均在F# 1.9.4.17版本下编写,在F# CTP 1.9.6.0版本下可能不能通过编译。
相关文章推荐
- F#探险之旅(四):面向对象编程(下)(转)
- ART世界探险(8) - 面向对象编程
- 面向对象编程
- Java笔记 第四章(2)Java面向对象编程基础 第二部分(类的成员变量和方法)
- javascript面向对象编程
- 深入理解Javascript面向对象编程
- Java面向对象编程-第14章数组
- SpringMVC深度探险(四) —— SpringMVC核心配置文件详解
- Java 面向对象编程--第十五章 集合(学习笔记)
- javascript面向对象编程
- 面向对象编程的软件设计原则
- javascript高级程序设计笔记-第六章(面向对象编程)
- OOP-面向对象编程
- virtual,abstract,interface的使用和区别(面向对象编程)二
- 浅谈你对面向对象编程的认识
- Javascript面向对象编程(二):构造函数的继承
- 面向对象编程的三大基本特征
- 黄勇-架构探险(从零开始写Java Web框架)第一章节学习日记
- Python面向对象编程——引言