본문 바로가기

설계

AI와 협업 프로젝트: 설계 1/2

https://cs-essay.tistory.com/107    AI와 협업 프로젝트: 회의록 1/2

https://cs-essay.tistory.com/108    AI와 협업 프로젝트: 회의록 2/2

 

저도 완전히 이해합니다! 😅

Python동적 타이핑에 의존하고 들여쓰기가 중요한 언어여서, 특히 C/C++/Java에 익숙한 분들에게는 좀 혼란스러울 수 있습니다. 게다가 중괄호 없이 코드를 작성하는 스타일은 정말... 고수들이 아니면 상당히 피곤할 수 있죠. 😬

그래서 제가 제안할 건, 설계 단계에서 필요한 개념C#, C, Java 스타일로 철저히 다루고, 구현 후 Python 포팅이나 다른 언어 포팅이 가능하도록 유지하는 방식입니다.

예시로 다시 GameCore 설계를 다뤄볼게요.

이 설계가 언어 간 차이를 넘어서는 공통된 개념을 이해할 수 있도록 돕겠습니다.


GameCore 설계 - C / C++ / Java / C# 공통 구조

1. GameCore 클래스

C / C++ 스타일

#include <iostream>   // 입출력을 위한 헤더 파일
#include <unordered_map>   // 해시 맵을 사용하기 위한 헤더 파일
#include <string>   // 문자열을 사용하기 위한 헤더 파일

// PlayerData 클래스는 플레이어의 상태를 나타냄
class PlayerData {
public:
    std::string name;    // 플레이어 이름
    int level;           // 플레이어 레벨
    int score;           // 플레이어 점수
};

// GameModule 클래스는 게임에서 실행할 모듈들의 공통 인터페이스 역할을 함
class GameModule {
public:
    // 각 모듈은 반드시 이 함수를 구현해야 함. 'command'는 실행할 명령어, 'player'는 해당 플레이어의 상태
    virtual std::string run(const std::string& command, PlayerData& player) = 0; 
};

// GameCore 클래스는 전체 게임의 핵심 구조를 담당하는 클래스
class GameCore {
private:
    // 명령어 접두어와 모듈을 연결하는 맵 (commandPrefix -> GameModule*)
    std::unordered_map<std::string, GameModule*> modules;

    // 플레이어의 ID와 해당 플레이어의 데이터를 연결하는 맵 (playerId -> PlayerData)
    std::unordered_map<std::string, PlayerData> playerData;

public:
    // 모듈을 등록하는 함수. 명령어 접두어와 해당 모듈을 연결함.
    // 예: "낚시"라는 명령어에 대해 낚시 모듈을 등록
    void registerModule(const std::string& commandPrefix, GameModule* module) {
        modules[commandPrefix] = module;   // commandPrefix에 해당하는 명령어를 처리할 모듈을 등록
    }

    // 명령어를 처리하는 함수. 
    // 플레이어의 ID를 통해 그에 맞는 PlayerData를 찾고, 모듈을 실행해 결과를 반환.
    std::string handleCommand(const std::string& command, const std::string& playerId) {
        // 명령어에서 접두어 부분을 추출
        std::string prefix = command.substr(0, command.find(' '));  // 공백 이전까지의 부분을 접두어로 인식
        
        // 접두어에 해당하는 모듈이 등록되어 있으면 해당 모듈을 실행
        if (modules.find(prefix) != modules.end()) {
            return modules[prefix]->run(command, playerData[playerId]);  // 명령어를 실행하고 결과 반환
        }
        
        // 등록되지 않은 명령어인 경우 에러 메시지 반환
        return "Unknown command.";
    }
};

Java 스타일

import java.util.HashMap;   // 해시 맵을 사용하기 위한 import
import java.util.Map;       // Map 인터페이스 사용을 위한 import

// PlayerData 클래스는 각 플레이어의 정보를 저장
class PlayerData {
    String name;    // 플레이어 이름
    int level;      // 플레이어 레벨
    int score;      // 플레이어 점수
}

// GameModule 인터페이스는 각 게임 모듈이 구현해야 하는 메서드를 정의
interface GameModule {
    // 각 모듈은 'command'를 실행하여 플레이어 데이터를 갱신하거나 작업을 수행해야 함
    String run(String command, PlayerData player);  
}

// GameCore 클래스는 전체 게임의 핵심 기능을 관리
public class GameCore {
    // 명령어 접두어와 해당 명령어를 처리할 모듈을 연결하는 맵
    private Map<String, GameModule> modules = new HashMap<>();
    
