프라이버시

프라이버시 (Privacy)

모듈형 프로그래밍과 객체 지향 프로그래밍의 주요 원칙 중 하나는 캡슐화예요. 간략히 말해 캡슐화는 소프트웨어의 구현자가 코드의 공개 인터페이스와 비공개 구현을 구분하겠다는 개념이에요. 이는 소프트웨어 라이브러리에만 적용되는 것이 아니라 추상화가 사용되는 모든 곳에 적용돼요.

Ada에서 캡슐화의 세분성은 대부분의 객체 지향 언어와 조금 달라요. 프라이버시가 일반적으로 패키지 수준에서 지정되기 때문이에요.

출처: 프라이버시 문서

본문

기본 캡슐화 (Basic encapsulation)

package Encapsulate is
   procedure Hello;

private

   procedure Hello2;
   --  Not visible from external units
end Encapsulate;

with Ada.Text_IO; use Ada.Text_IO;

package body Encapsulate is

   procedure Hello is
   begin
      Put_Line ("Hello");
   end Hello;

   procedure Hello2 is
   begin
      Put_Line ("Hello #2");
   end Hello2;

end Encapsulate;

with Encapsulate;

procedure Main is
begin
   Encapsulate.Hello;
   Encapsulate.Hello2;
   --  Invalid: Hello2 is not visible
end Main;

추상 데이터 타입 (Abstract data types)

이렇게 높은 수준의 세분성에서는 타입의 구현 세부 사항을 어떻게 숨기는지 분명하지 않을 수 있어요. Ada에서는 이렇게 할 수 있어요:

package Stacks is
   type Stack is private;
   --  Declare a private type: You cannot depend
   --  on its implementation. You can only assign
   --  and test for equality.

   procedure Push (S   : in out Stack;
                   Val :        Integer);
   procedure Pop (S   : in out Stack;
                  Val :    out Integer);
private

   subtype Stack_Index is
     Natural range 1 .. 10;

   type Content_Type is
     array (Stack_Index) of Natural;

   type Stack is record
      Top     : Stack_Index;
      Content : Content_Type;
   end record;
end Stacks;

package body Stacks is

   procedure Push (S   : in out Stack;
                   Val :        Integer) is
   begin
      --  Missing implementation!
      null;
   end Push;

   procedure Pop (S   : in out Stack;
                  Val :    out Integer) is
   begin
      --  Dummy implementation!
      Val := 0;
   end Pop;

end Stacks;

위 예시에서 공개 부분(Ada에서 패키지 선언의 보이는 부분 이라고 함)에 스택 타입을 정의하지만, 그 타입의 정확한 표현은 비공개예요.

그런 다음 비공개 부분에서 그 타입의 표현을 정의해요. 우리의 주요 공개 타입의 도우미 로 사용될 다른 타입들도 선언할 수 있어요. Ada에서 도우미 타입 선언이 흔하므로 유용해요.

용어에 대해 몇 마디 하자면:

  • 공개 부분에서 보는 Stack 타입을 타입의 부분 보기(partial view) 라고 해요. 이것이 클라이언트가 접근할 수 있는 것이에요.
  • 비공개 부분이나 패키지 본문에서 보는 Stack 타입을 타입의 전체 보기(full view) 라고 해요. 이것이 구현자가 접근할 수 있는 것이에요.

클라이언트(with하는 유닛)의 관점에서는 공개(보이는) 부분만 중요하고, 비공개 부분은 존재하지 않아도 무방해요. 이는 자신에게 중요한 패키지 부분을 선형으로 쉽게 읽게 해줘요.

--  No need to read the private part to use the package
package Stacks is
   type Stack is private;

   procedure Push (S   : in out Stack;
                   Val :        Integer);
   procedure Pop (S   : in out Stack;
                  Val :    out Integer);
private
   ...
end Stacks;

Stacks 패키지의 사용 방식:

--  Example of use
with Stacks; use Stacks;

procedure Test_Stack is
   S : Stack;
   Res : Integer;
begin
   Push (S, 5);
   Push (S, 7);
   Pop (S, Res);
end Test_Stack;

