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)
  }
}
/*
     +++++     
     +++++     
     +++++     
     +++++     
     +++++     
+++++++++++++++
+++++++++++++++
+++++++++++++++
+++++++++++++++
+++++++++++++++
     +++++     
     +++++     
     +++++     
     +++++     
     +++++ 
*/
时间: 2024-10-12 04:03:53

Programming in Scala (Second Edition) 读书笔记10的相关文章

Programming in Scala (Second Edition) 读书笔记12 Trais

1.什么是Trait ? Traits are a fundamental unit of code reuse in Scala. A trait encapsulatesmethod and field definitions, which can then be reused by mixing them intoclasses. Unlike class inheritance, in which each class must inherit from justone supercla

Programming in Scala (Second Edition) 读书笔记15 case class and pattern matching

一个算术表达式包含: 数字,变量,二元操作符,一元操作符.用下面几个类来模拟它们 package chapter15 abstract class Expr case class Var(name: String) extends Expr case class Number(num: Double) extends Expr case class UnOp(operator: String, arg: Expr) extends Expr case class BinOp(operator: 

Programming in Scala (Second Edition) 读书笔记21 隐式转化

1. There's a fundamental difference between your own code and libraries of other people: you can change or extend your own code as you wish, but if you want to use someone else's libraries, you usually have to take them as they are. 你可以改变或扩展自己的代码,但是对

Programming in Scala (Second Edition) 读书笔记7 内置控制结构

1. One thing you will notice is that almost all of Scala's control structures result in some value Scala的每种控制语句都是有值的 This is the approach taken by functional languages, in which programs are viewed as computing a value, thus the components of a progr

Programming in Scala (Second Edition) 读书笔记6 函数式对象

1.什么是函数式对象(functional object) ? The emphasis in this chapter is on classes that define functional objects, that is, objects that do not have any mutable state. 状态不可变的对象 2. 对函数式对象的取舍(immutable object trade-offs) Immutable objects offer several advanta

Programming in Scala (Second Edition) 读书笔记13 packages and import

目前,在一个包中你看到的top level的对象只有:class, trait, object.其实任何对象都可以是top level的.也就是说,没有必要把函数,value, variable等限制在class, trait, object中.它们可以在整个包范围内都是全局性的. 方法很简单,把这些东东放到package object中就行了.pakcage object的名字和包名相同 包含package object的文件放在该packag下,名字就叫package.scala chapt

Programming in Scala (Second Edition) 读书笔记6 函数和闭包

When programs get larger, you need some way to divide them into smaller, more manageable pieces. For dividing up control flow, Scala offers an approach familiar to all experienced programmers: divide the code into functions. In fact, Scala offers sev

Programming in Scala (Second Edition) 读书笔记26 Extractors

1. By now you have probably grown accustomed to the concise way data can be decomposed and analyzed using pattern matching. This chapter shows you how to generalize this concept further. Until now, constructor patterns were linked to case classes. Fo

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

创建并初始化一个String Array     val strArr1 = new Array[String](3)     strArr1(0) = "How"     strArr1(1) = "are"     strArr1(2) = "you!" 上述代码被transform 为     val strArr1 = new Array[String](3)     strArr1.update(0, "How")