ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 내일배움캠프 8일차 TIL - 클래스, 객체, 상속, 개인과제
    TIL/C# 2024. 4. 24. 20:27

     

    [학습목표]

    C# 강의 3주차를 수강하여 클래스와 객체, 상속과 다형성, 고급 문법 등을 학습한다. 그리고 개인 과제 완성도를 높인다.

    [학습내용]

    C# 강의

    오늘은 어제에 이어 C# 강의 3주차를 수강했다. 3주차 마무리 문제도 2문항이었다. 스네이크 게임 만들기와 블랙잭 게임 만들기였다. 천천히 하면 진행할 수 있는 난이도로 보였으나, 개인 과제가 더 급하기 때문에 일단 제쳐놓기로 했다.

     

    학습 1

    우선 새롭게 알게 된 것이 있다. '프로퍼티(property)'다. 다른 언어에서도 캡슐화를 위해 비슷한 기능을 구현하긴 했지만, 이렇게 용어로 만들거나 따로 형태가 있는 건 처음 보는 거 같다.

    프로퍼티는 클래스 멤버로서, 객체의 필드 값을 읽거나 설정하는데 사용되는 접근자(accessor) 메서드의 조합이다. 객체의 필드에 직접 접근하지 않고, 간접적으로 값을 설정하거나 읽을 수 있도록 한다.

     

    그러니까 다른 언어에서는 private으로 선언된 name 이라는 문자열 변수값을 건드리기 위해 public으로 setName()이나 getName() 메서드를 만들던 것을 프로퍼티로 대신한다는 것이다. 그래서 직접 name을 건드리지 않고 데이터를 조작할 수 있게 한다는 것이다.

    앞으로 잘 활용하려면 계속 사용해서 익숙해져야 할 거 같다.

     

    학습 2

    C# 강의에서 눈여겨본 건 또 있다. 바로 가상(virtual) 메서드와 추상(abstract) 메서드다. 이건 다른 언어에도 있었다. 그래도 사용해본 적이 없어서 한 번 더 짚고 넘어가면 좋겠단 생각이 들었다.

    가상 메서드는 기본적으로 부모 클래스에서 정의되고 자식 클래스에서 재정의할 수 있는 메서드다. virtual 키워드를 사용하여 선언되며, 자식 클래스에서 필요에 따라 재정의될 수 있다.
    추상 클래스는 직접적으로 인스턴스를 생성할 수 없는 클래스로, 주로 상속을 위한 베이스 클래스로 사용된다. abstract 키워드를 사용하여 선언되고, 추상 메서드를 포함할 수 있다. 추상 메서드는 구현부가 없는 메서드로 자식 클래스에서 반드시 구현되어야 한다.

     

    둘은 비슷한 것 같으면서도 다르다. 제일 큰 차이는 자식 클래스에서 재정의하는 게 필수냐는 것이다. 추상 메서드는 그렇지만 가상 메서드는 아니다. 또한 추상 메서드는 자식 클래스에서만 구현할 수 있지만, 가상 메서드는 부모 클래스에서도 가능하다. 그것이 재정의 될 수도 있지만 말이다.

     

    학습 3

    이번 주차 C# 강의에서 짚고 갈 마지막 내용은 out과 ref 키워드다.

    out, ref 키워드는 메서드에서 매개변수를 전달할 때 사용한다. out 키워드는 메서드에서 반환값을 매개변수로 전달하는 경우에 사용한다. ref 키워드는 메서드에서 매개변수를 수정하여 원래의 값에 영향을 주는 경우에 사용한다.

     

    다른 언어에서는 포인터 등을 활용하여 Call by referance를 구현했지만, C#에서는 이 키워드들을 사용하면 될 거 같다. 그런데 out과 ref, 둘은 얼핏 비슷해 보인다. 그런데 느낌이 살짝 다르다. out은 매개변수에서 변수로 값을 출력하는 느낌이다. 반면 ref는 매개변수로 변수를 잠깐 데려다가 쓰는 느낌이다. 그래서 out은 반드시 메서드 내에서 값을 할당해줘야 한다고 한다.

     

    여기까지가 C# 강의에서 새롭게 배우거나 한 번 짚고 넘어갈 내용들이었다.

     

    개인 과제

    오후에는 개인 과제를 진행했다. 어제 핵심 기능은 모두 구현했다. 하지만 우선적으로 뜯어 고쳐야 할 점이 남아있었다.

    1. 하나의 .cs 파일에 모든 것들을 때려넣었다.

    2. 화면 구성을 모두 메서드로 구현했다.

    그래서 이 두 가지 문제를 한번에 잡기 위해 화면 관리자(이하 씬 매니저)를 만들고, 그 다음 각 화면들을 모두 클래스로 구성하고 객체를 생성하여 출력하기로 했다.

    화면을 나눠준 모습과 씬 매니저

     

    ! 문제 발생 1

    화면의 수가 늘어날수록, 화면들을 호출하는(객체를 생성하는) 일이 복잡해졌다. 예를 들면 새로운 화면을 만들 때마다 그 화면을 호출하는 코드를 작성해야 하는 상황이 있다. 그래서 일일이 새롭게 작성하지 않고 하나의 코드만으로 원하는 화면을 효율적으로 호출할 수 있는 방법이 필요했다.

     

    시도

    구글링을 하다가 문자열을 이용하여 메서드를 동적으로 호출하는 방법을 찾아냈다. 그래서 이걸 클래스에 적용시켜보면 어떨까 하는 생각이 들었다.

    그런데 처음에는 생각을 잘못하여 클래스를 호출하는 방법을 계속 찾고 있었다. 클래스는 실체가 없는데 어떻게 호출할 것인가.. 그래서 결국 인스턴스를 만들어야 한다는 것을 알게 되었고, 문자열을 이용하여 해당하는 이름의 인스턴스를 만들어내기로 했다.

     

    해결

    sceneName을 문자열로 받아서 타입을 찾고, 그 타입으로 인스턴스를 생성해줬다. 

            public void ChangeScene(string sceneName)
            {
                Type sceneType = Type.GetType("SpartaDungeonGame." + sceneName);
                object obj = Activator.CreateInstance(sceneType);
            }

     

    여기서 또 포인트는 namespace인 "SpartaDungeonGame"을 붙여줘야 했다는 것이다. 그래야 찾을 수 있나보다.

     

    참고자료

    나중에 쓸 일이 있을 거 같아서 메서드를 동적으로 호출하는 방법을 남겨놓아야겠다.

    nowScene = sceneName;
    Type t = this.GetType();
    MethodInfo m = t.GetMethod(sceneName);
    if (m != null)
    {
        m.Invoke(this, new object[] { });
    }

     

     

    ! 문제 발생 2

    그리고 이후로 진행하다보니 새로운 문제가 생겼다. 한 화면에서 여러 행동을 하기 위해서 콘솔의 이전 내용을 지우고 화면을 반복적으로 다시 불러와야 했다. 그랬더니 추가로 작성한 메시지가 같이 지워져버리는 것이었다. 메시지를 추가로 작성하는 일은 꼭 필요했다. 입력이 잘못되었다든가, 골드가 부족하다든가 하는 상황이다.

     

    시도

    처음에는 반복문을 잘 굴려보려고 했다. 그런데 화면 전체를 반복시키면 어차피 새로운 메시지는 지워지고, 메시지가 생기는 부분만 반복시키면 여러 기능을 하지 못하는 난관에 봉착했다. 어떻게든 해보려고 재귀함수까지 사용해봤는데 정답이 아니었다.

     

    해결

    그래서 결국 새로운 아이디어를 냈다. 메시지를 출력하는 기능을 가진 인스턴스를 만들면 어떨까? 그리고 static으로 만들어서 멤버 변수에 메시지를 저장하여서 화면이 갱신되어도 저장한 메시지를 출력하게 한다면 해결할 수 있을 것 같았다. 그래서 새롭게 SystemMessage 클래스를 작성하였다.

    namespace SpartaDungeonGame
    {
        internal class SystemMessage
        {
            public string message = "";
            public SystemMessage() { }
    
            public void SetMessage(string message)
            {
                this.message = message + "\n";
            }
    
            public void PrintMessage()
            {
                int currentCursor = Console.GetCursorPosition().Top;
                Console.SetCursorPosition(0, currentCursor + 4);
                Console.Write(message);
                Console.SetCursorPosition(0, currentCursor);
                message = "";
            }
        }
    }

     

    그리고 메시지를 화면 최하단에 출력하여 가시성도 잡았다. 이를 위해서 Console.SetCursorPosition()을 이용했다. 

     

    학습

    다행히 콘솔에서 커서를 바꿀 수 있는 기능이 있었다. 관련 메서드는 이렇다.

    int t = Console.GetCursorPosition().top;
    int l = Console.GetCursorPosition().left;
    Console.SetCursorPosition(int left, int top);

     

     

    이렇게까지가 오늘 개인 과제를 진행하면서 발생한 문제들이었다. 그 이후 기능 구현면에서는 딱히 어려운 건 없었다.

    그래서 지금까지 구현한 내용은 이렇다.

     

    필수기능

    • 게임 시작 화면 구성
    • 스테이터스 보기 기능
    • 인벤토리 기능
    • 장착 관리 기능
    • 상점 기능
    • 아이템 구매 기능

    추가기능

    • 아이템 정보 클래스화
    • 아이템 정보 배열(리스트)로 관리
    • 아이템 판매 기능
    • 장착 관리 기능 개선

    예외 처리와 버그 수정까지 했더니 생각보다 많은 기능을 구현하지 못했다. 하지만 내일까지 시간을 더 투자한다면 더 많은 기능을 구현할 수 있을 거 같다.

    [결과물]

    깃허브 리포지터리

     

    GitHub - JNUSYJ/SpartaDungeonGame

    Contribute to JNUSYJ/SpartaDungeonGame development by creating an account on GitHub.

    github.com

    [회고]

    오늘은 개인 과제에 많은 시간을 썼다. 내일도 오늘처럼 우선 C# 강의 4주차는 미뤄두고, 개인 과제 진행을 해야할 거 같다. 추가 기능 순서대로 구현하고, 여유가 된다면 namespace를 사용해서 코드 개선 작업도 한 번 해야겠다.