제한 타입 (Limited types)

Ada의 제한 타입(limited type) 기능을 사용하면 대입과 비교 연산이 자동으로 제공되지 않는 타입을 선언할 수 있어요.

package Stacks is
   type Stack is limited private;
   --  Limited type. Cannot assign nor compare.

   procedure Push (S   : in out Stack;
                   Val :        Integer);
   procedure Pop (S   : in out Stack;
                  Val :    out Integer);
private
   subtype Stack_Index is
     Natural range 1 .. 10;

   type Content_Type is
     array (Stack_Index) of Natural;

   type Stack is limited record
      Top     : Stack_Index;
      Content : Content_Type;
   end record;
end Stacks;

package body Stacks is

   procedure Push (S   : in out Stack;
                   Val :        Integer) is
   begin
      --  Missing implementation!
      null;
   end Push;

   procedure Pop (S   : in out Stack;
                  Val :    out Integer) is
   begin
      --  Dummy implementation!
      Val := 0;
   end Pop;

end Stacks;

with Stacks; use Stacks;

procedure Main is
   S, S2 : Stack;
begin
   S := S2;
   --  Illegal: S is limited.
end Main;

이것은 유용한데, 일부 데이터 타입에서 내장 대입 연산이 잘못될 수 있기 때문이에요(예: 깊은 복사가 필요할 때). Ada는 제한 타입에 대해 비교 연산자 =/=를 오버로드할 수 있게 해주고, 비제한 타입의 내장 선언을 재정의할 수 있게 해줘요.

Ada는 제어 타입(controlled types)을 통해 대입에 대한 특별한 의미를 구현할 수도 있게 해줘요. 하지만 어떤 경우에는 대입이 단순히 부적절해요. 한 예로 Ada.Text_IO 패키지의 File_Type이 있어요. 제한 타입으로 선언되어, 한 파일을 다른 파일에 대입하려는 시도는 불법으로 감지돼요.

자식 패키지와 프라이버시 (Child packages & privacy)

이전에 자식 패키지 절에서 패키지가 자식 패키지를 가질 수 있음을 봤어요. 프라이버시는 자식 패키지에서 중요한 역할을 해요. 이 절에서는 자식 패키지에 적용되는 몇 가지 프라이버시 규칙을 논의할게요.

패키지 P의 비공개 부분이 정보를 캡슐화하기 위한 것이지만, 자식 패키지 P.C의 특정 부분은 P의 이 비공개 부분에 접근할 수 있어요. 그런 경우 P의 비공개 부분 정보는 그 선언의 공개 부분에 선언된 것처럼 사용될 수 있어요. 구체적으로, P.C의 본문과 P.C 선언의 비공개 부분은 P의 비공개 부분에 접근할 수 있어요. 하지만 P.C 선언의 공개 부분은 P 선언의 공개 부분에만 접근할 수 있어요. 다음 표가 이를 요약해요:

자식 패키지의 부분 부모 선언의 비공개 부분에 대한 접근
선언: 공개 부분
선언: 비공개 부분
본문

이 절의 나머지에서는 자식 패키지에서 비공개 정보에 대한 접근이 실제로 어떻게 동작하는지 예시를 보여줄게요.

먼저 자식 패키지 P.C의 본문이 부모 P의 선언 비공개 부분에 접근할 수 있는 예시를 볼게요. 이전 소스 코드 예시에서 Encapsulate 패키지의 비공개 부분에 선언된 Hello2 프로시저는 그곳에서 보이지 않으므로 Main 프로시저에서 사용할 수 없다는 것을 봤어요. 하지만 이 제한은 Encapsulate 패키지의 자식 패키지 부분에는 적용되지 않아요. 실제로 자식 패키지 Encapsulate.Child의 본문은 Hello2 프로시저에 접근할 수 있고, Child 패키지의 Hello3 프로시저 구현에서 볼 수 있듯 호출할 수 있어요:

package Encapsulate is
   procedure Hello;

private

   procedure Hello2;
   --  Not visible from external units
   --  But visible in child packages
end Encapsulate;