    // 플레이어 ID와 해당 플레이어 데이터를 연결하는 맵
    private Map<String, PlayerData> playerData = new HashMap<>();

    // 모듈을 등록하는 메서드
    // 예를 들어, "낚시"라는 명령어에 대해 낚시 모듈을 연결
    public void registerModule(String commandPrefix, GameModule module) {
        // 명령어 접두어와 해당 모듈을 맵에 등록
        modules.put(commandPrefix, module);  
    }

    // 명령어를 처리하는 메서드
    // 플레이어 ID를 통해 해당 플레이어의 정보를 찾고, 적절한 모듈을 실행
    public String handleCommand(String command, String playerId) {
        // 명령어에서 접두어(첫 번째 단어)만 추출
        String prefix = command.split(" ")[0];  
        
        // 해당 접두어에 맞는 모듈이 등록되어 있으면 모듈을 실행하고 결과 반환
        if (modules.containsKey(prefix)) {
            return modules.get(prefix).run(command, playerData.get(playerId));
        }
        
        // 등록되지 않은 명령어는 "Unknown command."를 반환
        return "Unknown command.";  
    }
}

C# 스타일

using System;  // 시스템 관련 클래스 사용을 위한 네임스페이스
using System.Collections.Generic;  // 컬렉션(예: Dictionary)을 사용하기 위한 네임스페이스

// PlayerData 클래스는 각 플레이어의 데이터를 저장
public class PlayerData {
    public string Name { get; set; }  // 플레이어 이름
    public int Level { get; set; }    // 플레이어 레벨
    public int Score { get; set; }    // 플레이어 점수
}

// IGameModule 인터페이스는 게임 모듈들이 구현해야 할 공통 메서드를 정의
public interface IGameModule {
    // 각 모듈은 'command'를 실행하여 'player' 데이터에 작업을 수행해야 함
    string Run(string command, PlayerData player);  
}

// GameCore 클래스는 전체 게임의 핵심 기능을 관리
public class GameCore {
    // 명령어 접두어와 해당 명령어를 처리할 모듈을 연결하는 딕셔너리
    private Dictionary<string, IGameModule> modules = new Dictionary<string, IGameModule>();
    
    // 플레이어 ID와 해당 플레이어 데이터를 연결하는 딕셔너리
    private Dictionary<string, PlayerData> playerData = new Dictionary<string, PlayerData>();

    // 모듈을 등록하는 메서드
    // 예를 들어, "낚시"라는 명령어에 대해 낚시 모듈을 등록
    public void RegisterModule(string commandPrefix, IGameModule module) {
        // 명령어 접두어와 해당 모듈을 딕셔너리에 등록
        modules[commandPrefix] = module;  
    }

    // 명령어를 처리하는 메서드
    // 플레이어 ID를 통해 해당 플레이어의 데이터를 찾고, 적절한 모듈을 실행
    public string HandleCommand(string command, string playerId) {
        // 명령어에서 접두어(첫 번째 단어)만 추출
        string prefix = command.Split(' ')[0];  
        
        // 해당 접두어에 맞는 모듈이 등록되어 있으면 모듈을 실행하고 결과 반환
        if (modules.ContainsKey(prefix)) {
            return modules[prefix].Run(command, playerData[playerId]);
        }
        
        // 등록되지 않은 명령어는 "Unknown command."를 반환
        return "Unknown command.";  
    }
}

2. GameModule 인터페이스 (모듈화)

C / C++ 스타일

  • C++에서는 pure virtual function을 사용하여 인터페이스처럼 동작시킬 수 있습니다.
// GameModule 클래스는 모든 게임 모듈이 구현해야 하는 기본 인터페이스 역할을 합니다.
class GameModule {
public:
    // run 메서드는 각 모듈이 실행하는 명령어에 대한 처리를 담당합니다.
    // - command: 사용자가 입력한 명령어 문자열
    // - player: 명령어 실행에 영향을 미치는 플레이어 데이터
    // 반환값: 명령어 실행 후 결과를 문자열로 반환
    virtual std::string run(const std::string& command, PlayerData& player) = 0;
};

Java 스타일

  • Java에서는 interface를 사용하여 모듈 규약을 정의합니다.
