10장 composition and inheritance - codeport/scala GitHub Wiki

  • 구성(composition)은 클래스가 작업을 실행하는 다른 클래스에 대한 참조를 가지고 있는 것을 의미한다.
  • 상속(inheritance)은 슈퍼클래스와 하위클래스의 관계를 의미한다.

10.1 A two-dimensional layout library

  • 2차원 레이아웃 요소를 만드는 라이브러리(text layout framework) 예제
  • 편의를 위해서 팩토리 메서드 생성
elem(s: String): Element

다음과 같이 사용

val column1 = elem("hello") above elem("***")
val column2 = elem("***") above elem("hello")
column1 beside column2
hello  ***
 ***  world
  • combinator: elements를 엮어 새로운 element 를 생성하는 opertaor

10.2 Abstract classes

  • 추상클래스에는 abstract키워드를 붙임
abstract class Element {
  def contents: Array[String]
}
  • java와 달리 abstract method 앞에 abstract keyword가 필요없음. body가 없다면 abstract method 임.
  • 구현부를 가진 메서드는 concrete method
scala> class A { def abc: Int }
<console>:7: error: class A needs to be abstract, since method abc is not defined
  class A { def abc: Int }
        ^
scala> abstract class A { def abc: Int }
defined class A   

definition vs. declaration : signature를 명시했다면 선언(declaration), 내용이 정의되었다면 정의(definition). abstract method는 선언만 있다고 정의는 없는 경우.

10.3 Defining parameterless methods

  • parameterless method: ()도 명시하지 않은 method.
    • 매개변수를 갖지 않으며, 객체의 상태를 변경하지 않는 메서드의 경우에 권장하는 convention.
    • uniform access principle을 지원: 속성을 필드로 구현하건 메서드로 구현하건 클라이언트 코드는 영향을 받지 않아야 함. -> 즉, 마치 field처럼 보이는 method라는 것인가? java였다면 method라면 () 를 붙이고, 아니라면 그냥 썼어야.
  • empty-paren method: ()를 명시한 method

parameterless method 메서드를 호출할 때 뒤에 ()를 붙이니 오류남. 그래서 uniform access principle을 지원한다는 것인가 봄.

class A  { def height: Int = "123".length
           def height2(): Int = "123".length
           val height3: Int = "123".length 
 }
scala> val b =new A
b: A = A@1532075
scala> b.height2
res39: Int = 3
scala> b.height2()
res40: Int = 3
scala> b.height3
res41: Int = 3
scala> b.height3()
<console>:10: error: Int does not take parameters
              b.height3()
                       ^
scala> b.height
res44: Int = 3
scala> b.height()
<console>:10: error: Int does not take parameters
              b.height()
                      ^

parameterless method와 field는 사용하는 방법은 동일하지만, 실행속도 측면에선 차이가 있음. 당연히 method는 매 호출마다 evaluation이 되어야 하니. 대신 field는 메모리를 차지함.

parameterless method로 정의되어 있지 않은 메서드라도 argument가 없다면 () 를 생략할 수 있으므로 uniform access principle을 준수할 수 있음. ex) new String("aaa").length

반대로, 인자가 없는 method라 하더라도 side-effect를 동반한다면 가급적 () 를 붙이길 권장(convention 수준).

"abc".length // no side-effect. drop ()
println() // has side-effect. specify ()

###10.4 Extending classes

클래스 확장 시 extends. java와 동일. scala의 AnyRef는 java의 Object에 해당.

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

Figure 10.1 · Class diagram for ArrayElement.

상속

  • 상위클래스의 모든 멤버를 상속하지만
  • private 은 상속되지 않으며
  • 같은 이름, 매개변수를 가진 멤버는 상속되지 않음 (overriding & implementation)
  • subtyping: superclass 위치엔 subclass의 값이 들어갈 수 있음

10.5 Overriding methods and fields

