액세스 제어
액세스 제어 (Access Control)
프로그램을 만들다 보면 "이 타입이나 함수를 다른 코드에서도 쓸 수 있게 할까, 아니면 지금 이 모듈 안에서만 쓸 수 있게 할까"를 결정해야 하는 순간이 찾아와요. F#에서 **액세스 제어(access control)**란 타입, 메서드, 함수 같은 특정 프로그램 요소를 어떤 클라이언트가 사용할 수 있는지를 선언하는 메커니즘이에요. 이번 글에서는 F#의 액세스 지정자와 그 규칙을 하나씩 살펴볼게요.
출처: https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/access-control
본문
F#에서는 액세스 지정자(access control specifier)로 public, internal, private 세 가지를 모듈, 타입, 메서드, 값 정의, 함수, 프로퍼티, 명시적 필드에 적용할 수 있어요.
public은 해당 요소를 모든 호출자가 접근할 수 있음을 뜻해요.internal은 해당 요소를 같은 어셈블리(assembly) 안에서만 접근할 수 있음을 뜻해요.private은 해당 요소를 감싸고 있는 타입 또는 모듈 안에서만 접근할 수 있음을 뜻해요.
참고
protected지정자는 F#에서는 사용하지 않아요. 다만protected접근을 지원하는 언어로 작성된 타입을 쓸 때는 그 타입이 그대로 허용되죠. 그래서 protected 메서드를 오버라이드해도 그 메서드는 여전히 클래스와 그 파생 클래스 안에서만 접근할 수 있어요.
접근 지정자는 요소 이름 앞에 붙여요. 아무 지정자도 쓰지 않으면 기본값은 public인데, 타입 안의 let 바인딩만 예외예요. 이것들은 항상 해당 타입에 private이죠.
F#의 시그니처(signature)는 프로그램 요소에 대한 접근을 제어하는 또 다른 메커니즘을 제공해요. 물론 액세스 제어에 시그니처가 꼭 필요한 건 아니에요. 자세한 내용은 Signatures 문서를 참고하세요.
액세스 제어 규칙
액세스 제어에는 다음 규칙이 적용돼요.
- 상속 선언(즉 클래스의 기본 클래스를 지정하는
inherit사용), 인터페이스 선언(즉 클래스가 인터페이스를 구현한다는 지정), 그리고 추상 멤버는 항상 감싸는 타입과 같은 접근성을 가져요. 따라서 이런 구성 요소에는 액세스 지정자를 붙일 수 없어요. - 판별된 공용체(discriminated union)의 개별 케이스 접근성은 공용체 자체의 접근성에 의해 결정돼요. 즉 특정 공용체 케이스는 공용체 자체보다 덜 접근 가능할 수 없어요.
- 레코드 타입의 개별 필드 접근성은 레코드 자체의 접근성에 의해 결정돼요. 즉 특정 레코드 레이블은 레코드 자체보다 덜 접근 가능할 수 없어요.
예제
다음 코드는 액세스 지정자의 사용을 보여줘요. 프로젝트에는 Module1.fs와 Module2.fs 두 파일이 있고, 각 파일은 암시적으로 하나의 모듈이에요. 그래서 모듈은 Module1과 Module2 두 개가 생기죠. Module1에는 private 타입과 internal 타입이 정의돼 있어요. private 타입은 Module2에서 접근할 수 없지만, internal 타입은 접근할 수 있어요.
// Module1.fs
module Module1
// This type is not usable outside of this file
type private MyPrivateType() =
// x is private since this is an internal let binding
let x = 5
// X is private and does not appear in the QuickInfo window
// when viewing this type in the Visual Studio editor
member private this.X() = 10
member this.Z() = x * 100
type internal MyInternalType() =
let x = 5
member private this.X() = 10
member this.Z() = x * 100
// Top-level let bindings are public by default,
// so "private" and "internal" are needed here since a
// value cannot be more accessible than its type.
let private myPrivateObj = new MyPrivateType()
let internal myInternalObj = new MyInternalType()
// let bindings at the top level are public by default,
// so result1 and result2 are public.
let result1 = myPrivateObj.Z
let result2 = myInternalObj.Z
Module1.fs에서 만든 타입의 접근성을 확인하는 테스트 코드는 다음과 같아요.
// Module2.fs
module Module2
open Module1
// The following line is an error because private means
// that it cannot be accessed from another file or module
// let private myPrivateObj = new MyPrivateType()
let internal myInternalObj = new MyInternalType()
let result = myInternalObj.Z
첫 번째 파일에서 MyPrivateType은 private이라 Module2에서는 아예 만들 수 없어요. 그 줄을 주석 처리한 채로 둔 건 바로 그 "다른 파일이나 모듈에서는 접근할 수 없다"는 의미를 보여주기 위해서예요. 반면 MyInternalType은 같은 어셈블리 안에 있으므로 Module2에서도 internal로 접근해서 result에 그 Z 값을 담을 수 있어요.