Humility

아무리 노력해도 최고가 되지 못할 수 있다⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀그럼에도 노력하는자가 가장 겸손한 것 아닌가

공부하는 블로그

유니티/문법

[Unity] 유니티 C#) 생성자

새벽_글쓴이 2024. 11. 26. 22:17
반응형

생성자 ( Constructor )

클래스의 인스턴스가 생성될 때 자동으로 호출되는 특별한 메서드

 

기본특징

생성자의 이름은 반드시 클래스 이름과 동일해야 한다
반환 타입을 지정하지 않는다 ( void도 사용하지 않음 )
접근 제한자는 보통 public을 사용한다

 

생성자 유형들의 사용 사례

1. 기본 생성자 ( 매개변수 없는 생성자 )

public class Player
{
    // 기본 생성자
    public Player()
    {
        // 초기화 코드
    }
}

public class Item
{
    private string itemName;
    private int durability;

    public Item()
    {
        itemName = "기본 아이템";
        durability = 100;
    }
}

 

사용시기

객체 생성 시 기본값으로 초기화가 필요할 때
직렬화/역직렬화가 필요한 클래스 (JSON 변환 등)
컬렉션에서 객체를 생성할 때

 

2. 매개변수가 있는 생성자

public class Player
{
    private string name;
    private int health;

    // 매개변수가 있는 생성자
    public Player(string playerName, int initialHealth)
    {
        name = playerName;
        health = initialHealth;
    }
}

public class Weapon
{
    private string weaponName;
    private int damage;

    public Weapon(string name, int dmg)
    {
        weaponName = name;
        damage = dmg;
    }
}

 

사용시기

객체 생성 시 필수 데이터가 있을 때
객체의 초기 상태를 외부에서 지정해야 할 때

 

3. 생성자 오버로딩

public class Player
{
    private string name;
    private int health;

    // 기본 생성자
    public Player()
    {
        name = "Unknown";
        health = 100;
    }

    // 이름만 받는 생성자
    public Player(string playerName)
    {
        name = playerName;
        health = 100;
    }

    // 이름과 체력을 받는 생성자
    public Player(string playerName, int initialHealth)
    {
        name = playerName;
        health = initialHealth;
    }
}

public class Character
{
    private string name;
    private int health;
    private string job;

    public Character() 
    {
        name = "Unknown";
        health = 100;
        job = "Novice";
    }

    public Character(string characterName)
    {
        name = characterName;
        health = 100;
        job = "Novice";
    }

    public Character(string characterName, string characterJob)
    {
        name = characterName;
        health = 100;
        job = characterJob;
    }
}

 

사용시기

객체 생성 시 다양한 초기화 옵션을 제공할 때
선택적 매개변수가 많을 때

※ 예시
Character char1 = new Character(); // 기본 캐릭터
Character char2 = new Character("Hero"); // 이름만 지정
Character char3 = new Character("Warrior", "Knight"); // 이름과 직업 지정

 

4. this 키워드 사용

public class Player
{
    private string name;
    private int health;
    private int level;

    public Player(string name, int health)
    {
        this.name = name;    // this로 멤버 변수와 매개변수 구분
        this.health = health;
        this.level = 1;
    }
}

public class Enemy
{
    private string name;
    private int level;
    private float health;

    public Enemy(string name, int level, float health)
    {
        this.name = name;
        this.level = level;
        this.health = health;
    }
}

 

사용시기

매개변수 이름이 필드 이름과 동일할 때
현재 인스턴스의 다른 멤버에 접근해야 할 때


※ 예시
Enemy boss = new Enemy("드래곤", 50, 1000f);

 

5. 생성자 체이닝

public class Player
{
    private string name;
    private int health;
    private int level;

    public Player() : this("Unknown", 100)
    {
        // 다른 생성자 호출
    }

    public Player(string name) : this(name, 100)
    {
        // 다른 생성자 호출
    }

    public Player(string name, int health)
    {
        this.name = name;
        this.health = health;
        this.level = 1;
    }
}

public class PlayerCharacter
{
    private string name;
    private int level;
    private string role;

