객체지향이나 클래스의 개념이 생소하다면 일단은 ‘특정한 임무에 관련된 변수들과 그 변수들을 핸들링하는 관련 함수의 집합’ 정도로 이해해도 될 것 같다. 예를 들어서 ‘좌표점과 그것에 관련된 계산’이라는 임무에 대해서
(1) x좌표
(2) y좌표
(3) 한 좌표의 원점으로부터의 거리를 구하는 함수
(4) 두 점의 거리를 구하는 함수
정도를 구현한다고 하자. 보통 (1),(2)번을 멤버변수라고 하고 (3)(4)번은 멤버함수라고 한다. 이것들을 전체를 하나의 이름으로 묶은 것을 클래스라고 한다.
코로나에서 이것을 외부 모듈로 구현한다면 먼저 다음과 같은 형태를 생각해 볼 수 있다. (외부모듈에 대한 기본적인 것은 이전 포스트를 참조)
┌─────────────────────────────
local Sqrt = math.sqrt
local M={}
function M.New(x,y)
local pt = {x=x or 0, y = y or 0} -- 먼저 멤버변수를 테이블로 새로 생성
function pt:GetLength() -- 첫 번째 멤버함수를 pt안에서 생성
return Sqrt(self.x*self.x + self.y*self.y)
end
function pt:DistTo(pt2) -- 두 번째 멤버함수를 pt 안에서 생성
local dx = self.x - pt2.x
local dy = self.y - pt2.y
return Sqrt(dx*dx + dy*dy)
end
return pt -- 생성된 테이블(인스턴스)를 반환한다.
end
return M
└─────────────────────────────
이 예제에서는 M.New() 함수 안에서 새로운 테이블을 생성한 후 이 안에서 변수와 함수를 다 정의하여 반환하는 식으로 처리했다. 이것을 예를 들어서 ‘point.lua’라고 저장했다면 다른 파일(예를 들어서 main.lua)에서 다음과 같이 불러서 쓸 수 있다.
┌── "main.lua" ───────────────────────────
local CPoint = require "point" -- 외부모듈을 읽어들인다.
local pt1 = CPoint.New(10,20) -- 첫 번째 인스턴스 생성
local pt2 = CPoint.New(30,40) -- 두 번째 인스턴스 생성
print("length of pt1:".. pt1:GetLength() ) -- 길이 22.36이 찍힘
print("length of pt2:".. pt2:GetLength() ) -- 길이 50이 찍힘
print("distance:".. pt1:DistTo(pt2) ) -- 두 점의 거리 28.28이 찍힌다
└─────────────────────────────
이제 CPoint.New()함수를 호출해서 새로운 점좌표를 얼마든지 생성할 수 있으며 보통 이렇게 생성되는 객체를 인스턴스(instance)라고 부른다. 그리고 이렇게 생성된 인스턴스를 통해서 관련 함수를 호출할 수 있다. (print 함수 안의 명령들)
그런데 이 point1.lua 의 단점은 인스턴스를 생성할 때 마다 그 인스턴스 안에 함수의 본체도 같이 구현된다는 것이다. 예를 들어 100개를 생성하면 함수 본체도 각각 100개가 존재한다. 멤버함수의 개수나 덩치가 커진다면 이것은 실행이나 메모리 관점에서 굉장히 비효율적이다. 그래서 다음과 같이 멤버함수는 외부로 빼는 방식을 생각해 볼 수 있다.
┌─────────────────────────────
local Sqrt = math.sqrt
local function GetLength(tbl) -- 함수 본체를 외부에 정의
return Sqrt(tbl.x*tbl.x + tbl.y*tbl.y)
end
local function DistTo(pt1, pt2) -- 함수 본체를 외부에 정의
local dx = pt1.x - pt2.x
local dy = pt1.y - pt2.y
return Sqrt(dx*dx + dy*dy)
end
local M={}
function M.New(x,y)
local pt = {x=x or 0, y = y or 0}
function pt:GetLength() -- 본체로 리다이렉션시킨다
return GetLength(self)
end
function pt:DistTo(pt2) -- 본체로 리다이렉션시킨다
return DistTo(self, pt2)
end
return pt
end
return M
└─────────────────────────────
이제 함수 본체는 (인스턴스 개수와 상관없이) 외부에 하나만 존재하며 인스턴스 안에는 단지 본체로 리다이렉션 시켜주는 조그만 함수가 있을 뿐이다. 앞의 경우보다는 훨씬 효율적이지만 여전히 (작은 크기지만) 함수가 인스턴스 내부에 존재하고 본체로 재호출한다는 점에서 비효율적이다.
좀 더 루아스럽고 우아하게(...) 개선하려면 이전 포스트에서 설명한 메타테이블의 __index 를 사용하면 된다.
┌─────────────────────────────
local Sqrt = math.sqrt
local mtIndex = {}
function mtIndex:GetLength()
return Sqrt(self.x*self.x + self.y*self.y)
end
function mtIndex:DistTo(pt2)
local dx = self.x - pt2.x
local dy = self.y - pt2.y
return Sqrt(dx*dx + dy*dy)
end
local M={}
function M.New(x,y)
local pt = {x=x or 0, y=y or 0} -- 멤버변수를 생성
return setmetatable(pt, {__index = mtIndex}) -- 멤버 함수를 메타테이블로 첨부한 후 반환
end
return M
└─────────────────────────────
이 방법이 코딩의 간결성이나 실행의 효율성에서 앞에서 소개한 방법들 보다 좀 더 앞선다고 할 수 있다.
댓글 없음:
댓글 쓰기