덕 타이핑(Duck Typing)에 대해 학습하며 작성된 글입니다. 학습 과정에서 작성된 글이기 때문에 잘못된 내용이 있을 수 있으며, 이에 대한 지적이나 피드백은 언제든 환영입니다.
1. Duck Typing
객체가 무엇을 할 수 있는지, 없는지를 기준으로 타입을 구분하는 것으로 객체의 변수, 메소드의 집합이 객체의 타입을 결정하는 것을 말합니다. 쉽게 말해 내가 정의한 동작을 할 수 있다면 그 타입으로 인정해주는 것 입니다.
Duck typing in computer programming is an application of the duck test—”If it walks like a duck and it quacks like a duck, then it must be a duck”—to determine whether an object can be used for a particular purpose. With nominative typing, an object is of a given type if it is declared as such (or if a type’s association with the object is inferred through mechanisms such as object inheritance). With duck typing, an object is of a given type if it has all methods and properties required by that type. Duck typing may be viewed as a usage-based structural equivalence between a given object and the requirements of a type. Wiki
개인적으로 이를 잘 나타내는 그림이라고 생각되는데요, 돼지 코가 전기 콘센트 같이 생겼다면, 그렇게 동작 한다면 이를 콘센트로 여기는 것입니다. 예시를 통해 살펴보겠습니다.
아래와 같이 Duck 클래스와 Person 클래스는 서로 상속 관계가 없지만, growl 함수는 object 인자로 넘어온 객체가 quack 메서드를 가지고 있으면 해당 객체를 오리로 간주하여 그것에 맞게 동작합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Duck:
def quack(self):
print("으르렁(꽥꽥)!")
class Person:
def quack(self):
print("사람이 으르렁(꽥꽥)! 소리를 냅니다.")
def growl(object):
object.quack()
duck = Duck()
person = Person()
growl(duck) # "으르렁(꽥꽥)!"
growl(person) # "사람이 으르렁(꽥꽥)! 소리를 냅니다."
하지만 이는 언어마다 다른데요, 자바는 정적 타입 언어로 컴파일 시점에 타입 체크를 수행합니다. 따라서 자바에서는 언어 차원에서의 덕 타이핑을 지원하지 않습니다. 자바에서는 객체의 타입을 명시적으로 선언하거나, 인터페이스와 상속을 통해 유사한 기능을 구현해야 합니다.
1
2
3
interface Animal {
void growl();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Dock implements Animal {
@Override
public void growl() {
System.out.println("으르렁(꽥꽥)!");
}
}
public class Person implements Animal {
@Override
public void growl() {
System.out.println("사람이 으르렁(꽥꽥)! 소리를 냅니다.");
}
}
2. 특징
덕 타이핑은 대표적으로 낮은 안전성과 빠른 개발 속도라는 두 가지 특징을 가지고 있습니다.
2-1. 낮은 안전성
개발하다 보면 흔히 가장 좋은 오류는 컴파일 시점의 오류다, 에러는 컴파일 시점에 발견되는 것이 가장 안전하다. 와 같은 말을 듣는데요, 덕 타이핑은 컴파일 시점에 예외를 알 수 없으므로 안전성이 (비교적) 낮습니다. 동적으로 타입을 결정하기 때문입니다. 따라서 아래와 같이 새로운 클래스가 등장하면 예상치 못한 결과를 얻을 수 있습니다.
1
2
3
4
5
6
7
8
# 기존 클래스
class Duck:
def get_size(self):
return "Small"
class Person:
def get_size(self):
return "Big"
1
2
3
4
5
6
7
# 새로 추가된 클래스
class FakeDuck:
def get_size(self):
return "Is Not a duck."
fake_duck = FakeDuck()
print_size(fake_duck) # "Is Not a duck." 출력
2-2. 유연성
하지만 이를 통해 유연성을 얻을 수 있습니다. 유연하기 때문에 빠르게 개발을 할 수 있으며, 이를 통해 생산성을 높일 수 있습니다. 매번 오버로딩이나 새로운 메서드/함수를 만들 필요가 없기 때문입니다. 자바는 덕 타이핑을 지원하지 않으며, 따라서 아래와 같이 인자의 타입이 다른 경우 매번 메서드를 재정의해 줘야 합니다. 이 경우 새로운 기능을 추가하기 위해 매번 메서드가 추가되며, 유지보수 대상이 늘어납니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 자바는 매 번 인자가 다를 때마다 오버로딩을 해줘야 합니다.
public class User {
......
public void speak(String message) {
// 구현
}
public void speak(int count) {
// 구현
}
......
}
3. 구조적 / 명목적 타입 타입 시스템
타입 시스템에는 구조 기반 타입(structural typing) 시스템과 명목 기반 타입 시스템(nominative type system)이 존재합니다.
- 구조 기반 타입 시스템
- 명목적 타입 시스템
3-1. 구조적 타입 시스템
타입스크립트에는 구조 기반 타입(structural typing)이라는 개념이 존재합니다. 이는 객체의 타입이 객체의 구조(속성/메서드)에 의해 결정되는 타입 시스템으로, 객체가 어떤 이름으로 정의되어 있는지와 상관없이 객체의 구조가 같으면 같은 타입으로 간주합니다. 따라서 아래와 같이 printName의 인자가 Person이어도 Child를 넣어도 정상적으로 출력됩니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface Person {
name: string;
}
interface Man {
name: string;
}
const printName = (person: Person) => {
console.log(person.name);
}
const child: Man = {name: "JY"}
printName(child) // JY 출력
즉 덕 타이핑은 객체가 특정 인터페이스를 구현하고 있는지에 대한 검사 없이 해당 메서드나 속성의 존재로 타입이 결정되며, 구조 기반 타입은 같은 구조(속성/메서드)라면 같은 객체로 인식하는 것입니다.
3-2. 명목적 타입 시스템
반명 명목적 타입 시스템도 존재하는데요, 즉, 같은 이름의 메서드를 가지고 있더라도 그 이름이 정확하게 같은 타입으로 선언되지 않았다면 같은 타입으로 인식하지 않는 것입니다. 따라서 명목적 타입 시스템은 컴파일 시점에 에러를 잡을 수 있으므로 비교적 안전성을 보장합니다. 런타임에서 발생할 수 있는 오류를 컴파일 타임에서 미리 잡아낼 수 있기 때문이죠.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
interface Person {
speak: () => void;
eat: () => void;
}
interface Child {
speak: () => void;
}
const sticking = (person: Person) => {
person.speak();
}
const jun: Person = {
speak: () => console.log("Person Speak"),
eat: () => console.log("Person Eat"),
};
const child: Child = {
speak: () => console.log("Child Speak"),
};
sticking(jun);
sticking(child); // 에러 발생
4. 정리
덕 타이핑은 객체가 무엇을 할 수 있는지, 없는지를 기준으로 타입을 구분하는 것으로 객체의 변수, 메소드의 집합이 객체의 타입을 결정하는 것을 말합니다. 이는 동적 언어를 대상으로 구현되며, 자바와 같은 정적 언어는 이를 언어적 차원에서 지원하지 않습니다. 여기에는 구조적/명목적 타입 시스템이 존재하며, 각각의 특성에 대해 별도로 학습해보실 것을 권장합니다.