- 추상 멤버 : 클래스나 트레이트의 멤버가 그 클래스 안에 완전한 정의를 갖지 않는 것, 상속한 서브 클래스에서 구현해야 한다.
- val, var, 메소드, 타입 추상 멤버 존재
20.1 추상 멤버 간략하게 돌아보기
trait Abstract {
class Concrete extends Abstract { |
20.2 타입 멤버
: 스칼라의 추상 타입은 클래스나 트레이트의 멤버로 정의 없이 선언된 타입(클래스나 트레이트 자체는 추상 타입 X)
: 구체적(비 추상) 타입 멤버는 어떤 타입에 대한 새로운 이름 또는 별명
: 타입 멤버 => 실제 이름이 너무 길거나 의미가 불명확할 때 더 간단하고 의도를 잘 전달할 수 있다. => 코드가 명확
: 서브 클래스에서 타입이 정해지는 경우 사용
20.3 추상 val
: val에 대해 이름과 타입은 주지만 값 지정 X
: 클래스 안에서 어떤 변수에 대해 정확한 값을 알 수 없지만, 그 변수가 클래스의 인스턴스에서 변하지 않으리란 사실을 알고 있을 때 추상 val 사용
: 추상 val 선언은 추상 메소드 선언과 비슷해 보인다.
val initial: String => 클라이언트 코드는 obj.initial로 val와 메소드를 같은 방식으로 사용 가능 but, val이라면 같은 값을 얻을 수 있다는 확신이 있지만 메소드라면 그런 보장이 없다. => 추상 val은 구현시 val 정의를 사용해야한다는 제약 => 추상 메소드 선언은 val이나 메소드 정의로 사용 가능하다.
abstract class Fruit { |
20.4 추상 var
: 추상 var도 암시적으로 게터 메소드와 세터 메소드를 정의하는 것과 같다.
trait AbstractTime { var hour: Int var minute: Int } |
trait AbstractTime { def hour: Int // getter for `hour' def hour_=(x: Int) // setter for `hour' def minute: Int // getter for `minute' def minute_=(x: Int) // setter for `minute' } |
20.5 추상 val 초기화
: 추상 val은 슈퍼 클래스에 빠진 자세한 부분을 서브 클래스에 전달할 수 있는 수단 제공
: 트레이트는 파라미터를 받을 생성자가 없기 때문에 트레이트에서 파라미터를 받으려면 서브 클래스에서 구현하는 추상 val을 이용한다.
trait RationalTrait {
new RationalTrait { // 트레이트를 혼합한 익명 클래스 인스턴스 => new Rational(1, 2)와 상응하는 효과 |
: new RationalTrait { ... } 와 new Rational(...) 은 비슷해 보이나 초기화 순서라는 중요한 차이 존재
new Rational(expr1, expr2)
|
: 클래스 Rational은 초기화하기 전에 expr1과 expr2 계산 => Rational 클래스의 초기화에서 사용가능
: 익명클래스를 초기화하는 도중에 표현식 expr1, expr2를 계산 => RationalTrait 다음에 초기화된다. => numerArg와 denomArg 값 사용 불가(디폴트 값이 들어 있다.)
trait RationalTrait {
scala> val x = 2 |
: RationalTrait 클래스를 초기화할 때 denomArg의 값이 디폴트 값인 0이기 때문에 require 호출이 실패한다.
=> 클래스 파라미터 인자는 클래스 생성자에 전달되기 전에 계산되고 서브클래스의 val 정의를 계산하는 것은 슈퍼 클래스를 초기화한 다음에만 이뤄진다.
=> 필드를 미리 초기화하거나 지연 val을 사용한다.
< 필드를 미리 초기화하기 >
: 슈퍼 클래스를 호출하기 전에 서브클래스의 필드를 초기화한다.
: 필드 정의를 중괄호에 넣어서 슈퍼 클래스 생성자 호출 앞에 위치시키면 된다.
" 익명 클래스 표현식에서 필드를 미리 초기화 " scala> new {
" 객체 정의에서 필드를 미리 초기화 " object twoThirds extends {
" 클래스 정의에서 필드를 미리 초기화 " class RationalClass(n: Int, d: Int) extends { |
: 미리 초기화한 필드는 슈퍼 클래스 생성자를 호출하기 전에 초기화되기 때문에 초기화 시 생성 중인 객체 언급 X
scala> new { | val numerArg = 1 | val denomArg = this.numerArg * 2 | } with RationalTrait :9: error: value numerArg is not a member of object $iw val denomArg = this.numerArg * 2 |
< 지연 계산 val 변수 >
: 어떤 val 정의 앞에 lazy 수식자가 있으면 프로그램에서 그 val 값을 처음 사용할 때 초기화한다. => 시스템이 스스로 어떻게 초기화할 지 결정한다.
scala> object Demo { ------------------------------------------------
scala> object Demo { |
: Demo를 초기화하는 과정에는 x를 초기화하는 과정이 들어가지 않는다. x의 초기화를 x가 맨 처음 쓰일 때까지 연기한다.
" 트레이트를 지연 val로 초기화하기 " trait LazyRationalTrait {
scala> val x = 2 |
: 모든 구체적 필드는 지연 필드
<초기화 순서>
1. LazyRationalTrait의 새 인스턴스를 만들고 초기화 코드를 실행한다. 초기화 코드에는 아무 것도 안 들어있다.
2. 익명 서브클래스의 주 생성자를 실행한다. numerArg와 denomArg가 초기화된다.
3. 인터프리터는 출력을 위해 toString을 호출한다.
4. toString에서 numer을 최초로 접근해 numer의 초기화 표현식을 계산한다.
5. 계산 과정에서 g에 접근에 numerArg와 denomArg에 접근한다.
6. toString에서 denom을 최초로 접근한다. 이 때 g를 다시 계산하지 않는다.
: g가 numer, denom 보다 위치상 뒤에 있으나 초기화는 먼저 진행 => 각 정의의 코드 순서는 중요하지 않다.
=> 지연 val을 사용할 경우 프로그래머가 모든 val을 필요한 곳에서 참조할 수 있게 배치하려면 어떻게 해야 할지 고민 하지 않아도 된다.
: 단, 지연 val은 부수효과가 없는 함수형 객체와 어울린다. 부수효과가 있는 경우 초기화 순서가 문제가 되기 시작한다.
20.6 추상 타입
- 추상 타입 선언 : 서브 클래스에서 구체적으로 정해야 하는 어떤 대상에 대한 빈 공간을 마련해두는 것으로 선언 시점에서는 어떤 타입인지 알려져 있지 않은 타입을 참조하기 위해 사용
Ex. 동물의 음식 섭취
class Food
class Grass extends Food |
: 타입 시스템이 불필요하게 엄격한 것으로 보일 수 있다. 어떤 메소드의 파라미터를 서브 클래스에서 특화하도록 허용한다면 아래 같은 상황이 나타난다.
class Food
|
=> 더 명확하게 타입을 설정
class Food
class Grass extends Food
scala> class Fish extends Food |
20.7 경로에 의존하는 타입
: bessy.SuitableFood 같은 타입을 경로에 의존하는 타입이라고 부른다.(경로는 객체에 대한 참조)
: 경로가 다르면 타입도 달라진다.
class DogFood extends Food class Dog extends Animal { type SuitableFood = DogFood override def eat(food: DogFood) {} } scala> val bessy = new Cow bessy: Cow = Cow@e7bbeb scala> val lassie = new Dog lassie: Dog = Dog@ce38f1 scala> lassie eat (new bessy.SuitableFood) :14: error: type mismatch; found : Grass required: DogFood lassie eat (new bessy.SuitableFood) ^ scala> val bootsie = new Dog bootsie: Dog = Dog@66db21 scala> lassie eat (new bootsie.SuitableFood) |
: 경로에 의존하는 타입은 자바의 내부 클래스 타입과 문법이 비슷하지만 경로 의존 타입은 외부 객체에 이름을 붙이는 반면, 내부 클래스 타입은 외부 클래스에 이름을 붙인다는 점이 다르다.
class Outer {
val o1 = new Outer => o1.Inner와 o2.Inner은 경로 의존 타입이고 더 일반적인 타입 Outer#Inner의 서브 타입이다. Outer#Inner은 Outer 타입의 임의의 외부 객체 내부에 있는 Inner 클래스를 의미 o1.Inner와 o2.Inner은 특정 외부 객체의 Inner 클래스를 의미
=> 내부 클래스 인스턴스는 그 인스턴스를 둘러싼 외부 클래스의 인스턴스를 가리키는 참조 존재 => 내부 클래스 인스턴스에서 외부 클래스의 인스턴스를 지정해야 한다.
scala> new o1.Inner
scala> new Outer#Inner |
20.8 세분화한 타입
: 어떤 클래스 A가 다른 클래스 B를 상속받을 때 A가 B의 이름에 의한 서브타입이라고 말한다. 각 타입에 이름이 있고 서브 타입 관계를 선언하면서 각 클래스의 이름을 명시하기 때문
: 스칼라는 세분화한 타입을 통해 구조적인 서브 타이핑(타입 간의 관계가 타입 내부 구조에 의해서 결정)을 지원
: 이름에 의한 서브타입이 편하고 먼저 사용해야 한다.(간결하기 때문에)
Ex. 풀을 먹는 동물을 포함할 수 있는 Pasture 클래스
1. AnimalThatEatsGrass 라는 트레이트를 만들어 목초지에 들어갈 모든 클래스에 포함시킨다. 하지만 장황하다. Cow 클래스는 이미 자신이 동물이고 풀을 먹는다고 선언했다.(SuitableFood 타입을 통해)
2. AnimalThatEatsGrass을 선언하는 대신 세분화한 타입을 사용한다.
class Pasture { var animals: List[Animal { type SuitableFood = Grass }] = Nil // ... } |
20.9 열거형
: 스칼라는 열거형을 위한 특별한 문법을 제공하지 않고 scala.Enumeration이라는 표준 라이브러리로 제공한다.
object Color extends Enumeration {
object Color extends Enumeration { |
: 스칼라의 열거형은 경로 의존 타입의 예로서 Color.Red, Color.Green, Color.Blue의 타입은 Color.Value 이다.
object Direction extends Enumeration {
scala> for (d <- Direction.values) print(d +" ") |
20.10 사례 연구
< Currency 클래스 설계 > : 달러, 엔, 유로 등 통화 단위로 일정 금액의 돈을 표현할 수 있다. 같은 단위의 통화를 표현하는 두 통화 인스턴스를 더할 수 있어야 한다. 어떤 통화에 이자율을 나타내는 값을 곱할 수도 있어야 한다.
abstract class Currency {
abstract class Dollar extends Currency { => Dollar 객체의 + 메소드에서 Euro 객체가 파라미터로 들어올 수 있다. => 추상 타입 사용 |
abstract class AbstractCurrency { // 스칼라는 추상 타입의 인스턴스를 만들 수 없고, 추상 타입을 다른 클래스의 슈퍼 타입으로 만들 수 없다. abstract class Dollar extends AbstractCurrency { |
=> 추상 타입의 인스턴스를 직접 만드는 대신 추상 타입을 만들어내는 추상 메소드를 만들 수 있다.
abstract class AbstractCurrency { => Currency가 Currency를 만들 수 있다. => Currency를 만들 방법이 make 밖에 없는데 Currency 안에 make 존재 => 최초의 Currency 객체를 만들어낼 방법이 없다. => 다른 생성자를 추가할 수 있지만 본질적으로 make와 하는 일이 같다.
=> 추상 타입과 팩토리 메소드를 밖으로 옮긴다.
abstract class CurrencyZone {
object US extends CurrencyZone { |
" 통화의 하위 단위 표현, 미국 달러의 하위 단위는 센트, amount 필드가 금액을 달러 대신 센트 단위로 저장 " }
---------------------------------------------- override def toString = // 10달러와 23센트의 합은 10.23 USD
private def decimals(n: Long): Int =
------------------------------------------------
object Europe extends CurrencyZone { |
" 환율 계산 " object Converter {
def from(other: CurrencyZone#AbstractCurrency): Currency = |
'스칼라' 카테고리의 다른 글
스칼라 22장 리스트 구현(Programming in Scala, 3rd) (0) | 2019.06.23 |
---|---|
스칼라 21장 암시적 변환과 암시적 파라미터(Programming in Scala, 3rd) (0) | 2019.06.23 |
스칼라 19장 타입 파라미터화, 변성(Programming in Scala, 3rd) (0) | 2019.06.07 |
스칼라 18장 변경 가능한 객체(Programming in Scala, 3rd) (0) | 2019.06.07 |
스칼라 17장 컬렉션(Programming in Scala, 3rd) (0) | 2019.06.06 |