Menu
Coddy logo textTech

덕 타이핑

Coddy Lua 여정의 Object Oriented Programming 섹션에 포함된 레슨 — 70개 중 39번째.

Lua는 객체의 클래스에는 신경 쓰지 않습니다. 오직 객체가 무엇을 할 수 있는지에만 신경을 씁니다. 이 개념을 덕 타이핑(duck typing)이라고 합니다: "만약 그것이 오리처럼 걷고 오리처럼 꽥꽥거린다면, 그것은 오리다." 프로그래밍 용어로 말하자면, 만약 객체가 당신이 호출하려는 메서드를 가지고 있다면, 그것은 작동합니다.

이것은 서로 전혀 관련이 없는 클래스들이라도 동일한 메서드 이름을 공유하기만 하면 함께 사용될 수 있음을 의미합니다:

local Circle = {}
Circle.__index = Circle
function Circle:new() 
    local obj = {}
    setmetatable(obj, Circle)
    return obj
end
function Circle:draw()
    print("Drawing a circle")
end

local Square = {}
Square.__index = Square
function Square:new()
    local obj = {}
    setmetatable(obj, Square)
    return obj
end
function Square:draw()
    print("Drawing a square")
end

이 클래스들은 상속 관계가 없으며 완전히 독립적입니다. 하지만 우리는 이들을 동일하게 다룰 수 있습니다:

local shapes = {Circle:new(), Square:new(), Circle:new()}

for _, shape in ipairs(shapes) do
    shape:draw()
end
-- 출력:
-- 원 그리기
-- 사각형 그리기
-- 원 그리기

루프는 각 객체가 어떤 타입인지 확인하지 않습니다. 단순히 :draw()를 호출하고 해당 메서드가 존재한다고 신뢰합니다. 이러한 유연성은 강력합니다. 루프 코드를 전혀 수정하지 않고도 새로운 도형 타입을 추가할 수 있습니다. 새로운 클래스가 :draw()를 구현하기만 하면, 그대로 작동합니다.

challenge icon

챌린지

쉬움

완전히 서로 관련이 없는 클래스들이 모두 동일한 방식으로 처리될 수 있는 알림 시스템을 구축하여 덕 타이핑(duck typing)의 강력함을 실습해 봅시다!

여러분은 서로 상속 관계가 없는 세 개의 독립적인 알림 클래스를 만들게 됩니다. 하지만 이 클래스들은 모두 :send() 메서드를 구현합니다. 즉, 실제 타입에 상관없이 혼합된 컬렉션을 순회하며 각 객체의 :send()를 호출할 수 있습니다.

코드는 다음 네 개의 파일로 구성됩니다:

  • Email.lua: 이메일 수신자를 저장하는 :new(recipient) 생성자를 가진 클래스입니다. 이 클래스의 :send() 메서드는 Emailing {recipient}를 출력해야 합니다.
  • SMS.lua: :new(phoneNumber) 생성자를 가진 완전히 별개의 클래스입니다. 이 클래스의 :send() 메서드는 Texting {phoneNumber}를 출력해야 합니다.
  • PushNotification.lua: :new(deviceId) 생성자를 가진 또 다른 독립적인 클래스입니다. 이 클래스의 :send() 메서드는 Pushing to {deviceId}를 출력해야 합니다.
  • main.lua: 모든 것을 하나로 합칩니다! 세 개의 입력을 읽고, 각 알림 타입의 인스턴스를 하나씩 생성하여 모두 하나의 테이블에 넣은 다음, 반복문을 통해 각 인스턴스의 :send()를 호출하세요.

세 개의 입력을 받게 됩니다:

  1. 이메일 수신자 (예: alice@example.com)
  2. 전화번호 (예: 555-1234)
  3. 기기 ID (예: device_99)

메인 파일에서 세 개의 알림 객체를 모두 생성하고, 테이블에 저장한 뒤, 테이블을 순회하며 각 객체의 :send()를 호출하세요. 순서는 Email이 먼저, 그다음 SMS, 마지막으로 PushNotification이어야 합니다.

예를 들어, 입력값이 bob@mail.com, 555-9876, phone_42라면 출력은 다음과 같아야 합니다:

Emailing bob@mail.com
Texting 555-9876
Pushing to phone_42

반복문에서 각 객체의 타입이 무엇인지 확인할 필요가 없다는 점에 주목하세요. 컬렉션의 모든 객체에 :send() 메서드가 있다고 믿고 호출할 뿐입니다. 이것이 바로 덕 타이핑의 실제 사례입니다! :send()를 할 수 있다면, 그것은 알림입니다.

치트 시트

Lua는 덕 타이핑(duck typing)을 사용합니다. 객체가 호출하려는 메서드를 가지고 있다면, 클래스에 관계없이 작동합니다. 원칙은 "만약 그것이 오리처럼 걷고 오리처럼 갹갹거린다면, 그것은 오리다"라는 것입니다.

서로 완전히 무관한 클래스라도 동일한 메서드 이름을 공유한다면 서로 바꿔서 사용할 수 있습니다:

local Circle = {}
Circle.__index = Circle
function Circle:new() 
    local obj = {}
    setmetatable(obj, Circle)
    return obj
end
function Circle:draw()
    print("Drawing a circle")
end

local Square = {}
Square.__index = Square
function Square:new()
    local obj = {}
    setmetatable(obj, Square)
    return obj
end
function Square:draw()
    print("Drawing a square")
end

이 클래스들은 상속 관계가 없지만, 동일하게 취급될 수 있습니다:

local shapes = {Circle:new(), Square:new(), Circle:new()}

for _, shape in ipairs(shapes) do
    shape:draw()
end
-- 출력:
-- Drawing a circle
-- Drawing a square
-- Drawing a circle

루프는 객체 타입을 확인하지 않습니다. 단순히 메서드를 호출하고 그것이 존재한다고 믿습니다. 필요한 메서드를 구현하기만 한다면 기존 코드를 수정하지 않고도 새로운 타입을 추가할 수 있습니다.

직접 해보기

-- main.lua
-- 모든 알림 유형을 하나로 모으고 덕 타이핑(duck typing)을 시연합니다

-- 알림 클래스 가져오기
local Email = require('Email')
local SMS = require('SMS')
local PushNotification = require('PushNotification')

-- 입력 읽기
local recipient = io.read()
local phoneNumber = io.read()
local deviceId = io.read()

-- TODO: 각 알림 유형의 인스턴스를 하나씩 생성합니다

-- TODO: 모든 알림을 단일 테이블에 넣습니다 (Email, SMS, PushNotification 순서)

-- TODO: 테이블을 반복하며 각 알림에 대해 :send()를 호출합니다
-- 이것은 덕 타이핑을 보여줍니다 - 유형을 확인하지 않고 그냥 :send()를 호출합니다
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

Object Oriented Programming의 모든 레슨