추상 타입
추상 타입을 이용하면 선언 시점에서 어떤 타입인지 알려지지 않은 타입을 참조할 수 있다.
추상타입을 사용하지 않는 아래 예제를 보면
class Food
abstract class Animal {
def eat(food: Food)
}
class Grass extends Food
class Cow extends Animal {
// override def eat(food: Grass) {} // 파라미터 타입이 다르기 때문에 오버라이드 할 수 없다.
override def eat(food: Food) {}
}
- 동물(
Animal
)은 음식(Food
)을 먹는다. - 소(
Cow
)는 풀(Grass
)을 먹는다. (소는 동물을 상속받고, 풀은 음식을 상속받는다)
하지만, 파라미터가 다르기 때문에 Animal
을 상속 받는 Cow
는 def eat(food: Grass)
로 오버라이드(Override
) 할 수 없다. (단지, 메소드 명이 같고 파라미터가 다른 오버로드딩(Overloading
) 관계일 뿐)
추상 타입을 이용하면 위 코드를 모델링 할 수 있다.
class Food
abstract class Animal {
type SuitableFood <: Food // <: Food는 Food 를 상속받는 서브 클래스들을 의미함
def eat(food: SuitableFood) // Food 를 상속받는 서브 클래스들을 파라미터로 가짐
}
class Grass extends Food
class Cow extends Animal {
override type SuitableFood = Grass // Food 의 서브클래스라는 것을 알려줌
override def eat(food: Grass) {}
}
type
키워드와<: Food
를 이용해서,Food
를 상속받는 서브클래스 타입이라는 것을 명시해준다.Grass
는Food
의 서브클래스이다.Cow
에서type
을 이용해서Grass
가Food
의 서브클래스라는 것을 명시한다.eat
메소드를 오버라이드(override
) 할 수 있다.
만약, Food
를 상속받는 Grass
와 Fish
가 있을 때, Cow
에게 Fish
를 먹이면 컴파일 오류가 발생한다. 반대로 Grass
를 먹일때는 오류가 발생하지 않는다. (의도한대로 동작을 한다)
val cow = new Cow()
cow.eat(new Grass)
class Fish extends Food
cow.eat(new Fish) // 컴파일 에러
Grass 와 Fish 모두 Food를 상속 받는 서브클래스이지만, Grass와 Fish는 아무런 관계가 없기 때문에 Cow 클래스의 eat 메소드에 파라미터 타입인 Grass가 아닌, Fish 타입을 넣었기 때문에 오류가 발생한다.
경로 의존 타입
곰(bear
) 클래스를 생성해보자.
class Grass extends Food
class Fish extends Food
class Cow extends Animal {
override type SuitableFood = Grass
override def eat(food: Grass) {}
}
// Bear 클래스 생성
class Bear extends Animal {
override type SuitableFood = Fish
override def eat(food: Fish) {}
}
Cow
와 Bear
는 동일한 SuitableFood
타입을 갖는다.
그렇다면, Bear
에게 Cow
의 SuitableFood
를 먹이면 정상 동작을 할까? 답은 No
val cow = new Cow
cow.eat(new Grass)
val bear = new Bear
bear.eat(new cow.SuitableFood) // 컴파일 오류 발생
bear.eat(new bear.SuitableFood)
타입의 이름이 SuitableFood로 같지만, 경로 의존 타입(path-dependent type)이 다르다. (쉽게 말해서, 타입이 다르다는 뜻)
cow.SuitableFood
!= bear.SuitableFood
- 소의 SuitableFood 타입은
cow.SuitableFood
이다. - 곰의 SuitableFood 타입은
bear.SuitableFood
이다.
자바와의 차이점
class Outer {
class Inner
}
자바에서는 위 내부 클래스(Inner)를 표현 할 때, Outer.Inner
라고 표현한다.
스칼라에서는 내부 클래스를 표현할 때, Outer#Inner
로 표현하다.
자바와 마찬가지로 스칼라에서도 내부 클래스 인스턴스에는 그 인스턴스를 둘러싸고 있는 외부 인스턴스(Outer
)를 가리키는 참조값을 가지고 있다.
따라서, 외부 인스턴스 참조값이 있어야 내부 클래스에 접근 할 수 있다. (반대로 말하면, 외부 인스턴스를 지정하지 않으면, 내부 클래스에 접근 할 수 없다)
val o1 = new Outer
val o1_i = new o1.Inner
val o1_i2 = new Outer#Inner // 오류. 외부 인스턴스 없이 Inner 클래스에 접근할 수 없다.
세분화한 타입
풀을 먹는 동물들이 포함된 목초지(Pasture) 클래스를 만들어 보자.
class AnimalThatEatsGrass extends Animal {
override type SuitableFood = Grass
override def eat(food: Grass) = {}
}
class Pasture { // 목초지
var animals: List[AnimalThatEatsGrass] = Nil
}
위에서 Cow
라는 풀을 먹는 동물을 만들었다.
하지만, 소 뿐만 아니라 다른 동물을 포함하기 위해, 풀을 먹는 동물(AnimalThatEatsGrass
)을 만들어서 또 다시 선언하는 것은 장황하다.
세분화한 타입을 사용하면, 위 문제를 해결할 수 있다.
class Pasture2 {
var animals: List[Animal{type SuitableFood = Grass}] = Nil
}
따로 클래스를 만들 필요없이 중괄호로 멤버 목록을 덧붙이면 된다.
'Language > Scala' 카테고리의 다른 글
Scala의 열거형(Enumeration type) (0) | 2020.02.23 |
---|---|
Scala 추상 val 초기화 (0) | 2020.02.23 |
Scala 추상 멤버 (타입멤버, 메소드, val, var) (0) | 2020.02.22 |