2024-04-26
어제 작성한 TIL 과 제출한 개인 프로젝트 과제의 피드백이 왔다.
둘 다 모두 긍정적인 피드백을 주셔서 뿌듯하였다.
TIL 의 피드백 중, 추가적으로 공부해보면 좋은 직렬화 내용을 추천해주셔서
오늘의 TIL 주제로 선정하게 되었다.
| TIL 피드백 | 개인 프로젝트 피드백 |
|---|---|
![]() | ![]() |
따라서, C# 은 단일 상속을 통해 설계의 일관성과 단순성을 유지
동작을 추상화, 클래스에 대한 행동제약을 걸어준다고 생각하자.
public interface IMovable
{
void Move(int x, int y);
void Run();
}
public class Marine : IMovable
{
public void Move(int x,int y)
{
Console.WriteLine($"마린이 ({x},{y}) 위치로 이동");
}
public void Run()
{
Console.WriteLine($"마린이 두 발로 달린다.");
}
}
public class Zergling : IMovable
{
public void Move(int x,int y)
{
Console.WriteLine($"저글링이 ({x},{y}) 위치로 이동");
}
public void Run()
{
Console.WriteLine($"저글링이 네 발로 달린다.");
}
}
static void Main(string[] args)
{
IMovable movable1 = new Player();
IMovable movable2 = new Enemy();
movable1.Move(1,5); // 마린 이동
movable2.Move(1,5); // 저글링 이동
movable1.Run(); // 마린 달리기
movable2.Run(); // 저글링 달리기
}
public interface IPay
{
void Payment(int x);
}
public class Cash : IPay
{
public void Payment(int x) { }
}
public class Card : IPay
{
public void Payment(int x) { }
}
public class Customer
{
public void GetPayment (IPay pay, int money)
{
pay.Payment(money);
}
}
static void Main(string[] args)
{
Customer customer = new Customer();
customer.GetPayment(new Cash(),5);
}
public interface IItemPickable
{
void PickUp();
}
public interface IDroppable
{
void Drop();
}
public class Item : IItemPickable, IDroppable
{
public string Name {get; set;} // 프로퍼티 활용
public void PickUp()
{
Console.WriteLine($"아이템 {Name} 을 주웠습니다."};
}
public void Drop()
{
Console.WriteLine($"아이템 {Name} 을 버렸습니다."};
}
}
public class Player
{
public void InteractWithItem(IItemPickable item)
{
item.PickUp();
}
public void DropItem(IDroppable item)
{
item.Drop();
}
}
static void Main()
{
Player player = new Player();
Item item = new Item{ Name = "Sword" };
player.InteractWithItem(item); // 아이템 주울 수 있음
player.DropItem(item); // 아이템 버릴 수 있음
}
인터페이스는 협업을 위해 만들어진 기능이라 해도 과언이 아니다.
예를 들어, 스타크래프트 게임 개발자가 되었다고 생각해보자.
테란 , 저그 , 프로토스 의 클래스가 존재하고,
각 종족의 유닛은 공격(A), 이동(M), 정지(S), 순찰(H) 등의 공통 기능과 특수 스킬을 가진다.
종족별, 종족 안의 유닛별로 개발자들끼리 기능 구현을 분업하기 위해서는
종족에 대한 클래스 구현이 끝나야만, 유닛을 담당하는 개발자가 작업을 시작할 수 있다.
그러나, Interface 를 활용하여 공통 기능이나 특수 스킬에 대한 추상적인 동작을 정의하면,
유닛을 담당하는 개발자는 기다릴 필요 없이 유닛에 적합하게 인터페이스를 구현하기만 하면 된다.
| 시즈 탱크 | 벌쳐 |
|---|---|
![]() | ![]() |
지난 [TIL] C# - 직렬화 에서는 클래스 (Player) 를 json 으로 변환할 때, Newtonsoft.json패키지를 설치하였다.
닷넷에서 기본적으로 제공해주는 직렬화 기능은 Json 라이브러리(JsonUtility) 를 통해 수행된다.
Player player = new Player();
// 직렬화
string saveData = JsonUtility.ToJson(player);
File.WriteAllText(path + FILE_NAME, saveData);
// 역직렬화
string loadData = File.ReadAllText(path + FILE_NAME);
player = JsonUtility.FromJson<Player>(loadData)
그러나, JsonUtility 는 Dictionary 로 저장한 데이터를 json 으로 저장하지 못한다.
JsonUtility 가 아닌, Newtonsoft.Json 의 JsonConvert 를 사용하면 Dictionary 도 직렬화할 수 있다.
using System.Collections.Generic; // Dictionary 사용을 위해
using Newtonsoft.Json; // JsonConvert 사용을 위해
Dictioanry<string,Player> dic = new Dictionary<stiring,Player>();
// 직렬화
string saveData = JsonConvert.SerializeObject<Dictionary<string,Player>>,Formatting.Indented);
File.WriteAllText(path + FILE_NAME, saveData);
// 역직렬화
string loadData = File.ReadAllText(path + FILE_NAME);
dic = JsonConvert.DeserializeObject<Dictionary<string, Player>>(loadData);
누군가 세이브 파일을 건드려 부적절한 방법으로 게임을 진행할 수도 있다.
json 파일을 수정하여 Gold 를 9999999 G 로 바꾼다거나, 공격력과 방어력 스탯을 조작할 수 있다는 것이다.
이러한 꼼수의 처리 방법을 도입할 필요가 있어보인다.
( 암호화 전 - playerStatData.json 파일 )
위 이미지로 볼 수 있듯이 유저가 json 파일에 접근할 수 있다면, "gold" : 900 을 99999 로 바꿀 수 있다.
Player 의 멤버 변수명과 할당된 값이 보기 쉽게 표시되어 있기 때문이다.
유저가 임의로 json 파일에 접근하여, 할당된 값을 바꾸지 못하게 하기 위해 암호화를 해주자.
암호화 (데이터 -> 암호)
1-0. 객체를 json 형태의 문자열로 직렬화
1-1. json 형태의 문자열을 암호화
1-2. 암호화한 문자열을 파일에 저장하기
복호화 (암호 -> 데이터)
1-0. 암호화된 문자열을 파일로부터 불러오기
1-1. 암호화된 문자열을 복호화
1-2. json 형태의 복호화된 문자열을 객체로 역직렬화
암호화와 복호화의 순서는 역순
ProtectedData 에 빨간줄이 생성되어, Newtonsoft.Json 패키지를 추가한 것처럼 관련 패키지를 추가해주었다.
주의해야 할 점