    public PlayerCharacter() : this("Player") { }

    public PlayerCharacter(string name) : this(name, 1) { }

    public PlayerCharacter(string name, int level) : this(name, level, "Warrior") { }

    public PlayerCharacter(string name, int level, string role)
    {
        this.name = name;
        this.level = level;
        this.role = role;
    }
}

 

사용시기

코드 중복을 피하고 싶을 때
여러 생성자가 비슷한 초기화 로직을 공유할 때

※ 예시
PlayerCharacter p1 = new PlayerCharacter(); // 모든 기본값
PlayerCharacter p2 = new PlayerCharacter("Hero"); // 이름만 지정

 

6. 정적 생성자

public class GameManager
{
    private static List<Player> players;

    // 정적 생성자
    static GameManager()
    {
        players = new List<Player>();
        // 다른 정적 초기화 코드
    }
}

public class GameConstants
{
    public static Dictionary<string, float> DamageMultipliers;
    public static List<string> AvailableWeapons;

    static GameConstants()
    {
        DamageMultipliers = new Dictionary<string, float>()
        {
            {"Critical", 2.0f},
            {"Normal", 1.0f},
            {"Weak", 0.5f}
        };

        AvailableWeapons = new List<string>()
        {
            "Sword",
            "Bow",
            "Axe"
        };
    }
}

 

사용시기

정적 데이터를 초기화할 때
프로그램 시작 시 한 번만 실행되어야 하는 초기화 작업
전역적으로 사용되는 설정이나 상수를 초기화할 때

※ 예시
// 정적 생성자는 자동으로 실행되므로 명시적 호출 불필요
float criticalMultiplier = GameConstants.DamageMultipliers["Critical"];

 

주 사용 목적

객체의 초기 상태 설정
필수 데이터 초기화
의존성 주입
리소스 할당

 

생성자 사용 시 주의사항

생성자에서는 가능한 한 간단한 초기화 작업만 수행
시간이 많이 걸리는 작업은 별도 메서드로 분리
예외 처리를 적절히 구현
순환 참조 피하기

 

실제 유니티에서의 활용 예시

public class Enemy : MonoBehaviour
{
    private string enemyName;
    private int damage;
    private float moveSpeed;

    // Awake는 생성자 대신 사용되는 유니티의 초기화 함수
    private void Awake()
    {
        enemyName = "Monster";
        damage = 10;
        moveSpeed = 5f;
    }

    // 생성 후 초기화가 필요한 경우 Initialize 메서드 사용
    public void Initialize(string name, int dmg, float speed)
    {
        enemyName = name;
        damage = dmg;
        moveSpeed = speed;
    }
}
유니티에서 MonoBehaviour를 상속받는 클래스의 경우 일반적인 생성자 대신
Awake나 Start 함수를 사용하여 초기화를 수행함

 


 

생성자 체이닝 이라는 개념을 처음 알았다
그래서 이 부분만 조금 더 공부했다

 

생성자 체이닝이란?

하나의 생성자가 같은 클래스의 다른 생성자를 호출하는 것

체이닝(Chaining)은 '연쇄' 또는 '사슬처럼 이어진다'는 의미입니다.
생성자 체이닝에서는 this() 키워드를 사용해 다른 생성자를 호출하는데,
이것이 마치 사슬처럼 이어져 있다고 해서 '체이닝'이라고 부릅니다.

 

간단한 예시

public class Character
{
    private string name;
    private int level;
    private int health;

    // 1번 생성자
    public Character(string name, int level, int health)
    {
        this.name = name;
        this.level = level;
        this.health = health;
    }

    // 2번 생성자는 1번 생성자를 호출
    public Character(string name) : this(name, 1, 100)
    {
        // name만 받고, level은 1, health는 100으로 기본값 설정
    }

    // 3번 생성자는 2번 생성자를 호출
    public Character() : this("Unknown")
    {
        // 모든 값을 기본값으로 설정
    }
}

 

위 코드를 사용하는 방법

