01/08 본캠프#10

guno park·2024년 1월 8일
0

본캠프

목록 보기
11/77

개인과제 Repactoring

마침내 개인과제 다시 건드려보았다.
크게 신경을 많이 쓴 부분은
1. 매개변수
2. if 조건 변경
3. Item Class
4. Json Parse
5. 람다나 델리게이트 사용해보기
이 세가지 인데, 일단 한 것들 찬찬히 살펴보면서 왜 됬는가, 왜 실패했는가 작성해보도록 하겠다.

추가로 이한별 튜터님에게 질문 드려서 알아온 내용도 정리함

매개변수 관련

처음 만들때는 Datasetting()이 없고, Class 안에다 static으로 선언하면 자유롭게 쓸 수 있다는 것을 몰라서 Main에서 시작할 때 Character와 ItemList(상점에 들어가는 것)을 작성했다.
이렇게 했을 때는, 다른 함수에서 그 객체들을 사용하려하면 참조가 필요했기에 매개변수를 받는 것으로 해결했었다

리팩토링을 하면서 객체들을 밖으로 꺼내준 후, 함수에 매개변수로 들어가 있는 것들을 정리하고, 그에 맞게 코드도 다 바꿔주었다.

  • 변경 전
static void sellItem(Character character)
{
    Console.Clear();
    Console.WriteLine("상점 - 아이템 판매");
    Console.WriteLine("필요한 아이템을 얻을 수 있는 상점입니다.");
    Console.WriteLine();
    Console.WriteLine("[보유 골드]");
    Console.WriteLine($"{character.hasgold} G");
    Console.WriteLine();
    Console.WriteLine("[아이템 목록]");
}
  • 변경 후
static void sellItem()
{
    Console.Clear();
    Console.WriteLine("■■■■■■■■");
    ShowHighlithtesText("상점 - 아이템 판매");
    Console.WriteLine("■■■■■■■■");
    Console.WriteLine();
    Console.WriteLine("보유하고 계신 아이템을 판매할 수 있습니다.");
    Console.WriteLine();
    Console.WriteLine("[보유 골드]");
    Console.WriteLine($"{_player.hasgold} G");
    Console.WriteLine();
    
}

if 조건 변경

조건을 몇개씩 걸면서 출력될 문자열들을 정했었지만, 삼항연산자를 사용하면서 간략하게 조절할 수 있었다.

조건1) 캐릭터가 무기를 장착하고 있지 않고, for문으로 선택된 아이템의 타입이 "Weapon" 일 때,
조건2)캐릭터가 무기를 장착하고 있고, for문으로 선택된 아이템의 타입이 "Weapon" 일 때,
조건2-1)장착된 무기의 아이템 코드가 선택된 아이템의 코드와 같을 때
조건2-2)장착된 무기의 아이템 코드가 선택된 아이템의 코드와 다를 때

  • 변경 전
 if (character.equipWeapon == null && character.item[i].type == "Weapon")
 {
     Console.WriteLine($"- {i + 1} {character.item[i].name}     | 공격력 +{character.item[i].attackPower}    | {character.item[i].explain}");
 }
 else if (character.equipWeapon != null && character.item[i].type == "Weapon")
 {
     if (character.equipWeapon.itemcode == character.item[i].itemcode)
         Console.WriteLine($"- {i + 1} [E] {character.item[i].name}     | 공격력 +{character.item[i].attackPower}    | {character.item[i].explain}");
     else if (character.equipWeapon.itemcode != character.item[i].itemcode)
         Console.WriteLine($"- {i + 1} {character.item[i].name}     | 공격력 +{character.item[i].attackPower}    | {character.item[i].explain}");
 }
  • 변경 후
Item isweapon = (_player.equipWeapon == null) ? null : _player.equipWeapon;
if (_playeritems[i].Type == itemType.무기)
{
    Console.WriteLine($"- {i + 1} {(isweapon == null ? "" : ((isweapon.itemcode == _playeritems[i].itemcode) ? "[E]" : ""))} {_playeritems[i].name}     | 공격력 +{_playeritems[i].attackPower}    | {_playeritems[i].explain}");
}

코드를 바꾸면서 무기를 장착하고 있는 지에 대해 미리 3항연산자로 null or Item 을 받아온 후,
출력할 때 다시 3항연산자로 물어본다
null이면 공백, null이 아닐 시 다시 3항연산자로 끼고 있는 아이템과 같은 지 체크한다.

배우기로는 복잡한 조건에는 3항연산자를 쓰는게 아니라고 배웠다.
근데 한번 이렇게라도 써보고 싶었다. 어쩌면 변경 전이 나을지도 모른다.

3항연산자 사용하는 이유가 가독성 높이려고 하는건데, 가독성을 해칠 정도면 그냥 안쓰는게 맞다. 적절하게 사용하자.

