-
Notifications
You must be signed in to change notification settings - Fork 36
10장 composition and inheritance
- 구성(composition)은 클래스가 작업을 실행하는 다른 클래스에 대한 참조를 가지고 있는 것을 의미한다.
- 상속(inheritance)은 슈퍼클래스와 하위클래스의 관계를 의미한다.
- 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
- 추상클래스에는
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는 선언만 있다고 정의는 없는 경우.
- 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
}
상속
- 상위클래스의 모든 멤버를 상속하지만
- private 은 상속되지 않으며
- 같은 이름, 매개변수를 가진 멤버는 상속되지 않음 (overriding & implementation)
- subtyping: superclass 위치엔 subclass의 값이 들어갈 수 있음
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를 가지므로 서로 관련이 없나보군!
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
}
class LineElement(s: String) extends ArrayElement(Array(s)) {
override def width = s.length
override def height = 1
}
superclass의 생성자 호출 시 extends class명 뒤에 param을 넘기면 됨
부모 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 현상을 방지할 수 있음.
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
java와 마찬가지로 override를 막기 위해선 member에 final을 명시함. class, method에 명시할 수 있음.
class ArrayElement extends Element {
final override def demo() {
println("ArrayElement's implementation invoked")
}
}
fragile base class 문제를 겪지 않으려면 inheritance보다는 composition을 사용하자.
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
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)
}
skip