본문 바로가기

Programming

객체지향프로그래밍 (OOP) - 캡슐화 : 쉽고 재미있는 C# Programming 의 기본

 

객체지향 프로그래밍(Object-Oriented Programming, OOP)은 소프트웨어를 개발하는 방법론 중 하나로, 현실 세계의 사물을 모델링하여 프로그램을 구성하는 개념입니다.

 

클래스를 기반으로 레고 조립하듯 하나씩 객체 ( Object )를 만들어가는 프로그래밍 방법을 '객체지향 프로그래밍 OOP'라고 합니다.

 

'객체지향 프로그래밍'의 특징에는 객체의 형태를 감싸는 '캡슐화'와 부모 클래스의 특성을 복제하여 다른 자식 클래스로 복제하는 '상속성', 그리고 자식 클래스에서 부모 클래스의 행동을 재가공하는 '다형성'이 있습니다.

 

그중 캡슐화에 대해서 먼저 살펴보겠습니다.


1. 캡슐화 (Encapsulation)

 

캡슐화 (Encapsulation) 는 객체지향 프로그래밍의 하나로, 객체의 상태와 행동을 하나로 묶고, 외부에서 직접 접근하지 못하게 은닉하는 개념입니다. 이는 객체의 내부 구현을 외부에 감춤으로써 정보 은닉, 모듈화, 코드 재사용성을 증가시키는 데 기여합니다.

 

2. 캡슐화의 핵심 개념

 

▶ 정보은닉 ( Information Hiding )

- 객체 내부의 세부 구현을 외부에 숨김으로써, 외부에서 직접적으로 접근이 불가능하게 만듭니다.

- 객체의 내부 구현을 변경해도 외부에서 영향을 받지 않도록 합니다.

 

모듈화 ( Modularity )

- 객체는 자체적으로 기능을 수행하며, 외부에서는 그 기능에만 접근할 수 있습니다.

- 객체는 외부에 대해 독립적이고 각각의 역할을 수행합니다.

 

코드 재사용성 ( Code Reusability )

- 객체의 내부 구현을 숨기면 외부에서는 객체의 기능만 이용할 수 있으므로, 해당 객체를 다른 프로그램이나 모듈에서도 재사용할 수 있습니다.

 

3. 캡슐화의 구현

 

접근 제어 지시자 ( Access Modifiers )

- 클래스의 멤버 변수나 메서드에 대한 접근을 제어할 수 있는 지시자를 사용합니다.

- C# 에서는 public, private, protected, internal 등이 사용됩니다.

class Person
{
    private string name;  // private으로 선언하여 외부에서 직접 접근 불가
    public void SetName(string newName)  // 외부에서는 SetName 메서드를 통해 name에 접근
    {
        name = newName;
    }
}

 

프로퍼티 ( Properties, 속성 )

- 클래스의 멤버변수를 간접적으로 접근할 수 있는 get/set 메서드를 정의하는 것을 프로퍼티라고 합니다.

- 프로퍼티를 통해 객체의 상태에 접근하고 수정할 수 있습니다.

class Person
{
    private string name;

    public string Name  // 프로퍼티를 통해 name에 접근
    {
        get { return name; }
        set { name = value; }
    }
}

 

메서드를 통한 접근

- 객체의 상태를 변경하는 메서드를 통해서만 객체 내부의 상태에 접근할 수 있도록 합니다.

class BankAccount
{
    private double balance;

    public void Deposit(double amount)  // 메서드를 통해만 balance에 접근
    {
        balance += amount;
    }
}

 

4. 캡슐화의 장점

 

보안성 ( Security )

- 정보 은닉을 통해 객체의 내부 상태를 외부로부터 보호하고, 외부에서는 필요한 인터페이스만 사용할 수 있습니다.

 

유지보수성 ( Maintainability )

- 내부 구현을 변경해도 외부에 영향을 미치지 않으므로 시스템의 유지보수가 용이해집니다.

 

재사용성 ( Reusability )

- 모듈화 된 객체는 다른 곳에서도 사용 가능하며, 코드의 재사용성을 높여줍니다.

 

구현의 자유도 ( Implementation Flexibility )

- 객체 내부 구현이 외부에 은폐되어 있기 때문에 내부 구현을 변경하더라도 외부 코드에 영향을 주지 않습니다.

 

