Notice
Recent Posts
Recent Comments
Link
«   2026/05   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
Archives
Today
Total
관리 메뉴

I'm FanJae.

[20260506] C# (캡슐화, 상속, 다형성) 본문

Unity/Unity 초격차캠프

[20260506] C# (캡슐화, 상속, 다형성)

FanJae 2026. 5. 6. 17:13

1. 캡슐화 (Encapsulation)

(1) 정의

- 객체 내부의 데이터와 동작을 하나로 묶고, 외부에서 내부 데이터에 직접 접근하지 못하도록 제한하는 것이다.

- private을 쓰는 것이 캡슐화가 아닌, 객체의 상태가 잘못 변경되지 않도록 접근 경로를 통제하는 것이다.

 

(2) 캡슐화가 필요한 이유

public class Player
{
	public int Hp;
}
player.Hp = -100; // 잘못된 값

- 이와 같이 만들면, 외부에서 잘못된 값을 넣는 문제가 발생한다.

 

(3) 올바른 캡슐화의 적용

 
public class Player
{
	private int hp;
	
	public int Hp
	{
		get { return hp; }
		private set
		{
				if (value < 0)
					hp = 0;
				else
					hp = value;
		}
	}
	public void TakeDamge(int damage)
	{
			if(damage < 0) return ;
			Hp -= damage;
	}
}

- 위와 같이 hp 필드를 private으로 숨기고,외부에서는 Hp 프로퍼티를 통해 값만 읽어올 수 있다.

- 값 변경은 TakeDamage()와 같은 내부 메서를 통해서 이루어지게 만든다.

 

(4) 정리

- 캡슐화는 다음과 같은 상황에서 사용할 수 있다.

① 외부에서 객체의 상태를 마음대로 바꾸면 안될때

public class Player
{
    public int Hp;
}
player.Hp = -100;
player.Hp = 999999;

값 변경 전에 검증이 필요할 때

public class Player
{
	private int hp;
	
	public int Hp
	{
		get { return hp; }
		private set // 값 변경 전 검증 과정
		{
				if (value < 0)
					hp = 0;
				else
					hp = value;
		}
	}
}

2. 상속(Inheritance)

(1) 정의

- 상속은 기존 클래스의 공통 기능을 물려받아서 새로운 클래스를 만드는 것을 의미한다.

- 부모 클래스는 공통 기능을 제공하며, 자식 클래스는 그 기능을 그대로 사용하거나 필요에 따라 확장할 수 있다.

(2) 상속이 필요한 이유

- 예를 들어, Warrior, Mage, Archer라는 클래스가 있다고 한다.

public class Warrior
{
    public string Name;

    public void Move()
    {
        Console.WriteLine("이동한다.");
    }
}

public class Mage
{
    public string Name;

    public void Move()
    {
        Console.WriteLine("이동한다.");
    }
}

public class Archer
{
    public string Name;

    public void Move()
    {
        Console.WriteLine("이동한다.");
    }
}

- 이들은 모두 공통적인 필드인 Name 과 Move() 를 가지고 있다.

- 상속을 사용하면 공통 부분을 부모 클래스로 분리할 수 있다.

 

① 상속을 적용한 예시

public class Character
{
	public string Name;
		
	public void Move()
    {
		Console.WriteLine("이동한다.");
    }
}
public class Warrior : Character
{
	public void Attack()
	{
		Console.WriteLine("검으로 공격한다");
	}
}
public class Mage : Character
{
	public void Attack()
	{
		Console.WriteLine("마법으로 공격한다");
	}
}
public class Archer : Character
{
	public void Attack()
	{
		Console.WriteLine("활로 공격한다");
	}
}

- 위와 같이 : 상속할 클래스명 을 적어주는 방식으로 사용할 수 있다.

- 이제 Warrior, Mage, Archer 는 Name, Move()를 그대로 사용할 수 있다.

 

- 정리하면 상속은 다음과 같은 목적으로 사용할 수 있다.

