第五章 面向对象编程(三)

来源:互联网 发布:安知玉如意妈妈网 编辑:程序博客网 时间:2024/05/07 06:01

第五章 面向对象编程(三)


访问基类

 

当访问类中的虚拟方法时,派生类中的方法的版本被调用,就是说,如果你想调用基类中的方法,而它已经被派生类覆盖,那么,就会自动调用派生类中的版本。这通常用于调用想被覆盖的方法的基本实现。这并不总是需要,但是,对于库函数设计是需要的;因为,如果不这样做,它会导致基类的出问题。

为了访问基类中的方法,要使用关键字 base。下面的例子实现一个类,从 System.Windows.Form 中派生。使用了隐式类构造,类型 MySquareForm 有一个参数 color:

module File3

 

open System.Drawing

openSystem.Windows.Forms

 

//define a class that inherits from 'Form'

type MySquareForm(color)=

  inherit Form()

  // override the OnPaint method to draw on the form

  override x.OnPaint(e) =

    e.Graphics.DrawRectangle(color,

                             10, 10,

                             x.Width - 35,

                             x.Height - 60)

    base.OnPaint(e)

  // override the OnResize method to respond to resizing

  override x.OnResize(e) =

    x.Invalidate()

    base.OnResize(e)

 

//create a new instance of the form

let form = newMySquareForm(Pens.Blue)

 

//show the form

doApplication.Run(form)

 

[

在交互式窗口中使用下面的语句:

 

form.ShowDialog()

 

否则,会出错:

System.InvalidOperationException:在单个线程上开始另一个消息循环是无效操作。

]

 

在这个窗体中,我们覆盖了两个方法OnPaint 和 OnResize;在这些方法中,使用了关键字 base,它授权访问基类,用来调用这个方法的基类实现。

 

 

属性和索引器

 

属性是一种特别类型的方法,对于调用它的代码来说,它看起来就像一个值;索引器也实现了类似的用途,对于调用它的代码来说,使方法更有一点儿像集合。属性和索引器都有存取器(accessors),包括一个用于读的取(get accessors)和用于写的存(set accessors)。

属性的定义,与方法相同,用关键字 member,加表示对象的参数,加点,加成员名;之后,不用方法参数,而是用关键字 with,加 get 或 set;后面是参数,get 方法的参数是空类型,set 方法必须有一个唯一的参数;加等号,加表达式,构成方法体。如果需要第二个方法,就用关键字 and 把它们连接到一起。

下面的例子定义了一个类,有唯一的属性 MyProp,返回一个随机数。存属性重置随机数生成的种子:

 

// aclass with properties

type Properties() =

  let mutable rand =new System.Random()

  // a property definition

  member x.MyProp

    with get () = rand.Next()

    and set y = rand <- new System.Random(y)

 

//create a new instance of our class

let prop = new Properties()

 

//run some tests for the class

prop.MyProp<- 12

printfn"%d" prop.MyProp

printfn"%d" prop.MyProp

printfn"%d" prop.MyProp

 

下面是运行的结果:

[

说是随机数,但是,可能不太像,因为每次运行结果都完全一样。

]

 

2137491492

726598452

334746691

 

也可以声明抽象属性,语法是相似的,只是关键字 member 换成了 abstract;省略表示对象的参数,就像在方法中做的一样;成员名的后面是用冒号隔开的类型名;加关键字 with,之加 get 或 set,表示继承的方法必须实现 get 或 set,用逗号隔开。对于调用它的代码来说,属性跟字段完全一样。

下面的例子是对前面代码的修改,现在使用接口IAbstractProperties。注意,派生类ConcreteProperties 必须使用关键字 with 和 and 实现 get 和 set 方法。

 

// aninterface with an abstract property

type IAbstractProperties=

  abstract MyProp: int

    with get, set

 

// aclass that implements our interface

typeConcreteProperties() =

  let mutable rand =new System.Random()

  interface IAbstractProperties with

    member x.MyProp

      with get() = rand.Next()

      and set(y) = rand <- new System.Random(y)

[

let prop = newConcreteProperties() :> IAbstractProperties

 

//run some tests for the class

prop.MyProp<- 12

printfn"%d" prop.MyProp

printfn"%d" prop.MyProp

printfn"%d" prop.MyProp

]

 

