반응형

추상화(Abstract)란 무엇인가?

추상화는 특정 정보는 숨기고, 필수 정보만 사용자에게 표시하는 프로세스이다. 추상화는 클래스나 인터페이스를 사용하여 구현할 수 있다. 클래스 또는 메서드를 선언할 때 abstract 키워드를 사용하면 된다.

 

추상화를 거쳐 추상 클래스 또는 추상 메서드가 되면 직접적인 접근이 제한되고, 상속에 의해서만 접근할 수 있다.

추상 클래스와 메서드

  • 추상 클래스 : 객체를 생성하는 데 사용할 수 없다. 접근을 위해서는 상속되어야 한다.
  • 추상 메서드 : 추상 메서드가 포함된 추상 클래스에서만 사용가능하며, 추상 클래스를 상속받은 자식 클래스(파생 클래스)에서 접근할 수 있다.

추상 클래스는 왜 사용되는가?

추상 클래스를 뼈에 비유해보자. 뼈는 동물의 기본 구조를 정의한다. 이와 마찬 가지로 추상 클래스는 하위 클래스들이 공유할 수 있는 기본적인 특성을 정의하는데 사용된다. 예시1을 살펴보자.

예시1:

using System;

// 추상 클래스인 Animal
abstract class Animal
{
    // 추상 메서드 MakeSound 정의
    public abstract void MakeSound();
}

// Animal을 상속받은 Dog 클래스 (살)
class Dog : Animal
{
    // MakeSound 메서드 구현
    public override void MakeSound()
    {
        Console.WriteLine("멍멍!");
    }

    // Dog 클래스의 특정한 기능과 속성
    public void WagTail()
    {
        Console.WriteLine("꼬리를 흔들며 행복해요!");
    }
}

// Animal을 상속받은 Cat 클래스 (장기)
class Cat : Animal
{
    // MakeSound 메서드 구현
    public override void MakeSound()
    {
        Console.WriteLine("야옹!");
    }

    // Cat 클래스의 특정한 기능과 속성
    public void Purr()
    {
        Console.WriteLine("편안해요.");
    }
}

class Program
{
    static void Main(string[] args)
    {
        // Dog 클래스의 인스턴스 생성
        Dog myDog = new Dog();
        Console.WriteLine("Dog이 소리를 냅니다:");
        myDog.MakeSound(); // Dog의 MakeSound 메서드 호출
        myDog.WagTail(); // Dog의 WagTail 메서드 호출

        Console.WriteLine();

        // Cat 클래스의 인스턴스 생성
        Cat myCat = new Cat();
        Console.WriteLine("Cat이 소리를 냅니다:");
        myCat.MakeSound(); // Cat의 MakeSound 메서드 호출
        myCat.Purr(); // Cat의 Purr 메서드 호출
    }
}

예시1에서는 Animal이 ‘뼈 역할’을 하고, 이를 상속받아 구체적인 동물 클래스들이 살이나 장기 역할을 한다고 보면 된다. 추상 클래스인 Animal에는 공통적인 동작인 MakeSound 메서드가 정의되어 있다.

 

다시 한번 정리하자면 추상 클래스는 하위 클래스들에게 상속할 ‘공통된 특성’을 정의한다. 이를 상속받은 Dog, Cat 클래스 등은 각각 저마다의 특성과 행동을 정의한다. 이런 식으로 호랑이, 염소 등 동물의 공통된 특징(예시1에서는 울음소리)을 상속받고, 각 클래스에 맞게 확장하고, 구체화하는 것이 가능해진다.

다형성과의 추상화

예시1에서 Cat 클래스와 Dog 클래스 내에 override 키워드가 사용된 것을 알 수 있다. 이전 포스트에서 다형성에 대해 이야기했는데, 다형성 역시 상속을 기반으로 한다. 다형성에 대한 자세한 설명은 아래 포스트를 참고하자.

