首页 > 代码库 > Programming in Scala (Second Edition) 读书笔记10

Programming in Scala (Second Edition) 读书笔记10

  1. 你肯定见过在控制台用字符打印图形的程序,这一章就从定义一个图形元素开始。我们的图形元素都是一些字符组成的矩形

abstract class Element {
  def contents: Array[String]
  def height: Int = contents.length
  def width: Int = if (height == 0) 0 else contents(0).length
}

上面定义的三个方法都没有参数,连小括号也省去了。这样的方法叫做:无参方法(parameterless method)

Such parameterless methods are quite common in Scala. By contrast, methods defined with empty parentheses, such as def height(): Int, are called
empty-paren methods. The recommended convention is to use a parameterless method whenever there are no parameters and the method accesses
mutable state only by reading fields of the containing object (in particular, it
does not change mutable state). This convention supports the uniform access
principle,1 which says that client code should not be affected by a decision
to implement an attribute as a field or method.


约定:如果没有参数,就省去括号,并且这样的方法只是读取对象的一个属性

这个约定支持“统一访问原则”,也就是说:无论将一个属性实现为域还是方法,客户代码都不应该受到影响。再看下面的定义

abstract class Element {
  def contents: Array[String]
  val height = contents.length
  val width = if (height == 0) 0 else contents(0).length
}

把方法改成field,客户代码依然可以按照同样的方式访问元素的高和宽。这就是所谓的“统一访问原则”

2.用field重载无参method

The uniform access principle is just one aspect where Scala treats fields and
methods more uniformly than Java. Another difference is that in Scala, fields
and methods belong to the same namespace. This makes it possible for a
field to override a parameterless method
. For instance, you could change
the implementation of contents in class ArrayElement from a method to
a field without having to modify the abstract method definition of contents
in class Element

class ArrayElement(conts: Array[String]) extends Element {
  val contents: Array[String] = conts
}

另一方面,因方法和域属于同样的命名空间,所以不能定义名字相同的方法和域

3.参数化的域(parametric field)

Consider again the definition of class ArrayElement shown in the previous
section. It has a parameter conts whose sole purpose is to be copied into the
contents field.
上面ArrayElement的定义中,参数conts存在的唯一作用就是被复制给域contents.能不能避免这样的多余代码?可以用parametric field做到。下面用parametric field 改写ArrayElement的定义

class ArrayElement(
    val contents: Array[String]
) extends Element

This is a shorthand that defines at the same time a parameter and field with the same name.

4.调用父类的构造函数

class LineElement(s: String) extends ArrayElement(Array(s)) {
  override def width = s.length()
  override def height = 1
}

LineElement needs to pass
an argument to the primary constructor of its superclass. To invoke a superclass constructor, you simply place the argument or arguments you want to
pass in parentheses following the name of the superclass

只需将要传递的参数放入父类类名后面的括号

5.什么时候方法前必须加override ?

Scala requires such a modifier for all members that
override a concrete member in a parent class. The modifier is optional if a
member implements an abstract member with the same name

如果重载了父类的具体方法,override是必须的,如果实现了抽象方法,override是可选的


6.定义另一个类

class UniformElement(
    ch: Char,
    override val width: Int,
    override val height: Int
    ) extends Element {
  private val line = ch.toString * width
  def contents = Array.fill(height)(line)
}


7.禁止重载。 和Java一样,在方法前加final关键字


8.实现above方法,用于将两个Element上下拼接为一个Elment

abstract class Element {
  def contents: Array[String]
  def height = contents.length
  def width = if (height == 0) 0 else contents(0).length
  def above(that: Element) = new ArrayElement(this.contents ++ that.contents)
}

++ 操作用来拼接两个Array.

above方法返回的是ArrayElement对象。由此可见:一个软件系统的各个组件是相互依赖的,如这里ArrayElement依赖Element,Element也依赖ArrayElement。一个软件系统开发的过程中,不是按照严格的先有谁,后有谁的顺序进行的。而是先有一个比较弱的A,然后再A的基础上有了B,然后A又使用B使自己更强大。软件系统不是组装起来的,而是“成长”起来的,就像人的发育过程一样,骨骼和肌肉是一起成长的,而不是一边长好骨骼,一边长好肌肉,然后再组装起来

9.实现beside方法,用于将两个Element左右拼接为一个Element

  def beside(that: Element): Element =  new ArrayElement (
      for (
          (line1, line2) <- this.contents zip that.contents
      ) yield line1 + line2
  )

解释一下这里用到的zip方法

object Test {
  val arr1 = Array(123, 456, 78)                  //> arr1  : Array[Int] = Array(123, 456, 78)
  val arr2 = Array("aaa", "bbb", "cccc")          //> arr2  : Array[String] = Array(aaa, bbb, cccc)
  arr1 zip arr2                                   //> res0: Array[(Int, String)] = Array((123,aaa), (456,bbb), (78,cccc))
}

Zip 的结果是 an array of Tuples


10.重载toString方法

override def toString = this.contents.mkString("\n")

至此,Element类如下:

abstract class Element {
  def contents: Array[String]
  def height = contents.length
  def width = if (height == 0) 0 else contents(0).length
  def above(that: Element): Element = new ArrayElement(this.contents ++ that.contents)
  def beside(that: Element): Element =  new ArrayElement (
      for (
          (line1, line2) <- this.contents zip that.contents
      ) yield line1 + line2
  )
  override def toString = this.contents.mkString("\n")
}

11.添加工厂方法

object Element {
  def elem(contents: Array[String]) =
    new ArrayElement(contents)
  
  def elem(line: String) =
    new LineElement(line)
  