索引器是有两个以上参数的属性,一个表示在伪集合中元素的位置,其他的表示在集合中的索引。在 C# 中,所有的索引器在底层实现上都是Item,但是,程序员们从来就不用这个名字,因为它总是隐含的;在 F# 中,程序员可以选择索引器属性的名字。如果选择的名字 Item,那么,会提供专门的语法访问这个属性。

创建索引器的语法与属性相似,但是,get 方法可有一个或多个参数,而 set 方法,有两个或多个参数;下一步是访问索引器中的元素,如果它的名字叫是Item,那么,就使用专门的类似访问数组的语法,只是把圆括号换成方括号:

 

// aclass with indexers

typeIndexers(vals:string[]) =

  // a normal indexer

  member x.Item

    with get y = vals.[y]

    and set y z = vals.[y] <- z

  // an indexer with an unusual name

  member x.MyString

    with get y = vals.[y]

    and set y z = vals.[y] <- z

 

//create a new instance of the indexer class

let index = new Indexers [|"One"; "Two"; "Three"; "Four"|]

 

//test the set indexers

index.[0]<- "Five";

index.Item(2)<- "Six";

index.MyString(3)<- "Seven";

 

//test the get indexers

printfn"%s" index.[0]

printfn"%s" (index.Item(1))

printfn"%s" (index.MyString(2))

printfn"%s" (index.MyString(3))

 

运行结果如下:

 

Five

Two

Six

Seven

 

注意,当索引器的名字不是 Item时,应该记住,其他 .Net 语言想使用这个类就很困难了。

 

 

覆盖非 F# 库中的方法

 

覆盖非 F# 库中的方法,方法定义必须是以元组的形式,就是必须用括号括起来,用逗号分隔。

下面的例子定义的类实现接口 System.Net.ICredentials,它只有一个方法GetCredential,有两个参数,就在接口实现的后面。如何在方法 GetCredentialList 中把接口当作值使用:

[

为这个方法创建一个 F# 函数

]

 

typeCredentialsFactory() = class

  interface System.Net.ICredentialswith

    member x.GetCredential(uri, authType) =

      new System.Net.NetworkCredential("rob","whatever", "F#credentials")

  member x.GetCredentialList uri authTypes =

    let y = (x :> System.Net.ICredentials)

    let getCredential s = y.GetCredential(uri, s)

    List.map getCredential authTypes

end

 

在第十四章,将学习更多有关 F# 与 C# 签名之间关系的内容。

 

 

抽象类

 

在 F# 中定义约定(contract)通常可接受的方法就是接口,在大多数情况下都能很好的工作。但是,接口有一个致命的问题:任何一点对接口定义的改变,对客户端代码来说都是破坏性的。这对于将要新建的应用程序来说可能不是问题,因为所有的底层代码对我们来讲,都是可控的。事实上,它甚至是有用的,因为,编译器会自动通知你所有需要改变的代码。然而,如果我们准备发布接口,作为库函数的一部分,那么,改变接口的定义,就会引起问题。例如,假设有一个接口,抽象类定义成约定,这里重要的差别在于,抽象的基类可能有具体的方法和属性,这使得版本化一个抽象基类比接口要容易一些,因为,可以添加具体的成员而不需要做重大改变。不像接口,抽象类可以有具体成员,意思是,类只能从抽象类继承。

抽象类的语法与类完全相同,只是抽象类可以有抽象成员。为了保证添加抽象成员不犯错误,没有提供实现,需要为抽象类加上 [<AbstactClass>] 属性。如果选择使用抽象类,前面的示例User 可能看起来就像这样:

 

// a abstract class that represents a user

// it's constructor takes one parameters,

// the user's name

[<AbstractClass>]

type User(name) =

  //the implmentation of this method should hashs the

  //users password and checks it against the known hash

 abstract Authenticate: evidence: string -> bool

  //gets the users logon message

 member x.LogonMessage() =

   Printf.sprintf "Hello, %s" name

 

 

类和静态方法

 

静态方法类似实例方法,但是它不指向类的任何实例,因此,不能访问类的字段。

创建静态方法,用关键字 static,加关键字 member,加方法名,加参数,加等号,加方法定义。与声明实例方法基本相同,只是多了一个关键字 static,少了表示对象的参数。不要表示对象的参数,是逻辑上的需要,因为,方法不需要访问对象的属性。