2024.04.23 - [C# 기초] - [C#기초] 상속(Inheritance)과 다형성(Polymorphism)

 

추상화와 다형성은 객체지향 프로그래밍에서 매우 밀접한 관련이 있는 개념이라고 한다. 코드의 재사용성 측면에서 둘 다 좋은 효율을 보여주는 거 같다.

 

추상화가 공통된 특성을 정의하는 것이라면, 다형성은 이를 바탕으로 하위 클래스들이 동일한 이름의 메서드를 사용할 수 있게 해 준다. 예시1에서는 MakeSound라는 메서드를 오버라이딩하여 모든 클래스에서 같은 이름으로 사용하고 있다.

반응형
반응형

상속(Inheritance)이란?

상속은 한 클래스의 필드(Field)와 메서드(Method)를 다른 클래스로 전달하는 개념이다. 보통 상속하는 클래스를 '부모 클래스', 상속 받는 클래스를 '자식 클래스'라고 한다.

Tenor

 

실생활에 있을 법한 예를 들어 보자. 엄마가 중학생 아들에게 학교 생활을 하며 사용하라고 체크카드를 줬다. 이 체크카드는 엄마 명의의 계좌에 연결되어 있다. 아들은 엄마 명의의 계좌에 있는 돈을 마음대로 사용할 수 있다. 이를 코드로 한 번 구현해 보자.

예시1:

class MotherAccount // base class 부모 클래스  
{
    public string accountName = "신한";
    public void Payment() // 
    {
        Console.WriteLine("0000원이 결제 되었습니다.");
    }
}

class Son : MotherAccount // Son 클래스가 MotherAccount 클래스를 상속  
{
    public string name = "영수";
}

class Program
{
    static void Main(string[] args)
    {
        // mySon 객체 생성  
        Son mySon = new Son();

        // Payment() 메서드 호출
        mySon.Payment();

        Console.WriteLine(mySon.name + "가 " + mySon.accountName + "체크카드를 사용했어요.");
    }
}

예시1의 결과

MotherAccount라는 클래스를 상속한 Son 클래스가 MotherAccount의 멤버에 접근할 수 있게 되었다. 상속을 하는 방법은 클래스를 선언할 때 다음과 같이 : 기호를 사용하면 된다. class Son : MotherAccount

sealed 키워드

다른 클래스가 특정 클래스에 상속되는 것을 원하지 않을 때는 sealed키워드를 선언부 앞에 추가하면 된다.
예시2:

sealed class Vehicle
{

}

class Car : Vehicle{

}

상속하려고 하는 경우 다음과 같은 에러 메시지가 발생할 것이다.

다형성(Polymorphism)이란 무엇일까?

다형성은 "다양한 형태"를 의미하는 말이다. 객체 지향 프로그래밍에서 중요한 개념인데, 같은 이름의 메서드가 다른 클래스에 의해 다르게 구현될 수 있는 능력을 의미한다. 아래 예시를 살펴보자.

예시3:

class Animal  // 부모클래스
{
  public virtual void animalSound() 
  {
    Console.WriteLine("The animal makes a sound");
  }
}

class Pig : Animal  // 자식클래스 Pig
{
  public override void animalSound() 
  {
    Console.WriteLine("The pig says: wee wee");
  }
}

class Dog : Animal  // 자식 클래스 Dog
{
  public override void animalSound() 
  {
    Console.WriteLine("The dog says: bow wow");
  }
}

class Program 
{
  static void Main(string[] args) 
  {
    Animal myAnimal = new Animal();  // Animal 객체 생성
    Animal myPig = new Pig();  // Pig 객체 생성
    Animal myDog = new Dog();  // Dog 객체 생성

    myAnimal.animalSound();
    myPig.animalSound();
    myDog.animalSound();
  }
}

상속을 통해 PigDog 클래스는 Animal 클래스의 특성을 상속받는다. 이처럼 새로운 동물 클래스를 추가할 때 기존 코드의 수정 없이 새로운 클래스를 만들어 확장할 수 있다.

 

앞서 다형성은 같은 이름의 메서드가 다른 클래스에 의해 다르게 구현될 수 있음을 의미한다고 했다. 예시3에서 살펴보면 PigDog 클래스는 Animal 클래스 내의 animalSound()라는 메서드와 동일한 이름을 가진 메서드를 클래스 내에 선언하고 있다.

 

100마리의 동물 클래스를 추가한다고 했을 때 다형성을 사용하지 않는다고 하면 어떻게 될까? animalSound1, animalSound2… 이런 형태로 메서드 이름을 각자 다르게 가져가야 할 것이다.

virtual과 override 키워드

다형성은 virtual 키워드와 override 키워드를 사용해 구현할 수 있다.

예시4:

class Animal  // 부모클래스
{
  public virtual void animalSound() 
  {
    Console.WriteLine("The animal makes a sound");
  }
}

부모 클래스에서 virtual 키워드가 붙은 메서드는 하위 클래스에서 재정의 할 수 있게 된다.

class Dog : Animal  // 자식 클래스 Dog
{
  public override void animalSound() 
  {
    Console.WriteLine("The dog says: bow wow");
  }
}

Dog 클래스 내의 animalSound()메서드는 Animal 클래스 내의 메서드와 이름이 같다. 하지만 메서드 내의 코드는 다르게 작성된 것을 확인할 수 있다. 이런 형태로 부모 클래스의 메서드를 재정의(Override) 할 수 있다.

반응형
반응형

프로퍼티 및 캡슐화(Property and Encapsulation)

캡슐화(Encapsulation)

객체 지향 프로그래밍의 주요 개념 중 하나이다. 데이터와 그 데이터를 조작하는 메서드를 하나로 묶어 “민감한” 데이터가 사용자에게 숨겨지도록 하는 것이다.

이전 포스트에서 접근 제한자의 사용 방법에 대해 다뤘다. private라는 접근 제한자를 사용함으로써 외부 클래스에서 접근을 제한했는데, 이와 같은 일련의 과정을 캡슐화(Encapsulation)라고 할 수 있을 것이다.

캡슐화는 다음과 같은 과정을 수행해야 한다.

  • 필드/변수를 private로 선언
  • private 필드에 액세스하고, 업데이트하기 위해 get, set 메소드를 사용

프로퍼티(Property)

앞서 설명한 거처럼 private는 동일한 클래스 내에서만 접근할 수 있다. 유니티로 개발을 하다 보면 때때로 외부 클래스에서 private 멤버에 접근해야 하는 경우가 있다. 이 것은 프로퍼티 통해 가능하다.

프로퍼티는 get, set 두 가지 메서드를 사용해 public으로 선언했을 때처럼 private 필드의 값을 읽고, 쓰고, 계산하는 멤버다. 멤버는 클래스 내에 있는 변수, 메서드 등을 멤버라고 한다.

예시 1:

class Person
{
  private string name; // 필드

  public string Name   // property
  {
    get { return name; }   // get 메소드
    set { name = value; }  // set 메소드
  }
}

Name 프로퍼티는 name 필드와 연결된다. 프로퍼티의 첫 글자는 대문자이고, 필드와 프로퍼티의 이름은 같게 하는 것이 권장된다.

get 메서드는 name 필드의 값을 반환한다. set 메소드는 name 필드에 값을 할당한다. 예시 1에서 value 키워드는 name 필드에 할당하는 값을 의미한다. 예시 2에서 조금 더 자세히 살펴보도록 하자.

예시 2:

class Person
{
  private string name; // 필드
  public string Name   // 프로퍼티
  {
    get { return name; }
    set { name = value; }
  }
}

class Program
{
  static void Main(string[] args)
  {
    Person myObj = new Person(); // 객체 생성
    myObj.Name = "Liam";
    Console.WriteLine(myObj.Name);
  }
}

//출력 : Liam

Program이라는 외부 클래스에서 Person 클래스에 대한 myObj라는 객체를 생성했다. 그리고 myObj객체에 Name 프로퍼티를 사용하여 name이라는 필드에 Liam이라는 값을 할당했다.

프로퍼티를 사용했기 때문에 private로 선언되었지만 외부 클래스에서 접근하여 필드의 값까지 수정할 수 있게 된 것이다.

자동 프로퍼티(Automatic Property)

C#에서는 단축/자동 프로퍼티를 사용하는 방법도 제공한다. 속성에 대한 필드를 정의할 필요가 없는 경우에는 예시 3에서 보는 것처럼 단축해서 코드를 작성할 수 있다.

예시 3:

class Person
{
  public string Name  // 프로퍼티
  { get; set; }
}

class Program
{
  static void Main(string[] args)
  {
    Person myObj = new Person();
    myObj.Name = "Liam";
    Console.WriteLine(myObj.Name);
  }
}

//출력 Liam

예시 2예시 3은 기능적으로는 동일하다고 불 수 있다. 그러나 내부적으로는 다르게 구현되어 있다. 예시 2에서는 name필드에 Liam이라는 값을 할당하지만, 예시 3에서는 백킹 필드(Backing Filed)라는 비공개 변수에 값이 할당된다.

백킹 필드는 프로퍼티에 의해 읽거나 쓰이는 실제 데이터를 저장하는 데 사용되는 비공개 변수다. 프로퍼티의 내부에서만 사용되며, 클래스 외부에서는 백킹 필드로 직접적으로 접근할 수는 없다.

그렇다면 여러 객체를 생성하는 경우에는 어떻게 될까? 당연하게도 객체마다 프로퍼티를 사용했을 때 각각의 백킹 필드가 할당된다.

부가적으로 설명하자면 객체를 생성하면 컴퓨터 메모리에 객체의 데이터와 메서드를 저장하기 위한 메모리 블록이 할당된다. 이 메모리 블록 내에서 객체와 관련된 모든 멤버에 대한 메모리가 할당되는 것이고, 고유 주소를 갖게 된다. 백킹 필드 객체가 할당받은 메모리 블록 내에 데이터를 저장하는 것이다.

총정리

캡슐화와 프로퍼티는 객체 지향 프로그래밍에서 코드의 가독성, 유지 보수성, 재사용성, 보안 등을 향상하는데 중요한 역할을 한다.

캡슐화로 외부에서 민감한 데이터에 접근하는 것을 막음과 동시에, 개발자가 프로퍼티를 통해 접근할 수 있게 한다.

반응형

+ Recent posts