// 3번 -> 2번 -> 1번 생성자 순서로 호출됨
Character char1 = new Character(); // name="Unknown", level=1, health=100

// 2번 -> 1번 생성자 순서로 호출됨
Character char2 = new Character("Hero"); // name="Hero", level=1, health=100

// 1번 생성자만 직접 호출

 

생성자 체이닝이 없다면?

public class Character
{
    private string name;
    private int level;
    private int health;

    // 코드 중복이 발생
    public Character()
    {
        name = "Unknown";  // 중복
        level = 1;        // 중복
        health = 100;     // 중복
    }

    public Character(string name)
    {
        this.name = name;
        level = 1;        // 중복
        health = 100;     // 중복
    }

    public Character(string name, int level, int health)
    {
        this.name = name;
        this.level = level;
        this.health = health;
    }
}

중복되는것을 많이 볼 수 있다

 

생성자 체이닝을 시각적으로 표현한다면?

Character() -> Character(name) -> Character(name, level, health)
3번 생성자   ->   2번 생성자   ->   1번 생성자

 

이를 통해 코드 중복을 줄이고 더 깔끔한 초기화 로직을 만들 수 있을 것 같다

2번째 예시

public class Player
{
    private string name;
    private int health;
    private string weapon;
    private int level;

    // 1. 모든 값을 받는 기본 생성자
    public Player(string name, int health, string weapon, int level)
    {
        this.name = name;
        this.health = health;
        this.weapon = weapon;
        this.level = level;
    }

    // 2. 이름만 받고 나머지는 기본값을 사용하는 생성자
    public Player(string name) : this(name, 100, "기본 검", 1)
    {
        // 본문이 비어있음 - 모든 초기화는 this()를 통해 다른 생성자에서 처리
    }

    // 3. 이름과 무기만 받는 생성자
    public Player(string name, string weapon) : this(name, 100, weapon, 1)
    {
        // 본문이 비어있음
    }
}

 

위 코드 사용 방법

// 각각 다른 생성자를 사용하는 예시
Player player1 = new Player("용사"); // 이름만 지정
Player player2 = new Player("궁수", "활"); // 이름과 무기 지정
Player player3 = new Player("기사", 150, "대검", 5); // 모든 값 지정

 

체이닝을 사용 안한 방법

public class Player
{
    private string name;
    private int health;
    private string weapon;
    private int level;

    // 코드가 중복되는 문제가 발생
    public Player(string name)
    {
        this.name = name;
        this.health = 100;  // 중복
        this.weapon = "기본 검";  // 중복
        this.level = 1;  // 중복
    }

    public Player(string name, string weapon)
    {
        this.name = name;
        this.health = 100;  // 중복
        this.weapon = weapon;
        this.level = 1;  // 중복
    }

    public Player(string name, int health, string weapon, int level)
    {
        this.name = name;
        this.health = health;
        this.weapon = weapon;
        this.level = level;
    }
}

 

조금 더 복잡한 예시

public class Enemy
{
    private string enemyName;
    private int health;
    private int damage;
    private float moveSpeed;
    private EnemyType type;
    private List<string> dropItems;

    // 1. 모든 속성을 초기화하는 주 생성자
    public Enemy(string name, int health, int damage, float speed, EnemyType type, List<string> drops)
    {
        enemyName = name;
        this.health = health;
        this.damage = damage;
        moveSpeed = speed;
        this.type = type;
        dropItems = drops ?? new List<string>();
    }

    // 2. 기본 드롭 아이템을 사용하는 생성자
    public Enemy(string name, int health, int damage, float speed, EnemyType type)
        : this(name, health, damage, speed, type, new List<string>{ "기본 포션" })
    {
    }

    // 3. 기본 이동 속도를 사용하는 생성자
    public Enemy(string name, int health, int damage, EnemyType type)
        : this(name, health, damage, 5.0f, type)
    {
    }

    // 4. 일반 몬스터용 간단한 생성자
    public Enemy(string name)
        : this(name, 100, 10, EnemyType.Normal)
    {
    }
}

public enum EnemyType
{
    Normal,
    Elite,
    Boss
}
반응형