using System.Collections.Generic; // Dictionary 사용을 위해
using Newtonsoft.Json; // JsonConvert 사용을 위해
using System.Security.Cryptography; // 암호화, 복호화를 위해
public class ProtectedDataTest // Pro
{
public static string Protect(string origin)
{
var PasswordProtect = Convert.ToBase64String(ProtectedData.Protect(Encoding.UTF8.GetBytes(origin), null, DataProtectionScope.CurrentUser));
return PasswordProtect;
}
public static string Unprotect(string origin)
{
var PasswordUnprotect = Encoding.UTF8.GetString(ProtectedData.Unprotect(Convert.FromBase64String(origin), null, DataProtectionScope.CurrentUser));
return PasswordUnprotect;
}
}
// static void SaveData() - 직렬화 -> 암호화 -> 파일 저장
string playerJson = JsonConvert.SerializeObject(player, Formatting.Indented);
playerJson = ProtectedDataTest.Protect(playerJson); // 암호화
File.WriteAllText(playerDataPath, playerJson);
// static void LoadDat() - 파일 읽기 -> 복호화 -> 역직렬화
string playerJson = File.ReadAllText(playerDataPath);
playerJson = ProtectedDataTest.Unprotect(playerJson);
player = JsonConvert.DeserializeObject<Player>(playerJson);
( 암호화 후 - playerStatData.json 파일 )
암호화된 playerStatData.json 파일은 일반 유저가 감히 해석할 수 없을 것이다.
이후, LoadData 함수로 암호화된 json 파일을 불러와 복호화하여 역직렬화 하는 과정도 성공적으로 실행된다.
C# - ProtectedData 클래스
C# - 암호화, 복호화 - 참고 블로그
C# - utf8 암호화 - 참고 블로그
C# - Learn Microsoft 를 참고하여 많은 함수들과 클래스를 구경할 수 있었다.
오늘 추가적으로 직렬화, 역직렬화에 대해 공부하면서, 게임 저장하고 불러오기에 대한 이해도가 더 올라간 것 같다.
팀원들과 개인 프로젝트 코드 리뷰를 통해 간단히 직렬화와 역직렬화에 대해 설명해주며, 설명이 매끄럽지 않았던 부분을 다시 공부해보며 복습하는 시간도 가질 수 있었다.