Item Class

혼자 만들 때는 Item Class 안에서 생성자를 통해 shoplist를 몽땅 만드는 방식을 택했지만,
개인과제 해설 영상을 보면서 '아이템 리스트를 외부에서 불러와서 쓰면 재밌겠다.' 라는 발상이 떠올라서 고치는 김에 한번 해봤다. 그 내용은 5번에서 작성하도록 한다.

앞서 말했듯 이전에는 이렇게 했다

 class Item
 {
     public string type { get; private set; }
     public string name { get; private set; }
     public string jobconfine { get; private set; }
     public int attackPower { get; private set; }
     public int defensePower { get; private set; }
     public int price { get; private set; }
     public string explain { get; private set; }
     public int itemcode { get; private set; }
     public List<Item> shoplist = new List<Item>();

     public static List<Item> itemsetting(List<Item> shoplist) //배열로 바꾸는게 더 좋을거 같긴한데 손댈게 너무 많음
     {
         //갑옷
         Item trainingArmor = new Item(); //밖에서 자식 클래스로 만들어주고싶은데 어떻게할지 모르겠음. +그렇게 하는거 아닌거같음
         trainingArmor.name = "수련자 갑옷";
         trainingArmor.type = "Armor";
         trainingArmor.jobconfine = "전사";
         trainingArmor.defensePower = 5;
         trainingArmor.explain = "수련에 도움을 주는 갑옷입니다. ";
         trainingArmor.price = 1000;
         trainingArmor.itemcode = 1001;
         shoplist.Add(trainingArmor);
        
         //무기
         Item oldSword = new Item();
         oldSword.name = "낡은 검";
         oldSword.type = "Weapon";
         oldSword.jobconfine = "전사";
         oldSword.attackPower = 2;
         oldSword.explain = "쉽게 볼 수 있는 낡은 검 입니다. ";
         oldSword.price = 600;
         oldSword.itemcode = 2001;
         shoplist.Add(oldSword);
         
     }
 }

바꾼 후에는 아이템을 추가할 때 이렇게 작성하게 되었다.

class Item
{        
    public string name { get; }
    public itemType Type { get; }
    public Job Job { get; }        
    public int attackPower { get; }
    public int defensePower { get;}
    public int price { get; }
    public string explain { get; }
    public int itemcode { get; }        

    public Item(string Name, itemType type, Job job, int Atk,int Def,int Price,string Explain,int itemCode)
    {
        name = Name;
        Type = type;
        Job = job;
        attackPower = Atk;
        defensePower = Def;
        price = Price;
        explain = Explain;
        itemcode = itemCode;
    }    
}

