잘 알려져 있다시피 클래스(class)는 객체지향 프로그램에서 핵심적인 역할을 하고 있고 루아에서는 이 기능을 베이스 수준에서 지원하지는 않지만 어느 정도 흉내는 낼 수 있다. 사실 객체지향에서 중요한 특성으로 캡슐화, 상속, 다형성 세 가지 정도가 언급이 되지만 소규모 프로젝트에서는 캡슐화 정도만 어느 정도 구현되어도 코딩과 수정 그리고 디버깅이 상당히 용이해진다고 개인적으로 생각한다. 글을 쓰고 있는 본인도 깊이 있는 지식은 없으므로 여기에서는 초보자들이 간단하게 쓸 수 있는 정도로만 설명하고자 한다.


  

  객체지향이나 클래스의 개념이 생소하다면 일단은 ‘특정한 임무에 관련된 변수들과 그 변수들을 핸들링하는 관련 함수의 집합’ 정도로 이해해도 될 것 같다. 예를 들어서 ‘좌표점과 그것에 관련된 계산’이라는 임무에 대해서


   (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

└─────────────────────────────


이 방법이 코딩의 간결성이나 실행의 효율성에서 앞에서 소개한 방법들 보다 좀 더 앞선다고 할 수 있다.


Posted by 살레시오
,