객체지향 프로그래밍

클래스를 이야기하려면 먼저 객체지햐 프로그래밍(Object Oriented Programming) 을 알아야한다.

객체지향 프로그래밍은 OOP 라고도 하는데, 코드 내 모든 것을 객체(Object) 로 표현하려는 프로그래밍 패러다임을 뜻한다.

여기서 객체는 세상의 모든 것을 지칭하는 단어로 사람, 연필, 자동차 등 모든 것을 객체로 볼 수 있다. 객체의 주요 특징으로 두 가지를 뽑아볼 수 있다. 바로 속성과 기능이다.

사람이라는 객체의 속성으로는 피부색, 키, 몸무게 등을 뽑을 수 있고, 기능으로는 걷기, 뛰기, 보기, 듣기 등을 뽑을 수 있다.

이러한 객체의 속성과 기능을 C# 코드를 통해 표현하자면 속성은 데이터로, 기능은 메소드로 표현할 수 있다. 정리하자면 객체는 데이터와 메소드로 이루어진다.


클래스

클래스는 객체를 만들기 위한 청사진이다. 클래스가 자동차 설계도라면, 객체는 생산된 실제 자동차라고 할 수 있다.

클래스는 객체가 가지게 될 속성과 기능을 정의하지만 실체를 가지지는 않는다. 클래스를 이용해 만든 객체가 실체를 가진다. 동일 클래스로 객체 3개를 만들면, 이 객체는 서로가 구분되는 고유한 실체를 가지며 저마다 메모리 공간을 차지한다.

string a = "123";
string b = "Hello";

위 코드에서 string 은 C#에서 이미 정의된 문자열을 다루는 클래스이고, ab 는 객체이다.

다시 말해, string 은 문자열을 담는 객체를 위한 청사진이고, ab 는 실제로 데이터를 담을 수 있는 실제 객체이다. ab 를 일컬어 string 의 실체(Instance) 라고 한다.


클래스의 선언

클래스는 class 키워드를 이용해서 선언한다.

class 클래스_이름
{
	// 데이터와 메소드
}

클래스 안에 선언된 변수들을 일컬어 필드(Field) 라고 하며, 필드와 메소드를 비롯하여 프로퍼티, 이벤트 등 클래스 내에 선언된 요소들을 일컬어 멤버(Member) 라고 한다.

객체의 생성

클래스는 청사진일뿐, 실체(인스턴스)가 아니다. 따라서 인스턴스를 만들어야 한다.

new 키워드와 클래스의 생성자를 이용해 클래스의 객체를 생성할 수 있다.

클래스_이름 객체이름 = new 클래스_생성자;
using System;

namespace Class
{
    class Cat
    {
        public string Name;
        public string Color;

        public void Meow()
        {
            Console.WriteLine($"{Name} : Meow");
        }
    }

    class MainApp
    {
        static void Main(string[] args)
        {
            Cat kitty = new Cat();
            kitty.Color = "White";
            kitty.Name = "Kitty";
            kitty.Meow();
            Console.WriteLine($"{kitty.Name} : {kitty.Color}");

            Cat nero = new Cat();
            nero.Color = "Black";
            nero.Name = "Nero";
            nero.Meow();
            Console.WriteLine($"{nero.Name} : {nero.Color}");
        }
    }
}
Kitty : Meow
Kitty : White
Nero : Meow
Nero : Black

생성자

객체가 생성될 때는 생성자(Constructor) 가 호출된다.

생성자는 클래스의 이름과 같고 반환 형식이 없다.

객체를 생성하는 시점에 객체의 필드를 원하는 값으로 초기화 할때 생성자를 이용할 수 있다.

class 클래스_이름
{
	한정자 클래스_이름(매개변수_목록) // 생성자
	{
		//
	}
	
	// 필드
	// 메소드
}
using System;

namespace Constructor
{
    class Cat
    {
        public Cat(string name, string color)
        {
            Name = name; 
            Color = color; 
        }

        public void Meow()
        {
            Console.WriteLine($"{Name} : Meow");
        }

        public string Name;
        public string Color;
    }

    class MainApp
    {
        static void Main(string[] args)
        {
            Cat kitty = new Cat("Kitty", "White");
            kitty.Meow();
            Console.WriteLine($"{kitty.Name} : {kitty.Color}");
        }
    }
}
Kitty : Meow
Kitty : White

기본 생성자

클래스를 선언할 때 명시적으로 구현하지 않아도 컴파일러는 생성자를 만들어 준다. 이렇게 컴파일러가 자동으로 만들어주는 생성자를 기본 생성자라고 한다.

만약 프로그래머가 생성자를 하나라도 직접 정의하면 C# 컴파일러는 기본 생성자를 만들지 않는다.

using System;

namespace Constructor
{
    class Cat
    {
        public void Meow()
        {
            Console.WriteLine("Meow");
        }
    }

    class MainApp
    {
        static void Main(string[] args)
        {
            Cat kitty = new Cat(); // 기본 생성자 호출
            kitty.Meow();
        }
    }
}
Meow

생성자 오버로딩

생성자도 메소드와 마찬가지로 오버로딩이 가능하다. 따라서 다양한 종류의 생성자를 준비해놓을 수 있다.

using System;

namespace Constructor
{
    class Cat
    {
        public Cat()
        {
            Name = "";
            Color = "";
        }

        public Cat(string name, string color)
        {
            Name = name; 
            Color = color; 
        }

        public string Name;
        public string Color;
    }

    class MainApp
    {
        static void Main(string[] args)
        {
            Cat kitty = new Cat();
            kitty.Name = "Kitty";
            kitty.Color = "White";
            Console.WriteLine($"{kitty.Name} : {kitty.Color}");

            Cat nero = new Cat("Nero", "Black");
            Console.WriteLine($"{nero.Name} : {nero.Color}");
        }
    }
}

Kitty : White
Nero : Black

종료자

객체가 소멸될 때는 종료자(Finalizer) 가 호출된다.

종료자는 클래스 이름 앞에 ~ 를 붙인 꼴이고, 매개변수 및 반환값이 없고, 한정자도 사용하지 않는다. 또한, 오버로딩도 불가능하며, 직접 호출할 수도 없다.

종료자는 CLR 의 가비지 컬렉터가 객체가 소멸되는 시점을 판단해서 종료자를 호출해 준다.

class 클래스_이름
{
	~클래스_이름()
	{
		//
	}
	
	// 필드
	// 메소드
}

종료자를 소개하기는 했지만, 다음과 같은 이유로 가급적 종료자를 사용하지 않는 것이 좋다.

using System;

namespace Finalizer
{
    class Cat
    {
        public Cat()
        {
            Name = "";
            Color = "";
        }

        public Cat(string name, string color)
        {
            Name = name; 
            Color = color; 
        }

        ~Cat()
        {
            Console.WriteLine($"{Name} : 소멸");
        }

        public string Name;
        public string Color;
    }

    class MainApp
    {
        static void Main(string[] args)
        {
            Cat kitty = new Cat("Kitty", "White");
            Console.WriteLine($"{kitty.Name} : {kitty.Color}");

            Cat nero = new Cat("Nero", "Black");
            Console.WriteLine($"{nero.Name} : {nero.Color}");
        }
    }
}
Kitty : White
Nero : Black
Kitty : 소멸
Nero : 소멸

위 프로그램의 출력 결과 중 마지막 두 줄은 실행할 때마다 달라질 수 있다. 객체의 소멸을 담당하는 가비지 컬렉터가 언제 동작할지는 예상할 수 없기 때문이다.