java와 달리 scala의 field와 method는 동일한 namespace에 위치함. 따라서 parameterless method를 field로 overriding할 수 있음(헉!) 또한 method와 field가 같은 이름을 가질 수 없음.

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

namespace

  • java: field, method, type, package
  • scala: values(field, method, package, singleton object) , type (class, trait)
    • singleton object의 이름과 class 이름은 서로 다른 namespace를 가지므로 서로 관련이 없나보군!

10.6 Defining parametric fields

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

parametric field: combine parameter and field. 생성자에 val/var 명시하는 내용. private, protected, override를 parameteric field에 붙일 수 있음.

class Cat {
  val dangerous = false
}
class Tiger(
  override val dangerous: Boolean,
  private var age: Int
) extends Cat
class Tiger(param1: Boolean, param2: Int) extends Cat {
  override val dangerous = param1
  private var age = param2
}

10.7 Invoking superclass constructors

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

superclass의 생성자 호출 시 extends class명 뒤에 param을 넘기면 됨

10.2

10.8 Using override modifiers

부모 class의 concrete member를 override할 땐 override를 반드시 명기해야 함. 단, abstract method를 구현할 경우엔 optional.

scala> abstract class A1 { def a:Int ; def b:Int = 4 }
defined class A1

scala> class A2 extends A1 { def a:Int = 3; def b:Int = 5 }
<console>:8: error: overriding method b in class A1 of type => Int;
 method b needs `override' modifier
       class A2 extends A1 { def a:Int = 3; def b:Int = 5 }
                                                ^

scala> class A2 extends A1 { def a:Int = 3; override def b:Int = 5 }
defined class A2

override를 강제함으로써 fragile base class 문제를 일으키는 accidental overrides 현상을 방지할 수 있음.

10.9 Polymorphism and dynamic binding

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)
}

// polymorphism...
val e1: Element = new ArrayElement(Array("hello", "world"))
val ae: ArrayElement = new LineElement("hello")
val e2: Element = ae
val e3: Element = new UniformElement('x', 2, 3)

polymorphism에서 메서드 호출과 표현식은 동적 바인딩 됨. 즉, runtime에 어떤 클래스의 기능이 호출될 지 결정됨.

abstract class Element {
  def demo() {
    println("Element's implementation invoked")
  }
}

class ArrayElement extends Element {
  override def demo() {
    println("ArrayElement's implementation invoked")
  }
}
class LineElement extends ArrayElement {
  override def demo() {
     println("LineElement's implementation invoked")
  }
}
// UniformElement inherits Element’s demo
class UniformElement extends Element

// Dynamic binding
def invokeDemo(e: Element) {
  e.demo()
}

scala> invokeDemo(new ArrayElement)
ArrayElement's implementation invoked
scala> invokeDemo(new LineElement)
LineElement's implementation invoked
scala> invokeDemo(new UniformElement)
Element's implementation invoked

10.10 Declaring final members

java와 마찬가지로 override를 막기 위해선 member에 final을 명시함. class, method에 명시할 수 있음.

class ArrayElement extends Element {
  final override def demo() {
    println("ArrayElement's implementation invoked")
  }
}

10.11 Using composition and inheritance

fragile base class 문제를 겪지 않으려면 inheritance보다는 composition을 사용하자.

10.12 Implementing above, beside, and toString

abstract class Element {
    def contents: Array[String]
    def width: Int =
      if (height == 0) 0 else contents(0).length
    def height: Int = contents.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 = contents mkString "\n"
  }

scala의 array는 scala.Seq 의 인스턴스로 변환됨. 이 클래스는 sequence-like 자료구조를 표현함.

for 문 안에서 tuple을 다룰 때

for( (line1, line2) <- Array(1,2,3) zip Array("a","b") ) yield line1 + line2

10.13 Defining a factory object

factory method를 만드는 일반적인 방법: companion object를 이용.

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

10.14, 15, 16

skip

⚠️ **GitHub.com Fallback** ⚠️