Menu
Coddy logo textTech

공통 인터페이스

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

덕 타이핑(Duck typing)은 서로 관련 없는 클래스들이 메서드 이름을 공유할 경우 함께 작동할 수 있음을 보여주었습니다. 이 아이디어는 강력한 설계 개념인 공통 인터페이스(common interface)로 이어집니다. 여러 클래스가 동일한 메서드 시그니처를 갖도록 의도적으로 설계하면, 서로 교체 가능한 컴포넌트를 만들 수 있습니다.

로깅 시스템을 구축한다고 상상해 보세요. 메시지를 파일, 화면 또는 네트워크와 같은 다양한 대상으로 보내고 싶을 수 있습니다. 각각에 대해 별도의 코드를 작성하는 대신, 모든 출력 클래스가 동일한 :write() 메서드를 구현하도록 설계합니다:

local LogFile = {}
LogFile.__index = LogFile

function LogFile:new(filename)
    local obj = {filename = filename}
    setmetatable(obj, LogFile)
    return obj
end

function LogFile:write(message)
    print("[FILE:" .. self.filename .. "] " .. message)
end

local Screen = {}
Screen.__index = Screen

function Screen:new()
    local obj = {}
    setmetatable(obj, Screen)
    return obj
end

function Screen:write(message)
    print("[SCREEN] " .. message)
end

이제 메시지를 출력해야 하는 모든 코드는 두 가지 유형 중 어느 것이든 수용할 수 있습니다:

local function logMessage(output, msg)
    output:write(msg)
end

local file = LogFile:new("app.log")
local screen = Screen:new()

logMessage(file, "Starting up")    -- [FILE:app.log] 시작 중
logMessage(screen, "Starting up")  -- [SCREEN] 시작 중

logMessage 함수는 어떤 클래스를 받는지 알 필요도 없고 신경 쓰지도 않습니다. 이 함수는 오직 :write() 메서드만 기대합니다. 이를 통해 코드가 유연해집니다. 기존 함수를 수정하지 않고도 나중에 새로운 출력 유형을 추가할 수 있습니다.

challenge icon

챌린지

쉬움

서로 다른 결제 수단을 상호 교환하여 사용할 수 있는 결제 처리 시스템을 구축해 봅시다! 공통 인터페이스인 :process(amount) 메서드를 공유하는 완전히 별개의 두 결제 클래스를 만들게 됩니다. 이를 통해 결제를 처리하는 모든 코드는 차이점을 알 필요 없이 두 유형 중 어느 것과도 작동할 수 있습니다.

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

  • CreditCard.lua: 카드 번호를 저장하는 :new(cardNumber) 생성자가 있는 결제 클래스입니다. 이 클래스의 :process(amount) 메서드는 Charging {amount} to card {cardNumber}를 출력해야 합니다.
  • BankTransfer.lua: :new(accountId) 생성자가 있는 완전히 별개의 결제 클래스입니다. 이 클래스의 :process(amount) 메서드는 Transferring {amount} from account {accountId}를 출력해야 합니다.
  • main.lua: 임의의 결제 객체와 금액을 인자로 받아 해당 객체의 :process(amount)를 호출하는 handlePayment(paymentMethod, amount) 함수를 만듭니다. 이 함수는 신용카드인지 은행 송금인지 알 필요가 없으며, 단지 객체에 :process() 메서드가 있을 것만 기대합니다.

세 가지 입력을 받게 됩니다:

  1. 신용카드 번호 (예: 4532-1234-5678)
  2. 은행 계좌 ID (예: ACC-9876)
  3. 결제 금액 (예: 150)

메인 파일에서 처음 두 입력을 사용하여 각 결제 유형의 인스턴스를 하나씩 생성하세요. 그런 다음 handlePayment 함수를 사용하여 주어진 금액을 두 결제 수단(먼저 신용카드, 그다음 은행 송금)을 통해 처리하세요.

예를 들어, 입력이 1111-2222-3333, ACC-5555, 75인 경우 출력은 다음과 같아야 합니다:

Charging 75 to card 1111-2222-3333
Transferring 75 from account ACC-5555

이 디자인의 장점은 handlePayment 함수가 :process()를 구현하는 모든 객체와 함께 작동한다는 것입니다. 내일 PayPal 클래스를 추가하더라도 :process(amount) 메서드만 있다면 기존 함수를 수정하지 않고도 그대로 사용할 수 있습니다!

치트 시트

공통 인터페이스는 서로 관련 없는 여러 클래스가 의도적으로 동일한 메서드 시그니처를 구현하여 서로 교체 가능하게 만드는 디자인 패턴입니다.

클래스가 메서드 이름을 공유하도록 설계함으로써, 해당 인터페이스를 구현하는 모든 객체와 함께 작동하는 함수를 작성할 수 있습니다.

local LogFile = {}
LogFile.__index = LogFile

function LogFile:new(filename)
    local obj = {filename = filename}
    setmetatable(obj, LogFile)
    return obj
end

function LogFile:write(message)
    print("[FILE:" .. self.filename .. "] " .. message)
end

local Screen = {}
Screen.__index = Screen

function Screen:new()
    local obj = {}
    setmetatable(obj, Screen)
    return obj
end

function Screen:write(message)
    print("[SCREEN] " .. message)
end

함수는 기대되는 메서드를 구현하는 모든 객체를 인자로 받을 수 있습니다.

local function logMessage(output, msg)
    output:write(msg)
end

local file = LogFile:new("app.log")
local screen = Screen:new()

logMessage(file, "Starting up")    -- [FILE:app.log] Starting up
logMessage(screen, "Starting up")  -- [SCREEN] Starting up

logMessage 함수는 어떤 클래스를 전달받는지 알 필요가 없으며, 단지 :write() 메서드가 있기만을 기대합니다. 이는 코드를 유연하게 만들고 기존 함수를 수정하지 않고도 새로운 타입을 추가할 수 있게 해줍니다.

직접 해보기

-- 결제 클래스 가져오기
local CreditCard = require('CreditCard')
local BankTransfer = require('BankTransfer')

-- 입력 읽기
local cardNumber = io.read()
local accountId = io.read()
local amount = tonumber(io.read())

-- TODO: 모든 결제 객체와 금액을 인자로 받는 handlePayment 함수를 생성하고,
-- 해당 객체에서 :process(amount)를 호출하도록 하세요


-- TODO: 입력을 사용하여 CreditCard 및 BankTransfer의 인스턴스를 생성하세요


-- TODO: handlePayment를 사용하여 두 결제 수단 모두를 통해 금액을 처리하세요
-- 먼저 신용카드, 그 다음 은행 송금 순으로 처리합니다
quiz icon실력 점검

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

Object Oriented Programming의 모든 레슨