프라이버시를 위한 클로저
Coddy Lua 여정의 Object Oriented Programming 섹션에 포함된 레슨 — 70개 중 44번째.
명명 규칙은 의도를 나타내지만, 실제로 접근을 막지는 않습니다. 누구나 여전히 player._health = 999라고 작성할 수 있으며 이는 정상적으로 작동합니다. 진정한 프라이버시를 위해, Lua는 self 대신 생성자 내부의 지역 변수에 데이터를 저장하는 다른 접근 방식을 제공합니다.
핵심적인 통찰은 함수 내부의 지역 변수는 해당 함수와 그 안에 정의된 모든 함수 내에서만 접근 가능하다는 것입니다. 이것을 클로저(closure)라고 합니다:
local Counter = {}
Counter.__index = Counter
function Counter:new()
local count = 0 -- 진정으로 프라이빗합니다! self에 존재하지 않습니다.
local obj = {}
setmetatable(obj, Counter)
return obj
end이 예제에서 count는 :new() 함수 내부에만 존재합니다. obj에 저장되지 않으므로 외부에서 접근할 방법이 없습니다 — myCounter.count는 nil을 반환하며, 우회할 수 있는 밑줄(underscore) 접두사가 붙은 필드도 없습니다.
count 변수는 클로저의 스코프 내에 존재합니다. 이 변수는 객체가 존재하는 동안 유지되지만, 외부 코드로부터는 완전히 숨겨집니다. 단지 정중한 제안에 불과한 self._count와 달리, 이 방식은 데이터를 진정으로 접근 불가능하게 만듭니다.
물론, 숨겨진 데이터와 전혀 상호작용할 수 없다면 그것은 유용하지 않습니다. 다음 레슨에서는 이러한 프라이빗 변수(private variables)를 읽고 수정할 수 있는 메서드를 생성자(constructor) 내부에 정의하는 방법을 배우게 됩니다. 이를 통해 진정한 캡슐화(encapsulation)를 유지하면서 제어된 접근 권한을 가질 수 있습니다.
챌린지
쉬움클로저를 사용하여 진정한 데이터 프라이버시를 보여주는 SecretKeeper 클래스를 만들어 봅시다! 이전 레슨의 언더스코어 관례와 달리, 이 방식은 객체 외부에서 데이터에 절대 접근할 수 없게 만듭니다.
코드는 두 개의 파일로 구성됩니다:
SecretKeeper.lua: 생성자 내부의 로컬 변수에 비밀을 저장하고self에는 저장하지 않는 클래스를 만듭니다. 생성자:new(secretValue)는 클로저 내에서만 존재하는 로컬 변수에 비밀을 저장해야 합니다. 현재로서는 객체가 이 비밀에 접근할 방법이 없지만(다음 레슨에서 다룹니다!), 객체가 작동하는지 확인하기 위해 인스턴스에"Keeper"로 설정된 공개name필드를 저장해야 합니다.main.lua: SecretKeeper 모듈을 불러오고(require) 입력에서 비밀 값을 읽습니다. 해당 비밀로 SecretKeeper 인스턴스를 생성합니다. 그런 다음 세 가지를 출력하여 비밀이 진정으로 프라이빗함을 증명하세요:- keeper의
name필드 (Keeper가 출력되어야 함) keeper.secret에 접근하려고 할 때의 결과 (nil이어야 함)keeper._secret에 접근하려고 할 때의 결과 (이 또한nil이어야 함)
- keeper의
하나의 입력을 받게 됩니다:
- 저장할 비밀 값 (예:
TopSecret123)
출력은 공개 필드는 작동하지만 비밀은 완전히 숨겨져 있음을 보여주는 세 줄이어야 합니다:
Name: Keeper
secret: nil
_secret: nil어떤 비밀을 전달하더라도 출력은 동일합니다. 접근할 방법이 없기 때문입니다! 생성자 내부의 로컬 변수는 외부 세계에서 보이지 않습니다. 이는 클로저 기반 프라이버시가 언더스코어 명명 관례와 근본적으로 다르다는 것을 증명합니다.
예를 들어, 입력이 MyPassword인 경우에도 출력은 다음과 같아야 합니다:
Name: Keeper
secret: nil
_secret: nil치트 시트
Lua에서 진정한 프라이빗 데이터를 생성하려면 self 대신 생성자 내부의 지역 변수에 저장하세요. 이는 클로저(closure)를 사용합니다. 지역 변수는 해당 변수가 정의된 함수와 그 내부에서 정의된 함수 내에서만 접근할 수 있습니다.
프라이빗 데이터가 있는 클래스의 예:
local Counter = {}
Counter.__index = Counter
function Counter:new()
local count = 0 -- 진정한 프라이빗! self에 있지 않음.
local obj = {}
setmetatable(obj, Counter)
return obj
end이 예제에서 count는 :new() 함수 내부에만 존재합니다. obj에 저장되지 않으므로 myCounter.count는 nil을 반환합니다. 외부에서 접근할 방법이 없습니다. (단순한 명명 규칙인) self._count와 달리, 이 방식은 데이터를 실제로 접근 불가능하게 만듭니다.
이 변수는 객체가 존재하는 동안 유지되지만 외부 코드로부터는 완전히 숨겨져 있어, 진정한 캡슐화를 제공합니다.
직접 해보기
-- SecretKeeper 모듈을 불러옵니다
local SecretKeeper = require('SecretKeeper')
-- 입력으로부터 비밀 값을 읽습니다
local secretValue = io.read()
-- TODO: 비밀 값을 사용하여 SecretKeeper 인스턴스를 생성합니다
-- TODO: keeper의 name 필드를 출력합니다 (형식: "Name: <name>")
-- TODO: keeper.secret에 접근할 때 어떤 일이 발생하는지 출력합니다 (형식: "secret: <value>")
-- TODO: keeper._secret에 접근할 때 어떤 일이 발생하는지 출력합니다 (형식: "_secret: <value>")
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.