Programming in Sala 책으로 스칼라 스터디하면서 정리했던 내용이다. 지금은 3판 번역본도 나왔지만, 약간 앞서서 스터디를 시작해서 2판으로 진행하고 있다.


10장

추상 클래스

  • 추상 클래스 : 구현이 없는 추상 멤버가 있는 클래스
  • abstract
  • 인스턴스를 만들 수 없음
  • 추상 메소드 : 추상 클래스 내의 구현이 없는 메소드
  • 구체 메소드 : 구현이 있는 메소드
abstract class Element {
    def contents: Array[String]
}

파라미터 없는 메소드 정의

abstract class Element {
    def contents: Array[String]
    def height: Int = contents.length
    def width: Int = if (height == 0) 0 else contents(0).length
}
  • 파라미터 없는 메소드와 빈 괄호 메소드를 자유롭게 섞어쓸 수 있음
  • 서로 오버라이드 가능
  • 메소드 호출시 빈 괄호 생략 가능
  • 함수 호출시 프로퍼티 접근 이상의 작업이 있따면 괄호를 사용하는 것이 좋음

클래스 확장

class ArrayElement(conts: Array[String]) extends Element {
    def contents: Array[String] = conts
}
  • extends 절이 없으면 scala.AnyRef를 상속
  • 상속 : 슈퍼클래스의 모든 멤버는 서브클래스의 멤버
    • 비공개 멤버 제외
    • 이름과 파라미터가 동일한 멤버 정의가 존재하면 제외 = 오버라이드(추상 멤버의 경우는 구현)
val ae = new ArrayElement(Array("hello","world"))
ae.width // 슈퍼클래스 멤버 사용
  • 서브타입 : 슈퍼클래스의 값을 필요로 하는 곳이라면 어디나 서브클래스의 값 사용 가능
val e: Element = new ArrayElement(Array("hello")) // Element 대신 사용
  • 구성(composition) : ArrayElement 이진 클래스 코드에 배열을 가리킬 참조 필드가 들어감

메소드와 필드 오버라이드

  • 필드와 메소드가 동일한 네임스페이스에 속함
  • 파라미터 없는 메소드를 필드(val)로 오버라이드 가능
class ArrayElement(conts: Array[String]) extends Element {
    val contents: Array[String] = conts
}
  • 동일한 이름의 필드와 메소드를 동시에 정의 불가
class WontCompile {
    private var f = 0 // 필드와 메소드가 같은 이름이므로
    def f = 1         // 컴파일할 수 없음
}
  • 스칼라의 네임스페이스
    • 값 : 필드, 메소드, 패키지, 싱글톤 객체
    • 타입 : 클래스, 트레이트 이름

파라미터 필드

class ArrayElement (
    val contents: Array[String] // 파라미터와 필드를 결합한 파라미터 필드(parametric field)
) extends Element
  • val, var 사용 가능
  • private, protected, override 수식자 사용 가능

슈퍼클래스의 생성자 호출

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

override 수식자

  • 부모 클래스에 있는 구체적 멤버를 오버라이드하는 모든 멤버에 override 수식자를 붙여야 함
  • 추상 멤버 구현시에는 생략 가능
  • 우연한 오버라이드 방지

다형성(서브타입 다형성)

Element 타입 변수가 ArrayElement, LineElement 등의 객체 참조 가능

val e1: Element = new ArrayElement(Array("hello","world"))
val ae: ArrayElement = new LineElement("hello")
val e2: Element = ae

동적 바인딩

메소드는 실행 시점에 실제 그 객체가 어떤 타입인가를 따름

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 Element {
    override def demo() {
        println("LineElement's implementation invoked")
    }
}

class UniformElement extends Element

def invokeDemo(e: Element) {
    e.demo()
}

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

final

  • 서브클래스가 특정 멤버를 오버라이드 못하게 함
class ArrayElement extends Element {
    final override def demo() {  // 메소드 오버라이드 불가
        println("ArrayElement's implementation invoked")
    }
}
final class ArrayElement extends Element {  // 클래스 상속 불가
    override def demo() {
        println("ArrayElement's implementation invoked")
    }
}

상속과 구성 사용

  • 구성과 상속은 이미 존재하는 클래스를 이용해 새로운 클래스를 정의하는 방법
  • 구성 : 코드 재사용
  • 상속
    • is-a 관계 : 'ArrayElement는 Element이다'
    • 슈퍼클래스의 타입으로 서비클래스를 사용하는 경우

예제 구현

  • 메소드 구현
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 // zip 연산은 순서쌍으로 이뤄진 하나의 배열로 변환
        ) yield line1 + line2
    )

override def toString = contents mkString "\n"
  • 팩토리 구현
object Element {
    private class ArrayElement(
        val contents: Array[String]
    ) extends Element

    private class LineElement(s: String) extends Element {
        val contents = 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(chr: Char, width: Int, height: Int): Element =
        new UniformElement(chr, width, height)
    def elem(line: String): Element =
        new LineElement(line)
}

import Element.elem
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 =
        elem(this.contents ++ that.contents)
    
    def beside(that: Element): Element =
        elem(
            for (
                (line1, line2) <- this.contents zip that.contents // zip 연산은 순서쌍으로 이뤄진 하나의 배열로 변환
            ) yield line1 + line2
        )

    override def toString = contents mkString "\n"
}
  • widen, heighten 메소드 추가
import Element.elem
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 = {
        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 heighten that.height
        val that1 = that heighten this.height
        elem(
            for (
                (line1, line2) <- this1.contents zip that1.contents // zip 연산은 순서쌍으로 이뤄진 하나의 배열로 변환
            ) yield line1 + line2
        )
    }
    
    def widen(w: Int): Element =
        if (w <= width) this
        else {
            val left = elem(' ', (w - width) / 2, height)
            val right = elem(' ', w - width - left.width, height)
            left beside this beside right
        }
    
    def heighten(h: Int): Element =
        if (h <= height) this
        else {
            val top = elem(' ', width, (h - height) / 2)
            val bot = elem(' ', width, h - height - top.height)
            top above this above bot
        }

    override def toString = contents mkString "\n"
}
  • Spiral 애플리케이션
import Element.elem
object Spiral {
    val space = elem(" ")
    val corner = elem("+")

    def spiral(nEdges: Int, direction: Int): Element = {
        if (nEdges == 1)
            elem("+")
        else {
            val sp = spiral(nEdges - 1, (direction + 3) % 4)
            def verticalBar = elem('|', 1, sp.height)
            def horizontalBar = elem('-', sp.width, 1)
            if (direction == 0)
                (corner beside horizontalBar) above (sp beside space)
            else if (direction == 1)
                (sp above space) beside (corner above verticalBar)
            else if (direction == 2)
                (space beside sp) above (horizontalBar beside corner)
            else
                (verticalBar above corner) beside (space above sp)
        }
    }

    def main(args: Array[String]) {
        val nSides = args(0).toInt
        println(spiral(nSides, 0))
    }
}


+ Recent posts