with Ada.Text_IO; use Ada.Text_IO;

package body Encapsulate is

   procedure Hello is
   begin
      Put_Line ("Hello");
   end Hello;

   procedure Hello2 is
   begin
      Put_Line ("Hello #2");
   end Hello2;

end Encapsulate;

package Encapsulate.Child is

   procedure Hello3;

end Encapsulate.Child;

with Ada.Text_IO; use Ada.Text_IO;

package body Encapsulate.Child is

   procedure Hello3 is
   begin
      --  Using private procedure Hello2
      --  from the parent package
      Hello2;
      Put_Line ("Hello #3");
   end Hello3;

end Encapsulate.Child;

with Encapsulate.Child;

procedure Main is
begin
   Encapsulate.Child.Hello3;
end Main;

같은 메커니즘이 부모 패키지의 비공개 부분에 선언된 타입에도 적용돼요. 예를 들어 자식 패키지의 본문은 부모 패키지의 비공개 부분에 선언된 레코드의 컴포넌트에 접근할 수 있어요. 예시를 볼게요:

package My_Types is

   type Priv_Rec is private;

private

   type Priv_Rec is record
      Number : Integer := 42;
   end record;

end My_Types;

package My_Types.Ops is

   procedure Display (E : Priv_Rec);

end My_Types.Ops;

with Ada.Text_IO; use Ada.Text_IO;

package body My_Types.Ops is

   procedure Display (E : Priv_Rec) is
   begin
      Put_Line ("Priv_Rec.Number: "
                & Integer'Image (E.Number));
   end Display;

end My_Types.Ops;

with Ada.Text_IO;  use Ada.Text_IO;

with My_Types;     use My_Types;
with My_Types.Ops; use My_Types.Ops;

procedure Main is
   E : Priv_Rec;
begin
   Put_Line ("Presenting information:");

   --  The following code would trigger a
   --  compilation error here:
   --
   --  Put_Line ("Priv_Rec.Number: "
   --            & Integer'Image (E.Number));

   Display (E);
end Main;

이 예시에서 Main 프로시저에서는 레코드 타입 Priv_RecNumber 컴포넌트에 접근할 수 없어요. Main 구현에서 주석 처리된 Put_Line 호출에서 볼 수 있죠. 거기서 Number 컴포넌트에 접근하려 하면 컴파일 오류가 나요. 하지만 My_Types 패키지의 자식 패키지인 My_Types.Ops 패키지 본문에서는 이 컴포넌트에 접근할 수 있어요. 따라서 Ops의 본문은 부모인 My_Types 패키지의 비공개 부분에 있는 Priv_Rec 타입 선언에 접근할 수 있어요. 이런 이유로 Main 프로시저에서 컴파일 오류를 일으키는 같은 Put_Line 호출이 My_Types.Ops 패키지의 Display 프로시저에서는 잘 동작해요.

이런 자식 패키지용 프라이버시 규칙은 부모 패키지의 기능을 확장하면서도 캡슐화를 유지할 수 있게 해줘요.

앞서 언급했듯 패키지 본문에 더해 자식 패키지 P.C의 선언 비공개 부분도 부모 P의 선언 비공개 부분에 접근할 수 있어요. 자식 패키지 My_Types.Child의 비공개 부분에 비공개 타입 Priv_Rec의 객체를 선언하고 Priv_Rec 레코드의 Number 컴포넌트를 직접 초기화하는 예시를 볼게요:

package My_Types.Child is

private

   E : Priv_Rec := (Number => 99);

end My_Types.Ops;

예상대로, 이 선언을 같은 자식 패키지의 공개(보이는) 부분으로 옮기면 이 컴포넌트를 초기화할 수 없어요:

package My_Types.Child is

   E : Priv_Rec := (Number => 99);

end My_Types.Ops;

위 선언은 Priv_Rec 타입이 비공개이므로 컴파일 오류를 일으켜요. My_Types.Child의 공개 부분은 자식 패키지 밖에서도 보이므로, Ada는 이 선언 부분에서 비공개 정보에 접근하는 것을 허용할 수 없어요.

더 알아보기 (Learn more)