오늘은 제네릭과 오버라이딩, 추상클래스를 실습. 기존 코드를 리팩토링 해보는 과정

심화 과제 1

  • 바로 위에서, Trainer라는 클래스를 만들었습니다.
  • 해당 구현 과제를 추상 클래스를 활용하여 리팩토링(다시 만들기)를 진행하려 합니다. 다음 구현사항에 맞추어 Trainer클래스를 완성시켜 주시기 바랍니다.
  • Monster라는 추상 클래스를 새로 하나 만들고, 해당 클래스의 필드로는 정수형 레벨, 열거형 몹타입, 그리고 문자열 이름을 넣어서 만들어 주시기 바랍니다.
  • Monster 추상 클래스에 BaseAttack이라는 abstract 메소드도 추가하여 주시기 바랍니다.

심화 과제 2

  • 이 Monster 클래스를 상속받는 Pikachu, Squirtle, Bulbasaur, Charmander 네 가지의 클래스를 만들어 주시기 바랍니다.
  • Pikachu의 BaseAttack에는 ‘전광석화’, Squirtle는 ‘물총발사’, Bulbasaur는 ‘덩굴채찍’, Charmander는 ‘화염방사’ 를 콘솔에 출력하는 기능을 각각 제작하여 주시기 바랍니다.
  • 각각 몬스터들의 생성자에는 인자값으로 이름과 레벨을 받을 수 있게 하고, 몹타입은 본인 취향 적절한 타입을 기입하여 주시기 바랍니다.

심화 과제 3

  • Trainer 클래스에 6개의 몬스터형을 담을 수 있는 배열이 있을겁니다.
  • 트레이너 생성자에는 자동으로 피카츄를 배열의 첫 번째에 담기게 하는 코드를 작성하여 주시기 바랍니다.

심화 과제 4

  • Trainer 클래스의 메소드로는, 몬스터를 인자값으로 받아 추가하는 기능,
  • 만약 6마리를 초과해서 추가하려 하면, 추가하지 않고 경고문을 내는 메소드를 하나 제작해주시기 바랍니다.
  • 이를 제작하기 위해 추가 필드가 필요하다면 추가 작성하시길 바랍니다.

심화 과제 5

  • 배열 속 보유중인 모든 몹들이 가진 BaseAttack을 전부 실행하는 AllAttack이라는 메소드를 작성하세요.

심화 과제 6

  • 메인에서 Trainer를 하나 만든 후, Trainer 객체에 Charmander 한마리를 이름은 ‘파이리’, 레벨은 5로 하여 추가하는 코드를 작성하세요.
  • Trainer의 AllAttack 메소드를 호출하여 트레이너가 가진 모든 몹의 공격이 콘솔에 출력되게 하세요.

Main.cs

    Console.WriteLine("와 오늘부터 포켓몬 트레이너다, 내 이름은? \t");
    string myName = Console.ReadLine();
    Trainer trainer = new Trainer(myName, 1, "피카츄");
    Monster pyri = new Charmander(5, "파이리");
    trainer.AddMonster(pyri);
    
    Monster[] monsters = new Monster[5]
    {
        new Charmander(5, "파이리"),
        new Squirtle(10, "꼬부기"),
        new Bulbasaur(15, "이상해씨"),
        new Charmander(20, "파이리"),
        new Charmander(20, "파이리"),
    };
    
    // 꽉 찼습니다 프린트
    foreach(Monster mons in monsters)
    {
        trainer.AddMonster(mons);
    }
    
    // 몬스터 상태 프린트
    foreach(Monster mymons in trainer.Monsters)
    {
        Console.WriteLine("---");
        mymons.PrintMonster();
        Console.WriteLine("---");
    }            
    
    trainer.AllAttack();

Monsters.cs

public abstract class Monster
{
    protected int _level;
    protected string _name;
    protected MobType _type;
    protected string _skill;

    public Monster(int level, string name)
    {
        _level = level;
        _name = name;
    }

    public abstract void BaseAttack();
    public abstract void PrintMonster();
}