캡슐화는 객체지향 프로그래밍의 중요한 특징 중 하나로, 객체를 구현하고 사용하는 데 있어서 유연성과 보안성을 제공합니다.

 

5. 캡슐화 심화

 

캡슐화는 데이터와 관련된 기능(메서드)을 하나의 묶음으로 묶어두는 것입니다.

이렇게 하면 데이터를 외부에서 직접 조작하지 못하도록 막고, 묶인 기능을 통해서만 데이터를 다룰 수 있게 됩니다.

public class BankAccount
{
    private decimal balance; // 은행 계좌의 잔액

    // 잔액을 조회하는 메서드
    public decimal GetBalance()
    {
        return balance;
    }

    // 입금하는 메서드
    public void Deposit(decimal amount)
    {
        if (amount > 0)
        {
            balance += amount;
        }
    }

    // 출금하는 메서드
    public void Withdraw(decimal amount)
    {
        if (amount > 0 && balance >= amount)
        {
            balance -= amount;
        }
    }
}

* 'decimal'은 C#에서 숫자를 표현하는 데이터 형식 중 하나입니다. 이는 부동 소수점 형식인 floatdouble과는 달리 고정 소수점 형식을 사용합니다. decimal은 주로 금융 계산과 같이 정밀한 소수점 연산이 필요한 경우에 사용됩니다.

* '고정 소수점 형식'은 소수점 아래 자릿수가 고정되어 있습니다. 즉, 어떤 연산을 수행하더라도 항상 일정한 소수 자릿수를 유지합니다.

decimal myDecimal = 123.45m;

* 'm'은 리터럴(Literals : 고정된 값을 나타내는 표기법, 프로그램 실행 중 변경되지 않는 값을 의미합니다.)이 decimal 형식임을 나타냅니다. 형변환을 통해 변환이 가능합니다.

 

여기서 balance는 은행 계좌의 잔액을 나타내는데, 이 데이터는 해당 클래스 내에서만 접근할 수 있고, 외부에서 직접적으로 접근할 수 없습니다. 

대신에 GetBalance(), Deposit(), Withdraw()라는 메서드를 통해서만 잔액을 조회하고 입출금을 할 수 있습니다.

 

이렇게 하면 은행 계좌의 잔액을 무분별하게 조작하는 것을 방지하고, 잔액 조작에 필요한 로직을 메서드 안에 넣어둠으로써 코드를 더욱 안전하게 만들 수 있습니다.

 

5.1 속성 (Properties)

 

C#에서의 속성(Properties)은 클래스나 구조체 등의 데이터 구조에서 데이터 멤버에 접근하는 데 사용되는 멤버입니다. 속성은 필드(변수)와 비슷하지만, 값에 접근하는 방식이 메서드를 호출하는 것과 유사하게 보이도록 설계되어 있습니다. 이러한 속성은 클래스의 캡슐화 및 데이터 접근을 더 효과적으로 관리할 수 있도록 도와줍니다.

 

아래는 C#에서 속성이 갖는 주요 특징과 의미를 설명한 것입니다:

 

  • 값의 은폐 (Encapsulation):

    속성은 클래스 내부의 구현 세부사항을 숨깁니다. 즉, 내부의 데이터에 직접 접근하는 것이 아니라 속성을 통해 간접적으로 값을 읽거나 쓸 수 있도록 합니다.

  • 접근 제어 (Access Control):

    속성을 사용하면 값을 읽고 쓸 때 추가적인 로직을 적용할 수 있습니다. 이로써 값을 유효성 검사하거나 특정 동작을 수행하는 등의 작업을 수행할 수 있습니다.

  • 객체의 외부와 내부를 연결 (Getters and Setters):

    속성은 일반적으로 get과 set 메서드를 가지며, 이를 통해 값을 반환하거나 설정할 수 있습니다. 이는 객체의 외부에서 속성 값을 읽고 쓸 때 메서드를 호출하는 것과 유사한 방식으로 사용됩니다.

  • 가독성 향상 (Readability):

    코드를 읽고 이해하기 쉽게 만들어줍니다. 예를 들어 myObject.Name = "John";와 같은 코드를 통해 속성을 사용하면, 멤버에 직접 접근하는 것보다 의도가 더 명확해집니다.