静态方法提供了另一种途径来创建对象的新实例,F# 没有提供重载类构造函数的功能,因此,提供静态方法来调用类的构造函数。下面再返回到User 类的示例,这一次增加一个静态方法,根据数据库中的用户唯一标识来创建用户:

 

open Strangelights.Samples.Helpers

 

// a class that represents a user

// it's constructor takes two parameters,the user's

// name and a hash of their password

type User(name, passwordHash) =

  //hashs the users password and checks it against

  //the known hash

  memberx.Authenticate(password) =

    lethashResult = hash (password, "sha1")

    passwordHash= hashResult

 

  //gets the users logon message

  memberx.LogonMessage() =

    Printf.sprintf"Hello, %s" name

 

  //a static member that provides an alterative way

  //of creating the object

  staticmember FromDB id =

    letname, ph = getUserFromDB id

    newUser(name, ph)

 

let user = User.FromDB 1

 

注意,调用静态方法,用的是和方法相关联的类型名,而不是和方法相关联的类型的值。

静态方法也可以为使用的类提供运算符。声明运算符的基本语法与声明其他静态方法相同,但是方法名换成了放在括号中的运算符。运算符的参数必须是元组,通常,需要使用类型注释,来指示其类型。

下面的例子假设你想在类 MyInt 中重新实现整型,并在类中定义加法:

 

type MyInt(state:int) = class

  memberx.State = state

  staticmember ( + ) (x:MyInt, y:MyInt) : MyInt = new MyInt(x.State + y.State)

  overridex.ToString() = string state

end

 

let x = new MyInt(1)

let y = new MyInt(1)

 

printfn "(x + y) = %A" (x + y)

 

运行结果如下:

 

(x + y) = 2

 

 

有明确字段和构造函数的类

 

本章到这里,关注还只是类的隐式语法,但是,F# 还有另一种类型的语法:显式语法。隐式语法通常更好的原因:第一,往往比显式语法更短;第二,F# 编译器能够优化这种类。隐式语法通常就是 let 绑定,和给类的构造函数的参数不会成为类中的字段;有了隐式语法,只要有可能(使对象在内存中更小),编译器可以自由地删除。然而,这些优化有时也会有问题。某些使用反射(reflection)的APIs,要求类和它们的实例有选定的字段;这样,作为程序员就需要使用显式语法才能有额外的控制能力。

在显式语法中,在类型名的后面不需要类的构造函数;相反,类型名后直接加等号,加关键字 class,加类的定义(比如,type User =class ...)。当使用显式语法时,类的结尾,用关键字end。

定义字段,使用关键字 val,加字段名和类型名,之间用冒号隔开。默认情况下,类中的字段是不可变的,就是说,一旦绑定到一个值,这个值只能重新绑定到另外的值。偶尔,重新绑定到其他值,也可能是有用的;这样,F# 就提供了关键字 mutable;当字段用关键字 mutable 定义,无论程序员什么时候选择,都能重新绑定。

为了能够创建类的一个实例,必须显式增加构造函数。这样做,需要增加一个成员,它总是命名为 new,加构造函数[ ?,好像应该是构造函数的参数],用括号括起来;加等号,加程序块(用大括号括起来),包含初始化中类中的每一个字段的表达式。重载构造函数是可能的,通过增加额外的有不同数量参数的构造函数实现;如果想用不同类型的参数重载,那么,必须提供类型注释。

下面的例子,重写了我们的第一个类,用显式语法,定义了简单的 User 类,代码多了几行;

 

open System.Web.Security

 

// give shorte name to password hashingmethod

let hash =FormsAuthentication.HashPasswordForStoringInConfigFile

 

// a class that represents a user

// it's constructor takes two parameters,the user's

// name and a hash of their password

type User = class

  //the class' fields

  valname: string

  valpasswordHash: string

 

  //the class' constructor

  new(name, passwordHash) =

    {name = name; passwordHash = passwordHash }

 

  //hashs the users password and checks it against

  //the known hash

  memberx.Authenticate(password) =

    lethashResult = hash (password, "sha1")

    x.passwordHash= hashResult

 

  //gets the users logon message

  memberx.LogonMessage() =

    Printf.sprintf"Hello, %s" x.name

end

 


0 0
原创粉丝点击