| 특징 | 설명 | 예시 |
|---|---|---|
| 캡슐화ㅤㅤ | 데이터(속성)와 기능(메서드)을 하나로 묶고, 외부에서 직접 접근하지 못하도록 보호 | 클래스 내부의 private 필드와 이를 제어하는 public 메서드 사용 |
| 상속 | 기존 클래스의 특성을 물려받아 새로운 클래스를 만드는 것 | class Warrior : Character (Character 클래스를 상속받는 Warrior) |
| 다형성 | 같은 이름의 메서드가 상황에 따라 다르게 동작하는 것 | Attack() 메서드가 각 캐릭터마다 다르게 구현될 수 있음 |
| 추상화 | 불필요한 세부 사항은 숨기고 핵심적인 부분만 보여주는 것 | 인터페이스나 추상 클래스를 통해 필요한 기능만 노출 |
게임을 만든다면 클래스는 캐릭터의 설계도
객체는 그 설계도로 만들어진 진짜 캐릭터 (실제 동작을 수행)
| 구성요소 | 설명 | 예시 |
|---|---|---|
| 필드(Field) | 객체가 가지는 변수 데이터 | string name, int hp |
| 메서드(Method) | 객체가 할 수 있는 동작(기능) | Attack(), Move() |
| 생성자(Constructor) | 객체를 만들 때 자동으로 실행되는 메서드 | Player(string name) |
| 속성(Property) | 필드를 간접적으로 접근하는 기능 | public int HP { get; set; } |
public class Player
{
public string name;
public int hp;
public int level;
// 생성자(Constructor)
public Player(string nameInput)
{
name = nameInput; // 이름을 매개변수로 받아 이름 지정
hp = 100; // hp 값 literal 값으로 초기화 (기본 체력 설정)
}
// 메서드(Attack) - 동작
public void Attack()
{
Console.WriteLine($"{name}이(가) 공격!");
}
}
// 사용 예시
public static void Main()
{
Player player1 = new Player("Hero"); // 객체 생성
player1.Attack(); // 객체안에 정의된 메서드 사용
}
>>> Hero이(가) 공격!
클래스와 구조체 모두 코드의 재사용성과 유지보수에 용이한 사용자 정의 데이터 타입
그렇다면 차이점은?
| 항목 | 클래스 (Class) | 구조체 (Struct) |
|---|---|---|
| 형식 | 참조 타입 (Reference Type) | 값 타입 (Value Type) |
| 메모리 저장 위치 | 힙(Heap)에 저장, 변수는 참조를 가짐 | 스택(Stack)에 저장 |
| 상속 | 다른 클래스 상속 가능 | 상속 불가 |
| 생성자 | 사용자 정의 생성자 + 기본 생성자 존재 가능 | 기본 생성자 자동 제공 (사용자 정의 생성자 가능) |
| 성능 | 객체가 커지면 성능 저하 가능 | 크기가 작고 단순할 때 성능 유리 |
| 사용 용도 | 상태 유지가 필요한 복잡한 객체 표현에 적합 | 간단한 데이터 집합 표현에 적합 |
클래스는 동작이 많은 복잡한 객체를 만들 때
구조체는 작고 가벼운 데이터 묶음을 만들 때
public class Player
{
public string name;
public int hp;
public int level;
// 기본 생성자
public Player()
{
name = "Player";
hp = 100;
level = 1;
}
// 오버로딩된 생성자
public Player(string nameInput, int hpInput, int levelInput)
{
name = nameInput;
hp = hpInput;
level = levelInput;
}
}
// 사용 예시
public static void Main()
{
Player player1 = new Player(); // 기본 생성자로 객체 생성
Player player2 = new Player("Hero", 300, 30); // 오버로딩된 생성자로 객체 생성
}
public class Player
{
public string name;
// 생성자
public Player(string name)
{
this.name = name;
Console.WriteLine($"{name} 생성됨!");
}
// 소멸자
~Player()
{
Console.WriteLine($"{name} 소멸됨!");
}
}
private int _hp; // private 필드
public int Hp // public 프로퍼티
{
get { return _hp; } // 읽기 (값 가져오기)
set{ _hp = value; } // 쓰기 (값 설정)
}
public string Name { get; set; }
// 읽기 전용
public int Level { get; private set; }
// 쓰기 전용
public int Score { private get; set; }
public class Player
{
public string Name { get; set; } // 자동 구현 프로퍼티
private int _hp;
public int Hp
{
get { return _hp; }
set
{
_hp = Math.Max(0, value); // 체력 설정 시 0 미만의 값 0으로 고정
}
}
}
// 사용
Player player = new Player();
player.Name = "용사";
player.HP = -10;
Console.WriteLine(player.HP);
>>> 0
클래스 내부의 private 필드에 보다 안전하고 유연하게 데이터 제어를 위해 사용한다.
상속을 사용하는 이유?
->역시나 목적은 코드의 재사용성과 유지보수성의 향상을 위함!
상속과 다형성이 어떤 역할을 하는지는 알겠는데, 뭐 어떻게 굴러가는 것인가?
// 부모 클래스
public class PersonCharacter
{
public string name;
public int hp;
public int attackPower;
// 생성자
public PersonCharacter(string name, int hp, int attackPower)
{
this.name = name;
this.hp = hp;
this.attackPower = attackPower;
}
// 일반 메서드 - 모든 자식 클래스가 이동 가능
public void Move()
{
Console.WriteLine($"{name}이(가) 이동합니다.");
}
// 가상 메서드 - 자식 클래스에서 override 키워드를 통해 다르게 구현 가능
public virtual void Attack()
{
Console.WriteLine($"{name}이(가) 칼을 휘둘러 공격!");
Console.WriteLine($"데미지: {this.attackPower}");
}
}
public class Archer : PersonCharacter
{
public Archer(string name, int hp, int attackPower) : base(name, hp, attackPower) { }
// 공격 메서드 재정의
public override void Attack()
{
Console.WriteLine($"{name}이(가) 화살을 쏴서 공격!");
Console.WriteLine($"데미지: {this.attackPower}");
}
}
public class Orc : PersonCharacter
{
public Orc(string name, int hp, int attackPower) : base(name, hp, attackPower) { }
//공격 메서드 재정의
public override void Attack()
{
Console.WriteLine($"{name}이(가) 소리를 지르며 돌진합니다!");
Console.WriteLine($"데미지: {this.attackPower}");
}
}
위와 같은 부모 클래스와 그것을 상속받은 자식 클래스를 정의하고
public static void Main()
{
// 배열의 타입은 부모 클래스타입이지만
// 자식 객체 참조하여 사용가능
PersonCharacter[] party = new PersonCharacter[]
{
new Archer("Archer Robin", 100, 300),
new Orc("Orc Graw", 500, 100)
};
foreach (var character in party)
{
character.Move(); // 부모 클래스의 공통 메서드 사용
character.Attack(); // 자식 클래스에서 재정의한 메서드 실행 (다형성)
}
}
>>> Archer Robin이(가) 이동합니다.
>>> Archer Robin이(가) 화살을 쏴서 공격!
>>> 데미지: 300
>>> Orc Graw이(가) 이동합니다.
>>> Orc Graw이(가) 소리를 지르며 돌진합니다!
>>> 데미지: 100
생성자를 통해 원하는 값으로 객체를 생성 후, 같은 메서드 이름으로 다른 동작을 수행하게 할 수 있다.
PersonCharater(부모 클래스)를 상속받는 Archer, Orc
virtual / override 키워드를 통해 같은 메서드이지만 다르게 동작하도록 구현
- virtual : 부모 클래스에서 해당 메서드가 자식에서 재정의될 수 있도록 허용
- override : 자식 클래스에서 부모의 virtual 메서드를 새롭게 구현
// 추상 클래스
public abstract class Animal
{
public string name;
public Animal(string name)
{
this.name = name;
}
// 일반 메서드 - 모든 동물에게 공통되는 기능
public void Breathe()
{
Console.WriteLine($"{name}이(가) 숨을 쉽니다.");
}
// 추상 메서드 - 자식이 반드시 구현해야 함
public abstract void MakeSound();
}
// 자식 클래스 - Dog
public class Dog : Animal
{
public Dog(string name) : base(name) { }
// 추상 메서드 구현
public override void MakeSound()
{
Console.WriteLine($"{name}이(가) 멍멍 짖습니다!");
}
}
// 자식 클래스 - Cat
public class Cat : Animal
{
public Cat(string name) : base(name) { }
// 추상 메서드 구현
public override void MakeSound()
{
Console.WriteLine($"{name}이(가) 야옹 울어요!");
}
}
// 사용 예시
public static void Main()
{
Animal dog = new Dog("뽀삐");
Animal cat = new Cat("나비");
dog.Breathe();
dog.MakeSound();
cat.Breathe();
cat.MakeSound();
}
>>>뽀삐이(가) 숨을 쉽니다.
>>>뽀삐이(가) 멍멍 짖습니다!
>>>나비이(가) 숨을 쉽니다.
>>>나비이(가) 야옹 울어요!
공통된 기능을 가지지만, 자식 클래스에게 일부 메서드의 구현을 강제할 때 사용
- abstract class : 추상 클래스 선언 키워드 (직접 인스턴스 생성 불가)
- abstract mathod : 자식 클래스가 반드시 override 해야하는 메서드