[ 파이썬 언어 공부 ] 기본적인 코드 방식을 클래스 사용 체계로 전환하기 2 - Whitmem
[ 파이썬 언어 공부 ] 기본적인 코드 방식을 클래스 사용 체계로 전환하기 2
Python Programming
2023-08-09 03:46 게시 1bd545532c8b1644a2c9

2
0
163
이 페이지는 외부 공간에 무단 복제할 수 없으며 오직 있는 그대로 게시되며 부정확한 내용을 포함할 수 있습니다. 법률이 허용하는 한 가이드 라인에 맞춰 게시 내용을 인용하거나 출처로 표기할 수 있습니다.
This page is not to be distributed to external services; it is provided as is and may contain inaccuracies.
이번 게시물에서는 다양한 클래스의 구성 예시를 보고 왜 구성에 문제가 있고, 비효율적인지 알아봅니다. 알아둘 것은 클래스는 개발자의 의도에 맞게끔 설계하는 것이며, 각 개발자의 구현 의도에 따라 구현 방법이 전혀 달라질 수 있습니다. 여러분들이 클래스를 제대로 설계하기 위해서는 다양한 수단의 클래스를 만들고 구축 해 봐야 어떤 부분이 문제인지, 어떤 부분이 비효율적인지 파악할 수 있으며 의도한 대로 클래스를 구축할 수 있습니다.
챕터 2. 게임 서버에서의 플레이어 관리
이번 방법에서는 게임에서 플레이어 관리를 하는 클래스에 대해 간단히 알아봅니다. 게임에는 기본적으로 플레이어가 새로 추가되거나 제거될 수 있습니다. 사용자가 게임에 입장하면 하나의 "플레이어"객체로 볼 수 있습니다.
플레이어
플레이어들은 x, y 좌표를 가지며 이름을 가집니다. 이러한 속성 정보는 추후 필요에 따라 더 추가되거나 제거될 수 있습니다. 따라서 플레이어 이름이나 각 좌표를 따로 리스트로 관리하는 것은 비효율적입니다. 그렇기 때문에 우리는 플레이어 하나의 객체에 대해 클래스로 관리 할 생각을 해 봐야 합니다. 플레이어 한 명은 닉네임 좌표외 각각의 동작 행위를 포함합니다.
이동
1) 특정 좌표로 움직일 수도 있고, 2) 이름을 개명할 수도 있고, 3) 원점으로 이동할 수도 있는 그러한 플레이어가 있다면 모든 플레이어들은 이러한 동작 행위를 독립적으로 수행할 수 있습니다. playerName1은 특정 좌표 (5,5)로 이동할 수 있으며 playerName2는 특정 좌표 (10,10) 으로 이동, playerName3은 원점으로 이동, playerName4는 이름을 playerName6로 개명할 수도 있습니다. 이러한 행위는 각각의 플레이어간 독립적으로 동작되지만 모든 플레이어들은 공통적으로 해당 행위를 수행할 수 있습니다. 즉 공통적으로 수행할 수 있는 부분을 Player클래스에 모두 선언해 실존하는 데이터만 다르게끔 객체를 여러개 생성하여 관리해야 합니다.
자 그러면 생각 해 봅시다.
위에서 설명한 GamePlayer 클래스를 구성한다고 가정합니다. 각 플레이어는 playerName x y 좌표를 가지고 각각 특정 좌표 x,y 로 움직이거나, 이름을 개명하거나, 원점으로 이동할 수 있습니다. 이러한 상황에서 PlayerAPlayerB가 존재할때, A는 5,5 좌표로, B는 10,10 좌표로 이동 해 봅시다.
대표적인 사례들
여러분들은 위 문구를 구현하기 위해 클래스를 어떤식으로 구성하셨나요? 보통 클래스 방식에 익숙치 않는다면, 다양한 실수를 하곤 합니다. 다양한 실수 사례를 통해 왜 이 방법이 좋지 않은지 알아봅시다.
대표적 예시 : 한 클래스에 모든 걸 담아내려고 하는 경우
위 문구에서 PlayerA PlayerB가 존재한다는 사실에만 집중하면 원래 의도하는 클래스의 목적을 상실할 수 있습니다. 다시 말하지만 클래스는 같은 작업들을 기능 단위로 묶어내고 동일한 패턴 및 작업에 대해서 객체로 여러개 생성하여 효율적으로 사용하기 위함입니다.
위 소스코드는 GamePlayer 를 처리하는 클래스를 나타내고 있습니다. 이 소스코드의 경우 한 클래스 안에 플레이어1 플레이어2를 담아내고 있습니다. 그리고 메소드(클래스 내 함수)를 통하여 자기 자신 객체에 저장된 player1과 player2에 대해 각각 처리 해주는 메소드를 따로 만들었습니다. 즉 플레이어 1 에 대해서 처리하기 위해서는 setXYPlayer1()을 호출해야 하고 플레이어 2에 대해서 처리하기 위해서는 setXYPlayer2()를 별개 호출해야 합니다. 이미 이 클래스를 근본적으로 설계할 때 두 명의 이상의 플레이어를 받도록 구현했기 때문입니다. 이 같은 상황에서 플레이어를 더 추가하려면 인자를 더 만들거나, 아예 클래스 안에 리스트로 플레이어를 추가적으로 관리해야하는 상황이 발생합니다. 즉 공통적인 작업을 별개 진행해야 합니다.
예를 들어 리스트로 관리할 수 있도록 수정 해 보겠습니다.
리스트로 관리
이번에는 클래스 안에서 여러 플레이어들 리스트로 받고, 각 플레이어들 인덱스를 통해서 x, y등을 수정할 수 있도록 구현하였습니다. 이 방법을 활용하면 이전에 비해서는 소스코드를 수정해야하는 부분은 비교적 줄어들었고 동적인 작업을 수행을 할 수는 있지만 여전히 매우 깔끔하지는 않습니다. x, y 좌표 속성 외 다른 속성을 추가할 때 마다 플레이어 개수 만큼의 공간을 가지는 속성을 한번에 생성하고 하나의 클래스 안에서 관리해야 합니다. 먼 미래 속성이 50가지가 된다면 관리해야 할 사항이 많아지고 중간에 오류가 발생하면 추적하기 매우 힘들어집니다.
그러면 도대체 어떻게 해야할까요? 플레이어 객체를 만들어야 하고, 플레이어 2명 이상이 적어도 존재 한다는데... 리스트로 관리해야 하는 것이 아닌가?
물론 플레이어를 두 명 이상 동적으로 관리하기 위해서는 리스트는 빠질 수 없습니다. 하지만 지금은 플레이어가 몇 명이 되더라도 고려 할 필요가 없습니다. 분명 플레이어각각 별개의 이름, 좌표를 가진다고 했습니다. 플레이어는 모두 개별적으로 각각 그러한 수단을 가지기 때문에, 지금은 플레이어 한명에 대해서만 구현하고 보는겁니다.
한 명에 대해서만 처리한다.
GamePlayer 클래스는 오직 한명에 대한 플레이어 이름을 받아서 그 객체의 자기 자신에 이름을 저장, 그 객체 자신의 x, y만 좌표를 지정하고 설정할 수 있도록 하고 있습니다. 이러면 이 클래스로 구현된 객체 하나에는 하나의 플레이어만 저장할 수 있습니다. 하지만 이러한 의도가 맞습니다. 왜냐하면 이를 호출하는 입장에서 객체 그 자체를 리스트로 관리 해 주면 되기 때문입니다.
밖에서 리스트로 관리
GamePlayer내 에서는 하나의 객체에 대해서만 관리하며, 이를 밖에서 호출하는 입장에서 여러개 객체를 생성하여 리스트로담아 관리하고 있습니다. 이렇게 되면 각 객체는 독립적으로 생성되고 객체들이 리스트에 담기기에 어디 전달하고 할 필요 없이 바로 꺼내서 메소드를 수행해주면 해당 객체에 대해서만 독립적으로 작업이 수행됩니다. 이러한 작업은 반드시 GamePlayer 클래스가 존재하는 파일에 작성 할 필요가 없습니다. 다른 파일에서도 GamePlayer클래스를 사용하여 객체를 생성하고 관리할 수 있습니다. 예를 들어 메인 파일에서도 가능합니다.
위 메인 파일, 아래 GamePlayer 파일
이렇게 되면 메인 파일 입장에서는 GamePlayer의 내부 소스코드를 알 필요가 없습니다. 단순히 호출해서 가져다 쓰면 됩니다.
객체의 대량 관리
그러면 여기서 리스트로 수백명의 플레이어를 생성 해 보겠습니다.
플레이어를 100명 생성해서 객체에 담기
모든 플레이어들에 대해서 (0.0) 좌표로 이동한다고 가정합시다. 현재 리스트 안에 객체가 100개가 있기 때문에, 모든 객체를 순회하면서 setXYPlaer()을 수행 해 주면 됩니다.
모든 객체들에 대해서 일괄적으로 수행
한편 지금은 단순히 (0,0) 좌표로 이동하기 위함이며, 이외 다른 메소드가 추가되거나 변경되고 일괄적으로 수행할 때 마다 해당 위치에서 for문을 수행해야 합니다. 따라서 이번에는 GamePlayer를 대량 관리 해주는 클래스를 만들 수 있습니다.
GamePlayerCollection
이제 실질적으로 클래스 안에 리스트가 들어가기 시작했습니다. 이번에는 플레이어 하나를 관리하는 클래스가 아닌, 대량의 GamePlayer객체를 관리하는 클래스를 만들기 때문에 이제서야 리스트가 들어가는 클래스를 새로 만듭니다. 즉 GamePlayerCollection 객체 한 개는 여러개의 GamePlayer을 담고 있을 수 있게 됩니다.
메인 코드 및 Collection 코드
from GamePlayer import GamePlayer from GamePlayerCollection import GamePlayerCollection colleciton = GamePlayerCollection() for x in range(100): colleciton.addPlayer(GamePlayer(str(x)))
메인 영역에서는 단순히 GamePlayerCollection 객체를 하나만 만들어서, 이 객체 안에 GamePlayer객체를 모조리 추가 해주고 있습니다. 그렇게 되면 하나의 GamePlayerCollection 객체 안에 100개의 GamePlayer객체가 담기게 됩니다. 이제, 이 상황에서 모든 GamePlayer들에 대해서 특정 좌표로 이동시키는 메소드를 GamePlayerCollection안에 만듭니다.
GamePlayerCollection 내 teleportAllToLocation()
즉, GamePlayerCollectionteleportAllToLocation()는 GamePlayerCollection 에 추가된 모든 GamePlayer에 대해 좌표 이동을 수행합니다. 이렇게 Collection 객체 안에 자신이 가지고 있는 모든 GamePlayer 객체에 대해 일괄 수행하는 메소드를 만들어주고 메인에서 teleportAllToLocation()만 호출 해 주면 현재 Collection이 가지고 있는 모든 객체에 대해서 내부적으로 좌표 이동을 수행합니다.
수행
그러면 Main 입장에서는 각 객체에 대해 직접 for로 순회 할 필요가 없으며 단순히 호출만 하면 가지고 있는 모든 객체에 대해서 일괄적으로 처리 해 줍니다. 즉 어떻게 보면 GamePlayer는 하나의 플레이어를 담당, GamePlayerCollection는 여러 GamePlayer를 담당하는데, 담당함과 동시에 어떤 처리를 일괄적으로 수행 할 수 있도록 클래스 함수 (메소드)를 내장했습니다. 결국 클래스의 제일 기초적인 내용은 기본적으로 하나의 객체를 관리 해주는 방법에서 파생됩니다. 물론 이는 개발자 마음입니다. 반드시 클래스가 하나의 객체만을 관리 할 필요는 없습니다. 하지만 기능을 분할하다보면 어차피 하나의 객체을 담당하는 클래스는 생기기 마련입니다.
댓글 0개
댓글은 일회용 패스워드가 발급되며 사이트 이용 약관에 동의로 간주됩니다.
확인
Whitmemit 개인 일지 블로그는 개인이 운영하는 정보 공유 공간으로 사용자의 민감한 개인 정보를 직접 요구하거나 요청하지 않습니다. 기본적인 사이트 방문시 처리되는 처리 정보에 대해서는 '사이트 처리 방침'을 참고하십시오. 추가적인 기능의 제공을 위하여 쿠키 정보를 사용하고 있습니다. Whitmemit 에서 처리하는 정보는 식별 용도로 사용되며 기타 글꼴 및 폰트 라이브러리에서 쿠키 정보를 사용할 수 있습니다.
이 자료는 모두 필수 자료로 간주되며, 사이트 이용을 하거나, 탐색하는 경우 동의로 간주합니다.