391
8
장
스칼라 객체지향 프로그래밍
8.8
부모 클래스 생성자 호출하기(그리고 좋은 객체지향 설계)
파생 클래스의 주 생성자는 반드시 부모 클래스의 생성자 중 하나를 호출해야 한다. 이때 주 생성
자나 보조 생성자 어느 것이든 관계없다. 다음 예제에서
Employee
는
Person
의 서브클래스다.
//
src
/
main
/
scala
/
progscala2
/
basicoop
/
EmployeeSubclass
.
sc
import progscala2
.
basicoop
.
Address
case class Person
(
//
예전에는
Person2
였으나
이름을
바꿨다
.
name
:
String
,
age
:
Option
[
Int
]
=
None
,
address
:
Option
[
Address
]
=
None
)
class Employee
(
//
➊
name
:
String
,
age
:
Option
[
Int
]
=
None
,
address
:
Option
[
Address
]
=
None
,
val title
:
String
=
"[
unknown
]",
//
➋
val manager
:
Option
[
Employee
]
=
None
)
extends Person
(
name
,
age
,
address
)
{
override def toString
=
//
➌
s
"
Employee
($
name
,
$
age
,
$
address
,
$
title
,
$
manager
)"
}
val a1
=
new Address
("
1 Scala Lane
",
"
Anytown
",
"
CA
",
"
98765
")
val a2
=
new Address
("
98765
")
val ceo
=
new Employee
("
Joe CEO
",
title
=
"
CEO
")
//
결과
:
Employee
(
Joe CEO
,
None
,
None
,
CEO
,
None
)
new Employee
("
Buck Trends1
")
//
결과
:
Employee
(
Buck Trends1
,
None
,
None
,
[
unknown
],
None
)
new Employee
("
Buck Trends2
",
Some
(
20
),
Some
(
a1
))
//
결과
:
Employee
(
Buck Trends2
,
Some
(
20
),
//
Some
(
Address
(
1 Scala Lane
,
Anytown
,
CA
,
98765
)),
[
unknown
],
None
)
new Employee
("
Buck Trends3
",
Some
(
20
),
Some
(
a1
),
"
Zombie Dev
")
//
결과
:
Employee
(
Buck Trends3
,
Some
(
20
),
//
Some
(
Address
(
1 Scala Lane
,
Anytown
,
CA
,
98765
)),
Zombie Dev
,
None
)
new Employee
("
Buck Trends4
",
Some
(
20
),
Some
(
a1
),
"
Zombie Dev
",
Some
(
ceo
))
392
2
부
기본기 다지기
//
결과
:
Employee
(
Buck Trends4
,
Some
(
20
),
//
Some
(
Address
(
1 Scala Lane
,
Anytown
,
CA
,
98765
)),
Zombie Dev
,
//
Some
(
Employee
(
Joe CEO
,
None
,
None
,
CEO
,
None
)))
➊
Employee
는
case
클래스가 아니라 일반적인 클래스다. 왜 그렇게 했는지에 대해서는 다음
절에 설명할 것이다.
➋ 새로운 필드
title
과
manager
다.
Employee
가
case
클래스가 아니기 때문에
val
키워드를
넣어야 한다. 다른 인자들은
Person
에서 왔으며, 이미 필드다. 여기서
Person
의 주 생성자를 호
출했다는 점에 유의하라.
➌
toString
을 오버라이딩한다. 오버라이딩하지 않으면
Person
.
toString
이 사용된다.
자바에서는 생성자 메서드를 정의하고
super
를 그 안에서 호출해서 부모 클래스의 생성 과정을
호출했을 것이다. 스칼라에서는 ‘
자식_클래스
(... )
extends
부모_클래스
(... )
’를 사용해서 부모
클래스의 생성자를 암시적으로 호출한다.
NOTE
_
스칼라에서는 자바와 마찬가지로 오버라이딩한 메서드 안에서
super
를 호출할 수 있지만,
super
를 사용해서 부모 클래스의 생성자를 호출할 수는 없다.
8.8.1
여담: 좋은 객체지향 설계
이 코드는 냄새가 난다.
Employee
선언의 클래스 인자 목록에
val
키워드가 붙은 인자와 아무
것도 붙지 않은 인자가 섞여 있다. 하지만 더 어려운 문제가 소스 코드에 숨어 있다.
비
-
케이스 클래스를 케이스 클래스로부터 파생시키거나, 반대 방향으로 파생시킬 수 있다. 하
지만 케이스 클래스를 다른 케이스 클래스로부터 파생시킬 수는 없다. 이는 자동으로 생성한
toString
,
equals
,
hashCode
메서드가 서브클래스에 대해서는 제대로 작동하지 않기 때문이
다. 그 이유는 인스턴스가 케이스 클래스 타입의 파생 타입일 가능성을 이런 메서드들이 무시
하기 때문이다.
이는 실제로는 설계에 의한 것이다. 이는 클래스를 파생시키는 것의 문제점을 보여준다. 예를 들
어 어떤
Employee
인스턴스와 다른
Person
인스턴스의 이름, 나이, 주소가 같다면 그 둘을 동등
393
8
장
스칼라 객체지향 프로그래밍
한 것으로 간주해야 할까? 객체의 동등성에 대해 더 유연하게 해석한다면 그렇다고 대답해야 할
것이다. 하지만 좀 더 엄격한 해석에서는 그렇지 않다고 대답할 것이다. 실제로 수학적인 정의에
서 동등성은 교환 법칙이 성립해야 한다. 즉,
somePerson
==
someEmployee
는
someEmployee
==
somePerson
과 같은 결과가 나와야 한다. 더 유연한 해석은 결합성을 깰 것이다. 어떤
Employee
의 인스턴스가 자신을
Employee
가 아닌
Person
인스턴스와 같다고 생각하리라고는
결코 예상할 수 없기 때문이다.
실제로 여기서는 동등성의 문제가 더 나쁘다. 왜냐하면
Employee
가
equals
와
hashCode
를 오
버라이딩하지 않기 때문이다. 이는 결과적으로 모든
Employee
인스턴스를
Person
인스턴스로
간주하는 것이다.
그렇게 두는 것은 이렇게 작은 타입이라 할지라도 위험하다. 누군가가 종업원의 컬렉션을 만들
면서 종업원들을 정렬하거나, 종업원을 해시 맵의 키로 사용하는 경우가 불가피하게 생길 것이
기 때문이다. 그런 경우 각각
Person
.
equals
와
Person
.
hashCode
가 사용되기 때문에,
CEO
와
문서 수발실의 직원 이름이
John
Smith
로 같은 경우 문제가 생길 것이다. 그 두 사람을 혼동
하는 일이 너무 자주 일어나서 문제가 심각해지거나, 거꾸로 가끔 혼동이 일어나긴 하는데 충분
히 재현되지 않아서 버그를 수정하기가 너무 어려울 수도 있다.
실제 문제는 우리가 상태를 상속한다는 것이다. 즉, 이 경우 상속을 사용하면서
title
과
manager
같은 상태를 추가하고 있다. 반대로, 상태 필드는 동일하게 유지하면서 동작만 파생시키면 튼튼
하게 구현하기 더 쉽다. 그렇게 하면, 예를 들어 방금 설명한
equals
와
hashCode
의 문제를 피할
수 있다.
물론 상속과 관련 있는 이런 문제는 오랫동안 알려져 있었던 것이다. 오늘날 좋은 객체지향 설계
는 상속보다는 합성
composition
을 선호한다. 그래서 클래스 객체를 만들기보다는 기능의 단위를 서
로 합성하는 쪽을 택한다.
다음 장에서 보겠지만, 트레이트를 사용하면 자바
8
이전의 자바 인터페이스를 사용하는 것보
다 합성을 훨씬 쉽게 할 수 있다. 따라서 이 책에 있는 ‘장난감’이 아닌 예제들은 상태를 추가하는
상속을 사용하지 않을 것이다. 다행히 상용 수준의 스칼라 라이브러리에서는 그런 식의 상속 계
층구조가 드물다.
어쩌면 스칼라 팀이 서브클래스에 잘 적용될 수 있는
equals
,
hashCode
,
toString
을 구현하기
로 결정을 내려야 했을 수도 있다. 하지만 그런 선택은 나쁜 설계를 지원하기 위해 복잡성을 추
Get 프로그래밍 스칼라: 실용적인 스칼라 활용법을 익히는 가장 확실한 실전 바이블 (2.11.x 버전 기반) now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.