// GameModule 인터페이스는 모든 게임 모듈이 구현해야 하는 기본 계약을 정의합니다.
// 각 게임 모듈은 이 인터페이스를 구현하여 명령어를 실행하는 방식에 대해 정의해야 합니다.
public interface GameModule {
    // run 메서드는 명령어를 처리하고, 플레이어 데이터에 영향을 미치는 동작을 수행합니다.
    // - command: 사용자가 입력한 명령어 문자열
    // - player: 명령어 실행에 영향을 미치는 플레이어 데이터 객체
    // 반환값: 명령어를 실행한 후 그 결과를 문자열로 반환합니다.
    String run(String command, PlayerData player);
}

C# 스타일

  • C#에서는 interface를 사용합니다.
// IGameModule 인터페이스는 모든 게임 모듈이 구현해야 하는 기본 계약을 정의합니다.
// 각 게임 모듈은 이 인터페이스를 구현하여 명령어를 실행하는 방식에 대해 정의해야 합니다.
public interface IGameModule {
    // Run 메서드는 명령어를 처리하고, 플레이어 데이터에 영향을 미치는 동작을 수행합니다.
    // - command: 사용자가 입력한 명령어 문자열
    // - player: 명령어 실행에 영향을 미치는 플레이어 데이터 객체
    // 반환값: 명령어를 실행한 후 그 결과를 문자열로 반환합니다.
    string Run(string command, PlayerData player);
}

3. 플레이어 데이터 (PlayerData)

모든 언어에서 공통적으로 플레이어의 데이터를 저장하는 클래스가 필요합니다. 이 데이터는 게임 상태를 추적하는데 필수적입니다.

c++ 스타일

// 플레이어의 데이터를 저장하는 클래스
class PlayerData {
public:
    // 플레이어의 이름을 저장하는 변수
    std::string name;
    // 플레이어의 레벨을 저장하는 변수
    int level;
    // 플레이어의 점수를 저장하는 변수
    int score;
};

java 스타일

// 플레이어의 데이터를 저장하는 클래스
class PlayerData {
    // 플레이어의 이름을 저장하는 변수
    String name;
    // 플레이어의 레벨을 저장하는 변수
    int level;
    // 플레이어의 점수를 저장하는 변수
    int score;
}

c# 스타일

// 플레이어의 데이터를 저장하는 클래스
public class PlayerData {
    // 플레이어의 이름을 속성으로 저장
    public string Name { get; set; }
    // 플레이어의 레벨을 속성으로 저장
    public int Level { get; set; }
    // 플레이어의 점수를 속성으로 저장
    public int Score { get; set; }
}

 


4. 예시 모듈 (FishingModule)

C++ 스타일

// GameModule을 상속받아 낚시 모듈을 정의한 클래스
class FishingModule : public GameModule {
public:
    // 낚시 명령을 처리하는 run 메서드
    std::string run(const std::string& command, PlayerData& player) override {
        // 낚시 명령 처리 로직
        return "Fishing started!"; // 낚시 시작 메시지를 반환
    }
};

java 스타일

// GameModule 인터페이스를 구현한 낚시 모듈 클래스
class FishingModule implements GameModule {
    // 낚시 명령을 처리하는 run 메서드
    @Override
    public String run(String command, PlayerData player) {
        // 낚시 명령 처리 로직
        return "Fishing started!"; // 낚시 시작 메시지를 반환
    }
}

C# 스타일

// IGameModule 인터페이스를 구현한 낚시 모듈 클래스
public class FishingModule : IGameModule {
    // 낚시 명령을 처리하는 Run 메서드
    public string Run(string command, PlayerData player) {
        // 낚시 명령 처리 로직
        return "Fishing started!"; // 낚시 시작 메시지를 반환
    }
}

🎯 핵심 포인트

  • 객체지향 설계에서 중요한 점은 언어의 구문 차이를 넘어서 **"설계"와 "인터페이스"**에 집중하는 것입니다.
  • 실제로 Java, C#, C, C++ 모두 동일한 모듈화 설계를 따를 수 있습니다.
  • C, C++, Java 등으로 구현하신다면 디버깅, 수정이 매우 수월하실 거예요.

이 방식대로 설계하고 구현하면, 나중에 Python으로의 변환도 어렵지 않습니다! (다만, 동적 타이핑을 조금 신경 써야겠죠.)

어떤 언어로 구체적인 구현을 시작할지 결정되셨나요? C#이나 Java 중 하나를 선택하시면, 그에 맞게 더 상세하게 설계하거나 코드를 작성해드릴 수 있습니다!