5.2 get 및 set

  • get : 속성의 값을 읽을 수 있는 블록( 중괄호 {} 로 둘러싸인 코드 부분)을 정의합니다.
  • set : 속성의 값을 설정할 수 있는 블록을 정의합니다.
public class Rectangle
{
    private double width;
    
    // 읽기/쓰기 속성
    public double Width
    {
        get { return width; }   // 값 읽기
        set { width = value; }  // 값 쓰기
    }
}

 

5.2.1 get 블록

  • get 블록은 속성으로부터 값을 읽을 때 실행되는 부분입니다.
  • 속성의 값을 반환하고, 읽기 연산을 처리합니다. 속성을 읽을 때 반환되는 값을 정의합니다.
public class Example
{
    private int value;

    // 읽기 속성
    public int GetValue
    {
        get { return value; }  // get 블록: 값을 반환
    }
}

 

5.2.2 set 블록:

  • set 블록은 속성에 값을 할당할 때 실행되는 부분입니다.
  • 속성에 값을 설정하고, 쓰기 연산을 처리합니다. 속성에 값을 설정할 때 전달되는 값을 받아 처리하거나 유효성 검사 등을 수행합니다.
public class Example
{
    private int value;

    // 쓰기 속성
    public int SetValue
    {
        set { this.value = value; }  // set 블록: 값을 설정
    }
}

* this : 현재 객체를 가리키는 키워드입니다. 클래스의 인스턴스 메서드 내에서 사용되며, 해당 메서드가 속한 객체를 나타냅니다. 'this'를 사용하여 현재 객체의 멤버에 접근할 수 있습니다.

보통 this는 명시적으로 필요한 경우가 아니라면 생략되어 사용됩니다. 특히 메서드 내에서 로컬 변수와 멤버 변수의 이름이 같은 경우에 구별하기 위해 사용됩니다.

위의 코드에서 this.value는 현재 객체의 value라는 멤버 변수(private int value;)를 나타냅니다. this를 사용하지 않고 value만 사용하는 것도 가능하지만, 가독성을 높이기 위해 명시적으로 사용될 때가 있습니다.

 

 

이 두 블록을 조합하여 읽기 전용, 쓰기 전용, 읽기/쓰기 속성 등을 정의할 수 있습니다. 아래는 읽기/쓰기 속성의 예시입니다.

public class Example
{
    private int value;

    // 읽기/쓰기 속성
    public int ReadWriteValue
    {
        get { return value; }      // 값을 읽을 때
        set { this.value = value; } // 값을 쓸 때
    }
}

 

이렇게 속성을 통해 getset을 사용하면 외부에서 객체의 상태를 안전하게 읽거나 쓸 수 있게 되며, 유효성 검사 등의 로직을 더 쉽게 추가할 수 있습니다.

 

5.2.3 자동 구현 속성(Auto-implemented Properties)

 

자동 구현 속성은 C#에서 속성을 간편하게 선언하는 방법 중 하나입니다. 이것은 getset 블록을 명시적으로 작성하지 않고 컴파일러에 의해 자동으로 생성되는 특별한 형태의 속성입니다.

 

간단한 구문으로 이해할 수 있습니다. 예를 들어, 다음과 같은 클래스가 있다고 가정해 봅시다.

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

 

위의 코드에서 Name과 Age는 자동 구현 속성입니다. 여기서 get과 set 블록이 명시되지 않았지만, 컴파일러는 이를 자동으로 생성합니다. 이것은 실제로 각 속성에 대한 백엔드 필드를 만들고, 값을 읽고 쓰는 메서드를 자동으로 구현합니다.

이를 통해 다음과 같이 사용할 수 있습니다.

Person person = new Person();
person.Name = "John";  // 속성을 통한 값의 설정
int age = person.Age;  // 속성을 통한 값의 읽기

 

여기서 Name과 Age는 마치 멤버 변수처럼 사용되지만, 컴파일러가 자동으로 생성한 get과 set 메서드를 통해 값을 읽고 쓸 수 있습니다.