public class Pikachu : Monster
{
    public Pikachu(int level, string name) : base(level, name)
    {
        _type = MobType.Electric;
        _skill = "전광석화";
    }
    
    public override void BaseAttack()
    {
        Console.WriteLine($"{_skill}");
    }
    
    public override void PrintMonster()
    {
        Console.WriteLine($"유형 : {_type}");
        Console.WriteLine($"레벨 : {_level}");
        Console.WriteLine($"이름 : {_name}");
    }        
}

public class Squirtle : Monster
{
    public Squirtle(int level, string name) : base(level, name)
    {
        _type = MobType.Water;
        _skill = "물총발사";
    }
    
    public override void BaseAttack()
    {
        Console.WriteLine($"{_skill}");
    }
    
    public override void PrintMonster()
    {
        Console.WriteLine($"유형 : {_type}");
        Console.WriteLine($"레벨 : {_level}");
        Console.WriteLine($"이름 : {_name}");
    }        
}

public class Bulbasaur : Monster
{
    public Bulbasaur(int level, string name) : base(level, name)
    {
        _type = MobType.Grass;
        _skill = "덩굴채찍";
    }
    
    public override void BaseAttack()
    {
        Console.WriteLine($"{_skill}");
    }
    
    public override void PrintMonster()
    {
        Console.WriteLine($"유형 : {_type}");
        Console.WriteLine($"레벨 : {_level}");
        Console.WriteLine($"이름 : {_name}");
    }        
}

public class Charmander : Monster
{
    public Charmander(int level, string name) : base(level, name)
    {
        _type = MobType.Fire;
        _skill = "화염방사";
    }

    public override void BaseAttack()
    {
        Console.WriteLine($"{_skill}");
    }
    
    public override void PrintMonster()
    {
        Console.WriteLine($"유형 : {_type}");
        Console.WriteLine($"레벨 : {_level}");
        Console.WriteLine($"이름 : {_name}");
    }        
}

아래는 제네릭과 관련하여 추가적인 정리

C#은 모든것이 Object 로 이뤄져 있다.

제네릭의 장점?

  • 역시 자바와 비슷.
    • object 형식으로 다양한 형식을 다루면 성능 저하를 유발할 수 있고 위험하다.
    • 수많은 다양한 형을 다루기 위해서는 좀 더 안전한 제네릭을 사용한다.
    • 제네릭은 들어오는 타입에 제약도 걸 수 있고, 안전성도 보장된다.
  • 추가적인 제네릭의 장점?
    • 박싱과 언박싱을 할 필요가 없어진다.
// using generic
List<int> list = new List<int>();
list.Add(1);
int a = list[0];

// using non-generic collection
ArrayList list2 = new ArrayList();
int num = 1;
// ArrayList 에 넣는 순간 아래 처럼 박싱이 일어남
//object obj = (object)num;
list2.Add(num);
string unboxNum = (int)list2[0];

collection 라이브러리 살짝 미리보기

List.cs

public void Add(T item)
{
    _version++;
    T[] array = _items;
    int size = _size;
    if ((uint)size < (uint)array.Length)
    {
        _size = size + 1;
        array[size] = item;
    }
    else
    {
        AddWithResize(item);
    }
}

ArrayList.cs

public virtual int Add(Object value)
{
    if (_size == _items.Length) EnsureCapacity(_size + 1);
    _items[_size] = value;
    _version++;
    return _size++;
}

List.cs 에서는 컴파일 타임에 T 에 int 를 넣어, 자료형을 정확히 만들어서 구현함.
ArrayList에서는 모든것이 Object로 넣고 빠지기 때문에, 무조건 박싱이 일어난다~

  • 선생님께 추가로 물어본 질문..
    • 그래서 C#은 ArrayList가 왜 남아있는가..? 모든 면에서 List가 좋아보이는데?
    • 하위 호환성때문에, 예전 제네릭 도입되기 이전코드들에서 (System.Collections)는 ArrayList 를 사용했었으나, 그 이후는 쓸 이유가 없음.
    • List 형을 무조건 사용하는걸로~