1. 공통 기능을 한 곳에 모으기 위하여
2. 중복 코드를 줄이기 위하여
3. 자식 클래스에서 기능을 확장하기 위하여
4. 다형성을 활용하기 위하여
    - '다형성'에 대한 것은 잠시 뒤에 다룬다

 

(3) 상속의 올바른 사용처

- 상속은 주로 is-a 관계일 때 사용한다.

Character
-> Warrior
-> Mage
-> Archer

Warrior는 Character이다. 와 같은 관계를 표현하기 적당하다.

- 이처럼 여러 클래스가 공통된 성격과 기능을 공유할 때 상속을 사용할 수 있다.

 

(4) 상속 사용 시 주의할 점

- 상속은 부모 클래스와 자식 클래스의 관계가 강하게 묶인다.

- 즉, 부모 클래스의 구조가 바뀌면 자식 클래스에도 영향을 미친다.

- 따라서, 상속 구조가 깊으면 코드의 추적이 어려워진다.

 

(5) 포함과 상속의 차이

① 포함(Composition)

- 포함은 한 클래스가 다른 객체를 자신의 멤버로 가지고 사용하는 관계를 의미한다.

- 포함은 주로 has-a 관계로 표현된다.

public class Weapon { }
public class Player
{
	private Weapon weapon;
}

플레이어는 무기를 가지고 있다. (Player has a Weapon)

- 하지만 플레이어와 무기의 관계를 ‘상속’으로 표현하는 것은 매우 어색하다.

 

② 상속(Inheritance)

- 반면, 상속은 자식 클래스가 부모 클래스의 기능과 속성을 물려받아 확장받는 관계이다.

- 상속은 주로 is-a 관계로 표현된다.

public class Character { }

public class Warrior : Character
{
}

전사는 캐릭터이다. (Warrior is a Character)

- 캐릭터와 전사의 관계는 ‘상속’으로 표현하기에 문제가 없다.


3. 다형성(Polymorphism)

- 해당 항목에 대해 얘기할 때, 오버라이딩(Overriding)의 개념이 등장한다.

- 오버라이딩은 별도 링크로 정리하였다.

- 해당 링크 : C# ( 오버로딩 & 오버라이딩 차이점 )

 

[20260506] C# ( 오버로딩 & 오버라이딩 차이점 )

1. 메서드 오버로딩 (Method Overloading) (1) 정의- 메서드 오버로딩은 동일한 함수의 이름을 사용해서 매개 변수의 타입이나 갯수 또는 순서를 다르게 하여 같은 이름의 메서드를 여러 개를 선언하거

fanjae.tistory.com

 

 

 

(1) 정의

- 같은 타입으로 객체를 다루지만, 실제 객체의 타입에 따라 서로 다른 동작이 실행되게 하는 OOP 개념이다.

- 보통 부모 클래스 타입의 변수로 자식 클래스 객체를 참조하고, 자식 클래스에서 override 한 메서드가 실행되는 방식으로 나타난다.

Character warrior = new Warrior("홍길동");
Character mage = new Mage("법사");

warrior.Attack();
mage.Attack();

- 여기서 변수 타입은 둘 다 Character 다.

- 하지만 실제 객체는 각각 다르다.

warrior 변수 타입 -> Character, warrior 실제 객체 -> Warrior
mage 변수 타입 -> Character, mage 실제 객체 -> Mage

 

(2) 다형성이 필요한 이유

- 다형성은 여러 자식 클래스를 하나의 부모 타입으로 묶어서 관리한다.

- 하지만 실제 동작은 각 자식 클래스에 맡기기 위해 필요하다.

 

① 기존 코드의 수정

public class Character
{
		public string Name;
		
		public void Move()
		{
				Console.WriteLine("이동한다.");
		}
}
public class Warrior : Character
{
		public void Attack()
		{
				Console.WriteLine("검으로 공격한다");
		}
}
public class Mage : Character
{
		public void Attack()
		{
				Console.WriteLine("마법으로 공격한다");
		}
}
public class Archer : Character
{
		public void Attack()
		{
				Console.WriteLine("활로 공격한다");
		}
}

- 위에서 언급되었던 코드에서 Attack()에 대한 오버라이딩을 진행하면 아래와 같다.

 