자동 구현 속성은 간단한 데이터 저장소를 만들 때, 특별한 로직이 필요하지 않을 때 편리하게 사용됩니다. 이것은 코드를 더 간결하게 만들어주고 가독성을 높일 수 있습니다.

 

자동 구현 속성은 변수(멤버 변수 또는 필드)를 편리하게 사용하기 위해 만들어진 것입니다. 이것은 변수처럼 값을 저장하고, 값을 읽고 쓰기 위한 간단한 방법을 제공합니다.

 

자동 구현 속성을 사용하면 별도의 get과 set 블록을 명시적으로 작성하지 않아도 됩니다. 이것은 코드를 더 간결하게 만들고, 클래스의 인터페이스를 간단하게 유지할 수 있게 해 줍니다.

 

5.3 readonly 속성:

 

readonly 속성은 C#에서 변수나 필드를 읽기 전용으로 만드는 특별한 형태의 속성입니다. 이는 해당 변수나 필드가 한 번 초기화된 이후에는 값을 변경할 수 없도록 합니다.

 

간단한 예제로 설명하겠습니다. 아래의 코드에서 MyReadOnlyProperty는 readonly로 선언되어 있습니다:

public class Example
{
    // readonly 속성
    public readonly int MyReadOnlyProperty;

    // 생성자에서 값을 초기화
    public Example(int initialValue)
    {
        MyReadOnlyProperty = initialValue;
    }
}

 

이 경우, MyReadOnlyProperty는 객체가 생성될 때 initialValue로 초기화되며, 이후에는 값을 변경할 수 없습니다. 다른 메서드에서도 값을 변경할 수 없고, 다시 할당할 수 없습니다. readonly 속성은 주로 객체의 불변성(Immutability)을 강조하고자 할 때 사용됩니다.

 

5.4 속성 초기자 (Property Initializers):

 

속성 초기자(Property Initializer)는 객체의 속성에 초기값을 설정하는 간편한 방법입니다. 이는 객체를 생성할 때 속성에 값을 할당하는 것을 더 간단하게 만들어줍니다.

 

간단한 예제를 통해 설명하겠습니다.

public class Person
{
    // 속성 초기자 사용
    public string Name { get; set; } = "John";
    public int Age { get; set; } = 25;
}

class Program
{
    static void Main()
    {
        // 객체 생성 시 속성 초기자 사용
        Person person = new Person();

        // 객체 정보 출력
        Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
    }
}

 

위의 코드에서 Person 클래스의 Name과 Age 속성은 각각 "John"과 25로 초기화되어 있습니다. 이는 속성 초기자를 통해 설정된 것입니다. 객체를 생성할 때 별도의 초기화 코드 없이 기본값이 자동으로 할당됩니다.

속성 초기자는 간단하게 객체의 초기 상태를 정의할 때 유용하며, 코드를 더 간결하게 만들어줍니다.

 

5.5. 메서드 (Method)

 

메서드(Method)는 특정 작업을 수행하는 코드 블록으로, 클래스나 객체 내에서 정의되며 호출될 때 해당 작업을 실행합니다. C#에서 메서드는 다음과 같은 형식을 가집니다:

[접근 한정자] 반환형식 메서드이름(매개변수)
{
    // 메서드의 실행 코드
    return 반환값; // 반환형식이 있는 경우
}

 

여기서:

  • 접근 한정자(Access Modifier): 메서드의 접근 권한을 제어합니다. public, private, internal 등이 있습니다.
  • 반환형식(Return Type): 메서드가 반환하는 값의 데이터 타입을 나타냅니다. 만약 메서드가 값을 반환하지 않는다면 void를 사용합니다.
  • 메서드 이름(Method Name): 메서드를 식별하는 이름입니다.
  • 매개변수(Parameters): 메서드가 호출될 때 전달되는 값들을 받기 위한 변수들입니다.
  • 실행 코드(Execution Code): 메서드가 수행할 작업이 정의된 코드 블록입니다.
  • 반환값(Return Value): 메서드가 반환하는 값이 있는 경우 사용됩니다.

간단한 예를 통해 메서드를 이해해 보겠습니다.

public class Calculator
{
    // 덧셈 메서드
    public int Add(int a, int b)
    {
        int result = a + b;
        return result;
    }

    // 곱셈 메서드
    public int Multiply(int a, int b)
    {
        int result = a * b;
        return result;
    }
}

 

