[C#] 11. static과 접근 제한자 그리고 캡슐화


Study/C#  2020. 7. 9. 18:55

안녕하세요. 명월입니다.


이 글은 C#에서 static과 접근 제한자 그리고 캡슐화에 대한 글입니다.


이전 글에서 인스턴스 생성과 Stack 메모리와 Heap 메모리에 대해 설명한 적이 있습니다.

링크 - [C#] 10. 인스턴스 생성(new)과 메모리 할당(Stack 메모리와 Heap 메모리) 그리고 null


C#에서는 기본적으로 new로 클래스 인스턴스를 생성하면 Heap 메모리에 인스턴스가 등록되고 함수등을 호출해서 사용할 수 있습니다. 이것이 C#에서 가장 최소 실행 단위가 됩니다.

그러면 Main의 함수는 어떨까요? 우리가 실행 함수라고 불리는 Main 함수를 호출하기 위해서는 어디서 클래스 인스턴스를 생성할까요?

즉, 인스턴스 생성이 되지 않으면 Main 함수를 부를 수 없습니다. 그러나 프로그램이 시작 점이 필요하기는 합니다. 즉, 닭이 먼저냐? 달걀이 먼저냐의 모순이 빠짐니다.

그래서 있는 키워드 전역 키워드 static이 있습니다. 이 static이 붙어 있는 클래스, 함수, 변수는 프로그램이 시작할 때 stack 메모리에 등록을 합니다.

즉, 인스턴스를 호출하지 않아도 사용할 수 있다는 뜻입니다.

using System;
namespace Example
{
  // Exam 클래스
  class Example
  {
    // 인스턴스 생성없이 호출할 수 있는 함수
    public static void Print()
    {
      // 콘솔 출력
      Console.WriteLine("Hello world");
    }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // Print 함수 호출
      Example.Print();

      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

위 예제를 보시면 Example 클래스 인스턴스를 생성하지 않고도 Print함수를 호출할 수 있습니다.

그러므로 Main 함수는 항상 static으로 작성되고 프로젝트 내에서는 꼭 하나여만 합니다.

이 static의 설정은 함수만 할 수 있는 것이 아니고 변수에서도 설정할 수 있습니다.

using System;
namespace Example
{
  // Exam 클래스
  class Example
  {
    // 변수에 static 설정
    private static int data;
    
    // static.data에 값 설정
    public void SetData(int data)
    {
      // static은 할당과 관계가 없기 때문에 this가 아닌 클래스 명으로 참조한다.
      Example.data = data;
    }
    // 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine("Data - " + Example.data);
    }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 2개의 인스턴스 생성
      Example ex1 = new Example();
      Example ex2 = new Example();
      // 각각의 인스턴스에 값을 설정한다.
      ex1.SetData(10);
      ex2.SetData(20);
      // 콘솔 출력
      ex1.Print();
      ex2.Print();
      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

위 예제를 보시면 인스턴스를 두개 선언해서 각각에 값을 설정해도 ex1과 ex2에서는 data가 같은 값으로 출력이 됩니다. 즉, Example.data는 인스턴스와 관계없이 프로그램이 실행될 때 생성되는 변수입니다. 그럼 여기서 Main에서 직접 참조가 가능할 것 같지만 여기서는 private라는 접근 제한자 때문에 접근이 되지 않습니다.


여기서 이 접근 제한자에 대한 설명입니다.

접근 제한자 설명
public 어디서나 사용할 수 있고 클래스 내, 외부와 파생 클래스에서도 클래스 맴버에 접근
private private를 포함한 클래스 내에서만 접근 가능
protected 기본 클래스와 파생 클래스에서만 접근 가능
internal 동일 네임스페이스 내에서 접근 가능
protected internal 동일 프로그램 내에서 또는 파생 클래스에서 접근 가능

C#에서는 총 다섯가지의 접근 제한자가 있습니다만 자주 사용하는 제한자는 public, private, protected입니다. 이 접근 제한자는 실행되는 스탭의 기준에서 클래스의 외부, 내부, 파생의 기준으로 접근을 제한하는 기능입니다.

using System;
namespace Example
{
  // Exam 클래스
  class Example
  {
    // public 함수
    public void CallPublic()
    {
      // 콘솔 출력
      Console.WriteLine("Public");
    }
    // private 함수
    private void CallPrivate()
    {
      // 콘솔 출력
      Console.WriteLine("Private");
    }
    // protected 함수
    protected void CallProtected()
    {
      // 콘솔 출력
      Console.WriteLine("Protected");
    }
    // 함수
    public void Call()
    {
      // 이 스텝은 ex 인스턴스 기준에서 Example 클래스 내부입니다.
      // public 접근 가능
      CallPublic();
      // private 접근 가능
      CallPrivate();
      // private 접근 가능
      CallProtected();
    }
  }
  // Example 상속한 클래스
  class SubExample : Example
  {
    public void Call()
    {
      // 이 스텝은 ex 인스턴스 기준에서 Example 클래스 파생입니다.
      // public 접근 가능
      CallPublic();
      // protected 접근 가능
      CallProtected();
    }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // Example 인스턴스 생성
      Example ex = new Example();
      // 이 스탭은 ex 인스턴스의 기준에서 Example 클래스 외부이므로 public만 접근 가능합니다.
      Console.WriteLine("Main");
      ex.CallPublic();
      Console.WriteLine();
      Console.WriteLine("Example");
      // Call 함수 호출
      ex.Call();
      Console.WriteLine();
      Console.WriteLine("SubExample");
      // SubExample 인스턴스 생성
      SubExample sub = new SubExample();
      // Call 함수 호출
      sub.Call();
      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

여기서 보시면 Main 함수에서는 Example 클래스의 인스턴스 ex의 public으로 접근 제한자가 설정된 함수만 호출을 할 수 있습니다.

Call 함수에서는 Example 클래스 내부에 있어서 내부로 인식이 되므로 모든 접근 제한자가 접근을 할 수 있습니다.

SubExample은 Example 클래스를 볼 때 파생 클래스 이므로 public protected만 접근이 가능합니다.


C#에서 왜 이런 접근 제한을 설정하는 가 하면 객체 지향 프로그래밍(OOP)의 특성 중에 하나인 캡슐화 때문입니다.

OOP에서 클래스는 하나의 객체(Object)로 설계를 합니다. 즉, 우리가 성적 프로그램을 만든다고 할 때, 클래스로 사람이라는 객체를 만들고 그 안에 성적을 넣는다고 설계합니다.

using System;
namespace Example
{
  // 성적을 위한 사람 클래스
  class Person
  {
    // 이름
    private string name;
    // 국어 성적
    private int kor;
    // 수학 성적
    private int mat;
    // 영어 성적
    private int eng;
    // 총점
    private int tot;
    // 평균
    private int avg;
    // 생성자
    public Person(string name, int kor, int mat, int eng)
    {
      // 맴버 변수 설정
      this.name = name;
      this.kor = kor;
      this.mat = mat;
      this.eng = eng;
      // 총점, 평균 계산
      Init();
    }
    // 초기화 함수
    protected void Init()
    {
      // 촘점 계산
      this.tot = this.kor + this.mat + this.eng;
      // 평균 계산
      this.avg = this.tot / 3;
    }
    // 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine($"{this.name} = {this.kor} , {this.mat} , {this.eng} = {this.tot} : {this.avg}");
    }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 인스턴스 생성
      Person p = new Person("a", 100, 60, 70);
      // 출력 함수
      p.Print();
      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

위 예제를 보시면 Person이라는 클래스에 이름과 국영수 성적을 생성자로 넣습니다.

그리고 생성자 안에서는 Init함수를 호출해서 총점과 평균을 계산합니다. 여기서 성적에 관계되는 맴버 변수는 전부 private입니다.

그리고 출력으로 성적을 콘솔에 출력합니다. 여기서 만약 변수가 모두 public라고 생각하면...

Init함수가 의미가 없어지고 Print 함수도 의미가 없어집니다. 아니, 클래서 전체가 의미가 없어집니다. 그냥 변수를 보기 좋게 정리해 놓은 단위일 뿐...

즉, 이런 설정을 확실히 정해 놓고 프로그램을 개발하지 않으면 결과적으로 엉망진창으로 개발될 확률이 높아지고 가독성이 매우 떨어지는 프로그램을 작성할 가능성이 높습니다.

물론 이 OOP를 코딩 표준을 지키지 않는다고 해서 실행되지 않는 것은 아닙니다.


OOP의 표준으로는 맴버 변수는 무조건 private 함수는 외부에서 호출해야 할 함수만 public으로 설정하고 모두 protected로 설정하는 것이 룰입니다.

OOP에 대해서는 다른 글에서 좀 더 자세하게 설명하도록 하겠습니다.


여기까지 C#에서 static과 접근 제한자 그리고 캡슐화에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.