② Attack()의 오버라이딩 진행

public class Character
{
		public string Name;
		
		public void Move()
		{
				Console.WriteLine("이동한다.");
		}
		public virtual void Attack()
		{
		}
}
public class Warrior : Character
{
		public override void Attack() // 부모 클래스 attack() 오버라이드
		{
				Console.WriteLine("검으로 공격한다");
		}
}
public class Mage : Character
{
		public override void Attack() // 부모 클래스 attack() 오버라이드
		{
				Console.WriteLine("마법으로 공격한다");
		}
}
public class Archer : Character
{
		public override void Attack() // 부모 클래스 attack() 오버라이드
		{
				Console.WriteLine("활로 공격한다");
		}
}

- Warrior, Mage, Archer 가 부모 클래스의 Attack() 을 Override 하였다.

- Override 를 하면, 다음과 같은 형태로 사용할 수 있다.

 

③ 다형성이 적용된 Character

Character[] characters = new Character[]
{
		new Warrior { Name = "WarriorFanJae" },
		new Mage { Name = "MageFanJae" },
		new Thief { Name = "ThiefFanJae"}
};

foreach(Character unit in characters)
{
	unit.Attack(); // 다형성에 의해 각자 재정의된 Attack이 실행된다.
}

- 이렇게 한 번에 관리가 가능해진다.

- 다형성을 사용하지 않으면, 이렇게 된다.

 

④ 다형성이 적용되지 않은 상태 예시

Warrior[] warriors = new Warrior[]
{
	new Warrior { Name = "WarriorFanJae" }
};

Mage[] mages = new Mage[]
{
	new Mage { Name = "MageFanJae" }
};

Archer[] archers = new Archer[]
{
	new Archer { Name = "ArcherFanJae" }
};

foreach(Warrior warrior in warriors)
{
	warrior.Attack();
}
foreach(Mage mage in mages)
{
	mage.Attack();
}
foreach(Archer archer in archers)
{
	archer.Attack();
}

- 각 타입별 배열을 따로 관리하는 형태가 된다.

- 여기서 만약 직업군이 하나 더 추가된다면, 코드가 매우 많이 늘어난다.

 

(3) 다형성의 원리

- 부모 타입 변수에 자식 객체를 담는 것만으로 다형성이 되는 것은 아니다.

Character character = new Warrior();

부모 타입인 Character 변수로 자식 객체인 Warrior를 참조하는 구조다.

- 하지만 실제로 자식 클래스의 동작이 실행되려면, 부모 클래스의 메서드가 virtual로 선언되어 있고, 자식 클래스에 override 하는 과정이 필요하다.

public class Character
{
		public string Name;
		
		public void Move()
		{
				Console.WriteLine("이동한다.");
		}
		public virtual void Attack()
		{
		}
}
public class Warrior : Character
{
		public override void Attack() // 부모 클래스 attack() 오버라이드
		{
				Console.WriteLine("검으로 공격한다");
		}
}
Character character = new Warrior();
character.Attack();

- 이때 변수 타입은 character지만, 실제 객체는 Warrior이다.

- 따라서 런타임에서는 Warrior가 Overriding한 메서드를 호출한다.

- 이처럼 실행 시점에 실제 객체 타입을 기준으로 호출할 메서드를 결정하는 것을 동적 바인딩이라고 한다.

 

(4) 다형성 사용 시 주의해야 할 점

- 다형성은 부모 타입으로 여러 자식 객체를 다룰 수 있게 해준다.

- 하지만, 부모 타입에 정의된 멤버만 직접 사용할 수 있다.

Character character = new Warrior();

character.Attack();
character.UseSwordMasterSkill(); // Warrior의 고유스킬이라고 가정하자.
// 이것은 불가능하다.

- character 변수의 실제 객체는 Warrior이다.

- 하지만, 컴파일러는 이 변수를 Character 타입으로 보기 때문에 Warrior에만 있는 메서드를 바로 호출 할 수 없다. (필요시 형변환은 할 수 있지만, 권장되지는 않는다.)

 

 
 
Comments