위의 Calculator 클래스에는 AddMultiply라는 두 개의 메서드가 있습니다. Add 메서드는 두 개의 정수를 더하고, Multiply 메서드는 두 개의 정수를 곱합니다. 이러한 메서드들을 사용하여 객체나 클래스의 기능을 확장하고 모듈화 할 수 있습니다.

 

"확장"과 "모듈화"는 재사용성을 높이고 코드의 유지보수성을 향상하는 데 기여하는 두 가지 중요한 소프트웨어 개발 원칙입니다.

 

  1. 확장(Extensibility)
    • 확장은 기존의 코드에 새로운 기능을 추가하거나 새로운 요구사항에 대응할 수 있도록 코드를 확장하는 것을 의미합니다.
    • 메서드를 추가하거나 수정함으로써 클래스나 객체의 기능을 확장할 수 있습니다.
    • 예를 들어, 새로운 계산 기능을 추가하거나 다양한 입력 형식을 처리할 수 있는 메서드를 추가하는 등이 확장의 예시입니다.
  2. 모듈화(Modularity)
    • 모듈화는 코드를 독립적인 모듈로 나누어 각 모듈이 특정 역할을 수행하도록 설계하는 것을 의미합니다.
    • 모듈은 재사용 가능한 단위로, 한 모듈의 변경이 다른 모듈에 영향을 미치지 않도록 설계됩니다.
    • 예를 들어, 여러 메서드로 구성된 클래스를 각각의 기능에 따라 모듈화 하면, 특정 기능이 변경되더라도 다른 기능에 영향을 미치지 않도록 할 수 있습니다.

재사용성은 코드를 재사용할 수 있는 정도를 나타내며, 확장과 모듈화가 이를 도와 재사용 가능한 코드를 작성하는 데 도움이 됩니다. 이로 인해 유사한 기능을 다양한 부분에서 사용하거나, 변경이 필요한 경우에도 전체 시스템을 수정하지 않고 해당 모듈만 수정할 수 있습니다.

 

5.6 매개변수(Parameter):

  • 매개변수는 메서드가 호출될 때 전달되는 값들을 받기 위한 변수입니다.
  • 메서드 정의에서 괄호 안에 매개변수를 선언하며, 여러 개의 매개변수가 있을 경우 쉼표로 구분합니다.
public void MyMethod(int a, string b)
{
    // 메서드 내용
}

 

5.7 반환값(Return Value)

  • 반환값은 메서드가 실행된 후에 결과를 호출한 곳으로 반환하는 값입니다.
  • 반환형식을 메서드 정의에 명시하고, return 키워드를 사용하여 반환값을 지정합니다.
public int Add(int a, int b)
{
    int result = a + b;
    return result;
}

 

5.8 메서드 오버로딩(Method Overloading)

  • 메서드 오버로딩은 같은 이름의 메서드를 여러 개 정의하는 것을 말합니다.
  • 메서드의 매개변수의 타입, 개수, 또는 순서가 다르면 동일한 이름의 메서드를 정의할 수 있습니다.
public int Add(int a, int b)
{
    return a + b;
}

public double Add(double a, double b)
{
    return a + b;
}

 

5.9 정적 메서드(Static Method)

  • static 키워드를 사용하여 정적 메서드를 정의할 수 있습니다. 정적 메서드는 클래스 자체에 속하며, 객체의 인스턴스를 생성하지 않고 호출할 수 있습니다.
public class MathHelper
{
    public static int Add(int a, int b)
    {
        return a + b;
    }
}

// 사용 예시
int result = MathHelper.Add(3, 4);

 

5.10 가변 길이 매개변수(Varargs)

  • C# 4.0부터는 가변 길이 매개변수를 사용할 수 있습니다. params 키워드를 사용하여 가변 길이의 매개변수를 받을 수 있습니다.
public void DisplayValues(params int[] values)
{
    foreach (var value in values)
    {
        Console.WriteLine(value);
    }
}

// 사용 예시
DisplayValues(1, 2, 3, 4, 5);

 

이러한 메서드의 특징과 활용은 코드의 구조를 유연하게 설계하고 다양한 상황에 대응할 수 있도록 도와줍니다.