Notice
Recent Posts
Recent Comments
Link
관리 메뉴

nomad-programmer

[C#] 덕 타이핑 (Duck Typing) 본문

카테고리 없음

[C#] 덕 타이핑 (Duck Typing)

scii 2020. 9. 26. 23:23

덕 타이핑 (Duck Typing)

오리처럼 걷고 오리처럼 헤엄치며 오리처럼 꽥꽥 거리는 새를 봤을 때, 나는 그 새를 오리라고 부른다.

이 문장은 미국의 시인인 제임스 위트콤 라일리의 시에서 인용된 것으로, 덕 타이핑을 가장 잘 설명하는 문장이기도 하다. 덕 타이핑은 객체 제향 프로그래밍과는 상당히 다른 각도에서 형식을 바라본다. 객체 지향 프로그래밍에서는 C#에서 어떤 형식이 오리라고 인정받으려면 그 형식의 조상 중에 오리가 있어야 한다.

class Duck
{
    public void Walk()
    { Console.WriteLine("Duck.Walk"); }
    
    public void Swim()
    { Console.WriteLine("Duck.Swim"); }
    
    public void Quack()
    { Console.WriteLine("Duck.Quack"); }
}

// Malled(청둥오리)는 Duck으로부터 상속을 받으므로 Duck이라고 인정할 수 있다.
class Malled : Duck
{
    // ...
}

반면에 오리 타이핑에서 어떤 형식이 오리로 인정을 받으려면 오리처럼 걷고, 오리처럼 헤엄치고, 오리처럼 꽥꽥 거리기만 하면 된다. 그 형식이 어느 형식으로부터 상속받는지는 전혀 중요하지 않다. 

다음은 오리 타이핑에서 오리로 인정하는 클래스들의 선언 코드이다.

class Duck
{
    public void Walk()
    { Console.WriteLine("Duck.Walk"); }
    
    public void Swim()
    { Console.WriteLine("Duck.Swim"); }
    
    public void Quack()
    { Console.WriteLine("Duck.Quack"); }
}

// Robot도 오리다.
class Robot
{
    public void Walk()
    { Console.WriteLine("Duck.Walk"); }
    
    public void Swim()
    { Console.WriteLine("Duck.Swim"); }
    
    public void Quack()
    { Console.WriteLine("Duck.Quack"); }
}

오리 타이핑 관점에서 보면 Duck도 오리이고, Robot도 오리이다. 둘 다 오리처럼 걷고, 헤엄치고, 꽥꽥 거리기 때문이다.

하지만 C# 컴파일러는 Duck이나 Mallard는 오리로 인정해도 Robot은 오리로 인정하지 않는다. 가량 다음과 같은 Duck 형식의 배열을 선언하고 여기에 Duck, Mallard, Robot의 인스턴스를 요소로 넣어 초기화하려면 컴파일러는 형식 검사를 하면서 Robot은 Duck 형식이 아니라고 에러 메시지를 내뱉을 것이다. 아무리 "오리처럼 걷고 헤엄치고 꽥꽥 거리면 오리다" 라고 생각해도 컴파일러는 오리라고 생각치 않는다. 

// Robot으로 인해 컴파일 에러 발생
Duck[] arr = new Duck[]{new Duck(), new Mallard(), new Robot()};

이런 경우는 dynamic 형식을 통해 해결할 수 있다. dynamic 형식이 형식 검사를 실행할 때 미룬다는 점을 이용하는 것이다.

dynamic[] arr = new dynamic[]{new Duck(), new Mallard(), new Robot()};

foreach(dynamic duck in arr)
{
    Console.WriteLine(duck.GetType());
    duck.Walk(); duck.Swim(); duck.Quack();
}

Duck, Mallard, Robot 클래스가 Walk(), Swim(), Quack() 메소드를 구현하고 있으므로 위 코드는 컴파일도 실행도 문제없이 잘 돌아간다.

dynamic duck typing 예제

using System;

namespace CSharpExample
{
    class Duck
    {
        public void Walk()
        {
            Console.WriteLine(this.GetType() + ".Walk");
        }

        public void Swim()
        {
            Console.WriteLine(this.GetType() + ".Swim");
        }

        public void Quack()
        {
            Console.WriteLine(this.GetType() + ".Quack");
        }
    }

    class Mallard : Duck { }

    class Robot
    {
        public void Walk()
        {
            Console.WriteLine("Robot.Walk");
        }

        public void Swim()
        {
            Console.WriteLine("Robot.Swim");
        }

        public void Quack()
        {
            Console.WriteLine("Robot.Quack");
        }
    }

    internal class MainApp
    {
        static int Main(string[] args)
        {
            dynamic[] arr = new dynamic[] { new Duck(), new Mallard(), new Robot() };

            foreach(dynamic duck in arr)
            {
                Console.WriteLine(duck.GetType());
                duck.Walk();
                duck.Swim();
                duck.Quack();
                Console.WriteLine();
            }

            return 0;
        }
    }
}


/* 결과

CSharpExample.Duck
CSharpExample.Duck.Walk
CSharpExample.Duck.Swim
CSharpExample.Duck.Quack

CSharpExample.Mallard
CSharpExample.Mallard.Walk
CSharpExample.Mallard.Swim
CSharpExample.Mallard.Quack

CSharpExample.Robot
Robot.Walk
Robot.Swim
Robot.Quack

*/

덕 타이핑을 사용하는 이유

인터페이스 상속을 이용하면 덕타이핑과 같은 비슷한 일을 할 수 있는데 덕타이핑을 하는 이유는 무엇일까?

인터페이스를 설계하기 위해서는 추상화를 잘 해야 하는데 추상화를 잘하려면 연습과 경험이 많이 필요하다. 인터페이스를 잘못 설계했다가는 나중에 파생 클래스를 수정해야 할 일이 생기면 위로는 인터페이스를 수정하고, 아래로는 자신의 파생 클래스들, 옆으로는 형제 클래스들을 줄줄이 수정해야 하는 일이 생긴다.
덕 타이핑은 이런 문제를 만났을 때 좀 더 유연하게 해결할 수 있도록 돕는다. 상속 관계를 이용하지 않기 때문에 프로그램 동작에 관여하는 부분만 손을 대면 되는 것이다. 

덕 타이핑이 좋은 점만 있는 것은 아니다. 우선 비주얼 스튜디오의 리팩토링 기능을 이요할 수 없다. 가령 Walk() 메소드의 이름을 Run()으로 고치고 싶어도 프로그래머가 직접 Walk() 메소드가 선언된 곳과 사용되고 있는 곳을 코드에서 찾아 수정해야 한다. 인터페이스를 이용했다면 비주얼 스튜디오를 이용해서 자동으로, 그것도 단번에 이 일을 할 수 있다.

인터페이스를 이용한 구조적 타이핑과 오리 타이핑 중 딱히 어느 쪽이 우수한 설계 기법이라고는 단정하기 어렵다. 어느 쪽이 우수한가 하는 문제는 대개 프로그래머의 취향에 따라 답이 다르게 나오기 때문이다.

Comments