private static void GameDataSetting()
{
    Console.Clear();
    List<Item> shoplist = new List<Item>();
	shoplist.Add(new Item("~~모시깽 모시깽 매개변수들);
    _shoplist = shoplist.OrderBy(p => p.Job).ToList();   
}

작성했을 때 크게 다를건 없다. 근데 이렇게 하면 나중에 '아이템 불러오기' 하기가 편해진다.

  • json 역직렬화 했을 때 값이 안 불러와지던 이유가 프로퍼티에 set이 없어서 그렇다.
  • 프로퍼티도 함수다.
  • 프로퍼티도 적절하게 사용해야 되는데 set;이 없으면 아예 수정이 안된다.
    private set; 이랑 없는 거랑은 다르다. 굳이 따지자면 readonly와 같다
    readonly도 사용할 곳에 적절히 사용하면 좋다. 내가 코드 문법을 얼마나 이해하고 있는 지 티가 난다. (public readonly string explainReadonly = "수정 X";)
  • set이 없으면 초기화할 때나 생성자에서만 값에 접근이 가능하다
  • Json으로 불러오기 할꺼면 생성자는 안만드는게 낫다.
    역직렬화 할 때도 생성자를 호출하기 때문이다. (why? 생성하고 대입해봐야되서)

Json Parse

데이터 저장, 불러오기 등 다양하게 사용되는 Json도 열심히 사용해보고 있다.
전에 사용했던 방법은 Jobject로 만들어서 저장, Jtoken을 불러와서 로드
이런 느낌이였는데
오늘 추가해본 방법은 클래스를 통째로 직렬화 해서 저장하고, 역직렬화해서 불러온 다음 덮어씌우는 것이다.

 static void SaveData(Character character, List<Item> items)
 {            
     string pathchar = CreateCharJson();
     string pathitem = CreateCharItemJson();   

     string savechar = JsonConvert.SerializeObject(character);
     string saveItems = JsonConvert.SerializeObject(items);
     
     File.WriteAllText(pathchar, savechar);
     File.WriteAllText(pathitem, saveItems);
 }

 static Character LoadData(Character character,List<Item> items)
 {
     string path = @"F:\Sparta\Github\Bak_s_homework\Bak-s\ConsoleApp2\save\save.txt";
     string pathitems = @"F:\Sparta\Github\Bak_s_homework\Bak-s\ConsoleApp2\save\saveCharItem.txt";
              
             var obj = File.ReadAllText(pathitems);
             var jsonString = JsonConvert.DeserializeObject<List<Item>>(obj);
             _playeritems = jsonString;

             var cha = File.ReadAllText(path);
             var charstring = JsonConvert.DeserializeObject<Character>(cha);
             _player = charstring;           
         }
     }

이런 느낌으로 작성했는데, 문제가 발생했다.

문제점

  1. Item을 저장했을 때 다른 값들은 잘 나오는데 ATK, DEF만 0으로 나옴.
    구글링 해보기로는 직렬화, 역직렬화 할 때 초기값으로 설정되면서 0으로 나오는 거라 하는데,
    내가 애초에 만들어져있는 아이템 리스트를 저장한 거였는데 왜 저렇게 됬는지 모르겠다.
    다 값이 들어있는 것들이였는데, 한창 찾고 있지만 좋은 정보가 보이지 않는다.

  2. Character를 불러올 때, 클래스 내에서 프로퍼티가 잠겨있으면 Json은 외부 유틸리티라서 접근하지 못한다. 고로 다 풀어줘야 통채로 불러오기 할 수 있다. 그래서 튜터님께 그냥 Private로 해놓고 set하는 메서드를 만들면 안되냐고 하니까, 외부 유틸리티라 그렇게는 안된다고 하셨다.

문제 해결

  1. 위에 적은 내용처럼 set;이 없기 때문에 값을 바꿀 수 없어서 int 초기값 0으로 설정되었다.
    프로퍼티 설정을 이해하고 잘 하자.

  2. 1번과 비슷한 느낌인데, 추가내용은 직렬화, 역직렬화 할 때 클래스, 구조체에 하는 게 기본이라고 하셨다. 그런데 우리가 기본적으로 작성하는 클래스에는 다양한 멤버가 있다.
    Battle()이 있을 수도 있고, TakeDamage()가 있을수도 있다.
    이런 부분들은 데이터 저장, 불러오기를 위해 Json으로 저장할 필요가 없는 부분들로
    데이터 관리를 위해서는 필요한 정보들만 가지고 있는 새로운 클래스나 구조체를 만들어 사용한다고 한다.

아직까지는 Json에 대해 깊게 공부할 필요는 없고, JsonConvert.Serialzie랑 JsonConvert.Deserialzie를 이해하고 어떨 때 사용할 지 알고있으면 된다고 하셨다.
그리고 Jobject나 Jtoken도 여쭤봤는데 그렇게 까지 갈 필요는 없고 지금 쓰기 제일 좋은 건
Newtonsofr.Json.net 이라고 하셨다.

  • Json 쓸 때는 확장자를 .json으로 하는게 제일 좋다.

람다나 델리게이트 사용해보기

델리게이트는 있던 거 뜯어고치는 방식이라 아직은 사용 못 해봤다. 내일 팀과제 만들거나 할 때 조금씩 사용해보자.
람다는 아이템 리스트 정렬할 때 사용했다.

_shoplist.OrderBy(p => p.Job).ToList();

Job순으로 정렬하는 방식이다. 람다를 사용했다고 하기에는 뭐하지만 쓴 건 쓴거니까.

추가내용

  • 델리게이트는 콜백 기능을 할 수 있어서 엄청 많이 쓸 거라고 하셨다.
    다양한 반응에 대해서 클래스에 일일히 다 정의할 수 없기 때문이다. 그런 부분을 델리게이트로 처리한다.
  • 람다 같은 경우에 코드를 축약할 수 있지만, 뒤에서 메모리 사용을 하는 경우도 많기 때문에, 그런 걸 비교해보면 람다를 쓰는 거랑 안 쓰고 작업하는 거랑 별반 차이 없을 수도 있다고 하셨다. 오히려 더 안 좋을 수도. 그래서 람다는 현재 깊게 파보지 않아도 되고, 사용 안하는 것을 권장하셨다. 나는 좋다.

정리

한 건 없는데 이거하면 저기서 터지고, 저거하면 여기서 터지고 구멍난 곳 땜빵치다가 시간 다 갔는데, 아직도 물이 새고 있다. 손짓 발짓 다 해가며 막지만 부족함을 느낀다.
왠지 잘되더라

막힌게 확 내려갔다.

Math.Max

기본 라이브러리로 있는 기능으로 최적화가 잘 되어 있어서 안 쓸 이유가 없는 기능이라고 하셨다.
지난 번에도 다룬거 같지만 수학적으로나 논리적으로 쓸 일이 있을 때 if문 조지지말고 Math 클래스를 이용해보자.

0개의 댓글