  def elem(ch: Char, width: Int, height: Int) =
    new UniformElement(ch, width, height)
}

12.用工厂方法重写Element类

import Element.elem

abstract class Element {
  def contents: Array[String]
  def height = contents.length
  def width = if (height == 0) 0 else contents(0).length
  def above(that: Element): Element = elem(this.contents ++ that.contents)
  def beside(that: Element): Element =  elem (
      for (
          (line1, line2) <- this.contents zip that.contents
      ) yield line1 + line2
  )
  override def toString = this.contents.mkString("\n")
  
}

13. 将 Element的子类都作为Element对象的私有类

given the factory methods, the subclasses ArrayElement,
LineElement and UniformElement could now be private, because they
need no longer be accessed directly by clients. In Scala, you can define
classes and singleton objects inside other classes and singleton objects. One
way to make the Element subclasses private, therefore, is to place them inside the Element singleton object and declare them private there. The classes
will still be accessible to the three elem factory methods, where they are
needed. Listing 10.12 shows how that will look.

object Element {
  private class ArrayElement(
    val contents: Array[String]) extends Element

  private class LineElement(s: String) extends ArrayElement(Array(s)) {
    override def width = s.length()
    override def height = 1
  }

  private class UniformElement(
    ch: Char,
    override val width: Int,
    override val height: Int) extends Element {
    private val line = ch.toString * width
    def contents = Array.fill(height)(line)
  }
  
  def elem(contents: Array[String]): Element =
    new ArrayElement(contents)

  def elem(line: String): Element =
    new LineElement(line)

  def elem(ch: Char, width: Int, height: Int): Element =
    new UniformElement(ch, width, height)
}

这些elem方法都必须明确指明返回值类型为Element,因为实际返回类型对外已经不可见了


14. 可能已经有人发现,Element类中定义的above 和 beside方法有问题,因为它们不能保证拼接之后的图形还是矩形的

We need one last enhancement. The version of Element shown in Listing 10.11 is not quite sufficient, because it does not allow clients to place elements of different widths on top of each other, or place elements of different
heights beside each other.
为此,做以下改进

  def widen(w: Int): Element = {
    if (w <= width) this
    else {
      val left = elem(‘ ‘, (w - width) / 2, height)
      val right = elem(‘ ‘, width - w - left.width , height)
      left beside this beside right
    }
  }
  
  def highten(h: Int): Element = {
    if (h <= height) this 
    else {
      val top = elem(‘ ‘, width, (height - h)/2)
      val bot = elem(‘ ‘, width, height - h - top.height)
      top above this above bot
    }
  }
  
    def above(that: Element): Element = {
    val this1 = this widen that.width
    val that1 = that widen this.width
    elem(this1.contents ++ that1.contents)
  }
  def beside(that: Element): Element = {
    val this1 = this highten that.height
    val that1 = that highten this.height
    elem (
      for (
          (line1, line2) <- this1.contents zip that1.contents
      ) yield line1 + line2
    )
  }


15.Elemen.scala的完整代码

package chapter10
import Element.elem

abstract class Element {
  def contents: Array[String]
  def height = contents.length
  def width = if (height == 0) 0 else contents(0).length
  def above(that: Element): Element = {
    val this1 = this widen that.width
    val that1 = that widen this.width
    elem(this1.contents ++ that1.contents)
  }
  def beside(that: Element): Element = {
    val this1 = this highten that.height
    val that1 = that highten this.height
    elem(
      for (
        (line1, line2) <- this1.contents zip that1.contents
      ) yield line1 + line2)
  }
  override def toString = this.contents.mkString("\n")

  def widen(w: Int): Element = {
    if (w <= width) this
    else {
      val left = elem(‘ ‘, (w - width) / 2, height)
      val right = elem(‘ ‘, width - w - left.width, height)
      left beside this beside right
    }
  }

  def highten(h: Int): Element = {
    if (h <= height) this
    else {
      val top = elem(‘ ‘, width, (height - h) / 2)
      val bot = elem(‘ ‘, width, height - h - top.height)
      top above this above bot
    }
  }
}

object Element {
  private class ArrayElement(
    val contents: Array[String]) extends Element

  private class LineElement(s: String) extends ArrayElement(Array(s)) {
    override def width = s.length()
    override def height = 1
  }

  private class UniformElement(
    ch: Char,
    override val width: Int,
    override val height: Int) extends Element {
    private val line = ch.toString * width
    def contents = Array.fill(height)(line)
  }
  
  def elem(contents: Array[String]): Element =
    new ArrayElement(contents)

  def elem(line: String): Element =
    new LineElement(line)

  def elem(ch: Char, width: Int, height: Int): Element =
    new UniformElement(ch, width, height)
}


16. 最后一步, 用我们的代码画一些有趣的东西 ^_^

package chapter10
import Element.elem
object TestMain {
  def main(args: Array[String]) {
     val e1 = elem(‘*‘, 5, 5)
     print(e1)
  }
}

/*输出

*****
*****
*****
*****
*****

*/
package chapter10
import Element.elem
object TestMain {
  def main(args: Array[String]) {
     val coner = elem(‘ ‘, 5,5)
     val block = elem(‘+‘, 5, 5)
     val edge =  (coner beside block beside coner )
     val mid = block beside block beside block
     val cross = edge above mid above edge
     print(cross)
  }
}
/*
     +++++     
     +++++     
     +++++     
     +++++     
     +++++     
+++++++++++++++
+++++++++++++++
+++++++++++++++
+++++++++++++++
+++++++++++++++
     +++++     
     +++++     
     +++++     
     +++++     
     +++++ 
*/


Programming in Scala (Second Edition) 读书笔记10