Facebook Twitter LinkedIn E-mail
magnify
Home 2014 二月

Scala开发教程(50): Ordered Trait

比较对象也是胖接口来的比较方便的一个应用领域,当你需要比较两个有顺序关系的对象时,如果只需要一个方法就可以知道需要比较的结果就非常便利。比如,你需要“小于”关系,你希望使用“< “比较就可以了,如果是“小于等于”,使用”<=”就可以。如果使用瘦接口来定义类,也许你只定义了一个<比较方法,那么如果需要小于等于,你可能需要使用(x<y)|| (x==y)。一个胖接口定义了所有可能的比较运算符,使得你可以直接使用<=来书写代码。
但胖接口带来的便利也是有代价的,回头看看我们前面定义的Rational类;

如果我们需要定义比较操作,则需要定义如下代码:

class Rational (n:Int, d:Int) {
 ...

  def < (that:Rational) =     this.numer * that.denom  > that.numer * this.denom

  def > (that:Rational) = that < this

  def <=(that:Rational) = (this < that) || (this == that)   def >=(that:Rational) = (this  > that) || (this == that)

}

这个类定义了四个比较运算符 < ,>,< =和>=。首先我们注意到后面三个比较运算符,都是通过第一个比较运算符来实现的。其次,我们也可以看到,后面三个比较操作对于任意对象都是适用的,和对象的类型无关。而需要实现这四个比较运算的胖接口都要重复这些代码。

Scala对于比较这种常见的操作,提供了Ordered Trait定义,使用它,你可以把所有的比较运算的代码使用一个compare定义来替代。这个ordered trait可以让需要实现比较运算的类,通过和Ordered特质“融合”,而只需实现一个compare方法即可。
因此我们可以修改前面的实现如下:

class Rational (n:Int, d:Int) extends Ordered[Rational]{
  ...

  override def compare (that:Rational)=
    (this.numer*that.denom)-(that.numer*that.denom)

}

要注意两点,一是Ordered 需要指明类型参数 Ordered[T] ,类型参数我们将在后面介绍,这里只需要知道添加所需比较类型的类名称,本例为Rational,此外,需要使用compare方法。 它比较有序对象,=0,表示两个对象相同,>0表示前面大于后面对象,<0表示前面小于后面对象。

下面为测试结果:

scala> class Rational (n:Int, d:Int) extends Ordered[Rational]{
     |   require(d!=0)
     |   private val g =gcd (n.abs,d.abs)
     |   val numer =n/g
     |   val denom =d/g
     |   override def toString = numer + "/" +denom
     |   def +(that:Rational)  =
     |     new Rational(
     |       numer * that.denom + that.numer* denom,
     |       denom * that.denom
     |     )
     |   def * (that:Rational) =
     |     new Rational( numer * that.numer, denom * that.denom)
     |   def this(n:Int) = this(n,1)
     |   private def gcd(a:Int,b:Int):Int =
     |     if(b==0) a else gcd(b, a % b)
     |
     |   override def compare (that:Rational)=
     |     (this.numer*that.denom)-(that.numer*that.denom)
     |
     | }
defined class Rational

scala> val half =new Rational(1,2)
half: Rational = 1/2

scala>   val third=new Rational(1,3)
third: Rational = 1/3

scala> half < third res0: Boolean = false
scala> half >= third
res1: Boolean = true

因此,你在需要实现比较对象时,首先需要考虑Ordered Trait,看看这个Trait能否满足要求,然后通过和这个Trait “混合”就可以很方便的实现对象之间的比较。
此外要注意Ordered Trait 没有定义equal 方法,因为如果需要定义equal方法,那么需要检查传入参数的类型,Ordered Trait无法实现,因此你如果需要==比较运算符,需要另外定义。

 

Scala开发教程(49): Trait示例–Rectangular 对象

在设计绘图程序库时常常需要定义一些具有矩形形状的类型:比如窗口,bitmap图像,矩形选取框等。为了方便使用这些矩形对象,函数库对象类提供了查询对象宽度和长度的方法(比如width,height)和坐标的left,right,top和bottom等方法。然而在实现这些函数库的这样方法,如果使用Java来实现,需要重复大量代码,工作量比较大(这些类之间不一定可以定义继承关系)。但如果使用Scala来实现这个图形库,那么可以使用Trait,为这些类方便的添加和矩形相关的方法。
首先我们先看看如果使用不使用Trait如何来实现这些类,首先我们定义一些基本的几何图形类如Point和Rectangle:


class Point(val x:Int, val y:Int)

class Rectangle(val topLeft:Point, val bottomRight:Point){
  def left =topLeft.x
  def right =bottomRight.x
  def width=right-left 

  // and many more geometric methods
}

这里我们定义一个点和矩形类,Rectangle类的主构造函数使用左上角和右下角坐标,然后定义了 left,right,和width一些常用的矩形相关的方法。
同时,函数库我们可能还定义了一下UI组件(它并不是使用Retangle作为基类),其可能的定义如下:

abstract class Component {
  def topLeft :Point
  def bottomRight:Point

  def left =topLeft.x
  def right =bottomRight.x
  def width=right-left

  // and many more geometric methods

}

可以看到left,right,width定义和Rectangle的定义重复。可能函数库还会定义其它一些类,也可能重复这些定义。
如果我们使用Trait,就可以消除这些重复代码,比如我们可以定义如下的Rectangular Trait类型:

trait Rectangular {
  def topLeft:Point
  def bottomRight:Point

  def left =topLeft.x
  def right =bottomRight.x
  def width=right-left

  // and many more geometric methods
}

然后我们修改Component 类定义使其“融入”Rectangular 特性:

abstract class Component extends Rectangular{
 //other methods
}

同样我们也修改Rectangle定义:

class Rectangle(val topLeft:Point, val bottomRight:Point) extends Rectangular{
  // other methods
}

这样我们就把矩形相关的一些属性和方法抽象出来,定义在Trait中,凡是“混合”了这个Rectangluar特性的类自动包含了这些方法:

object TestConsole extends App{
   val rect=new Rectangle(new Point(1,1),new Point(10,10))

    println (rect.left)
    println(rect.right)
    println(rect.width)

}

运行结果如下:

1
10
9