[python] 2. Class

시작하기에 앞서 당신은 게임 개발자가 되었고 RPG게임을 하나 만들었다고 하자.

1. Class

그동안 하나의 객체를 만들고 그 속성을 선언할 때 이렇게 해왔다.

name = "기사"
hp = 100
damage = 10

만약 이 방식으로 다른 캐릭터 객체를 추가하려면 이 행위를 여러번 해야하고
변수 이름도 캐릭터마다 다르게 일일이 설정해야 하므로 생산성이 매우 떨어진다.

붕어빵을 만들때 일일이 똑같은 모양으로 붕어빵을 빚는게 아니고 빵틀로 찍어내는 것 처럼
class를 하나의 빵틀로 생각하면 이해하기 쉬울 것이다.
이 class라는 빵틀로 만든 붕어빵(객체)은 instance라고 한다.

instance에 대해서 공부하는데 멤버 변수와 인스턴스 변수가 다른 개념인걸 확인했다.

  • 멤버 변수
    모든 인스턴스의 클래스와 공유되는 변수의 사본이 하나만 있을 경우 이를 클래스 변수나 정적 멤버 변수로 부른다.
  • 인스턴스 변수
    클래스의 각 인스턴스가 자신만의 변수 복사본을 소유하고 있는 경우 해당 변수는 인스턴스 변수라 부른다.

class로 instance를 생성하고 관련된 데이터를 멤버 변수로 한데 묶어 관리하게 되면 개발하기도 편하고 코드를 보는 입장에서도 편할 것이다.

2. Class선언

캐릭터를 생성하는 Unit이라는 클래스를 무작정 선언해보자.

class Unit:
    def __init__(self, name, hp, damage):
        self.name = name
        self.hp = hp
        self.damage = damage
    	print("{0} 영웅을 생성했습니다.".format(self.name))
    	print("체력 : {0}, 공격력 : {1}\n".format(self.hp, self.damage))

캐릭터명, 체력, 공격력이라는 인자를 받아오는 함수인거 같은데...
근데 여기서 self는 뭐고 init은 또 뭐야?

  • self.
    self.OOO이라는 클래스 내의 변수를 따로 선언하여 외부에서 받아온 변수를 클래스 내에서 접근 가능하도록 하는 것이다.
    name이라는 외부에서 받은 변수를 self.name으로 선언하여 Unit 클래스가 이를 사용(접근)하는 멤버변수로 탈바꿈시킨다 라고 이해하자.

  • __init__
    생성자라고 한다.
    __init__ 부분에서는 입력받은 argument의 종류와 class 선언 시 먼저 실행 시킬 코드를 실행한다.(초기화)
    위의 코드를 대입해보면 캐릭터명, 체력, 공격력 argument를 self로 멤버변수화하는 기능을 실행하고 출력문을 출력한다.

3. 객체 생성

그럼 게임의 캐릭터를 만들어야 하니 Unit이라는 클래스를 통해 새 영웅을 만들어보자.

user1 = Unit("기사", 100, 25)


'기사'영웅이고 '100'의 체력을 가지며 '25'의 공격력을 지닌 'user1'이라는 캐릭터를 만들었다.

만약 마법사 캐릭터를 만들고 싶다면?

user2 = Unit("마법사", 80, 20)

이런 방식으로 객체를 계속 생성해 나가면 된다.
그런데 이 게임 속 마법사는 투명 마법을 쓸 수 있다.
근데 우리는 Unit 클래스를 선언할때 투명의 유무를 판별할 수 있는 argument를 아무것도 정의하지 않았다.
이럴땐 객체 생성시 추가 멤버변수를 선언해야한다.

user2 = Unit("마법사", 80, 20)
user2.clocking = True
if user2.clocking == True:
    print("{0}는 현재 클로킹 상태입니다.".format(user2.name))

4. Class Method

캐릭터를 생성했으니 이 캐릭터의 행동(공격, 피해, 이동 등등...)을 부여해보자.
method를 활용하면 된다.
클래스 내의 함수를 method라고 한다.

class AttackUnit:
    def __init__(self, name, hp, damage):
        self.name = name
        self.hp = hp
        self.damage = damage
        print("{0} 영웅이 생성되었습니다.".format(self.name))
        print("체력 {0}, 공격력 {1}\n".format(self.hp, self.damage))

    def attack(self, direction):
        print("{0} : {1} 방향으로 적군을 공격합니다. [공격력 {2}]"\
            .format(self.name, direction, self.damage))

direction 인자를 받아와서 출력문을 실행하는 attack() 메소드를 선언했다.
객체를 생성하고 메소드를 실행시키면

knight1 = AttackUnit("기사", 100, 25)
knight1.attack("오른쪽")


당신의 캐릭터가 드디어 공격을 했다.
공격을 할 수 있으니 이제 공격을 받을 수도 있다.

    def damaged(self, damage):
        print("{0} : {1} 데미지를 입었습니다.".format(self.name, damage))
        self.hp -= damage
        print("{0} : 현재 체력은 {1} 입니다.".format(self.name, self.hp))
        if self.hp <= 0:
            print("{0} : 사망했습니다.".format(self.name))

damage 인자를 받아와서 공격 받을 때마다 hp를 damage만큼 감소시키고 만약 hp가 0 이하면 사망했다는 출력문을 출력하는 메소드이다.

knight1.damaged(50)
knight1.damaged(50)


당신의 캐릭터가 몬스터에게 2방을 맞고 죽어버렸다.

5. Class Inheritance

앞서 만들었던 Unit과 AttackUnit 두 클래스를 한 번 유심히 봐보자.

class Unit:
    def __init__(self, name, hp, damage):
        self.name = name
        self.hp = hp
        self.damage = damage
        print("{0} 영웅이 생성되었습니다.".format(self.name))
        print("체력 {0}, 공격력 {1}\n".format(self.hp, self.damage))

class AttackUnit:
    def __init__(self, name, hp, damage):
        self.name = name
        self.hp = hp
        self.damage = damage
        print("{0} 영웅이 생성되었습니다.".format(self.name))
        print("체력 {0}, 공격력 {1}\n".format(self.hp, self.damage))

    def attack(self, location):
        print("{0} : {1} 방향으로 적군을 공격합니다. [공격력 {2}]"\
            .format(self.name, location, self.damage))
    
    def damaged(self, damage):
        print("{0} : {1} 데미지를 입었습니다.".format(self.name, damage))
        self.hp -= damage
        print("{0} : 현재 체력은 {1} 입니다.".format(self.name, self.hp))
        if self.hp <= 0:
            print("{0} : 사망했습니다.".format(self.name))

생성자 부분에서 중복된 부분이 보인다.
개발할 때 중복을 제거하는것은 생산성을 높이고 오류를 줄이는데 아주 좋기 때문에 이 중복을 제거해야 한다.
이를 위해 상속이라는 개념을 사용한다.

AttackUnit 클래스의 생성자 부분에서 damage 변수를 제외하고 Unit클래스와 중복되므로
중복되는 부분을 Unit 클래스에서 상속받아오자.

class Unit:
    def __init__(self, name, hp):
        self.name = name
        self.hp = hp
    
class AttackUnit(Unit):
    def __init__(self, name, hp, damage):
        Unit.__init__(self, name, hp)
        self.damage = damage
        print("{0} 영웅이 생성되었습니다.".format(self.name))
        print("체력 {0}, 공격력 {1}\n".format(self.hp, self.damage))

AttackUnit 클래스의 인자로 Unit 클래스를 받음으로써 Unit 클래스로부터 상속받았다는 것을 선언했다.
또, 생성자 안에도 Unit.을 붙여 생성자 또한 Unit 클래스로부터 상속받았다는 것을 표현했다.

6. Multi Inheritance

그런데...
이 게임에서 마법사는 투명 마법도 쓸 수 있는데 심지어 날 수도 있다.(마법사 사기캐)
비행이라는 메소드를 AttackUnit 클래스에 추가하고 싶은데 그러기엔 다른 캐릭터 직업들은 비행을 할 수 없다.(ex 기사)
그러기에 Flyable이라는 클래스를 새로 만들어서 비행의 개념을 만들고

class Flyable:
    def __init__(self, flying_speed):
        self.flying_speed = flying_speed
    
    def fly(self, name, location):
        print("{0} : {1} 방향으로 비행합니다. [속도 {2}]"\
            .format(name, location, self.flying_speed))

FlyableAttackUnit라는 클래스를 만들어 비행가능한 공격 영웅을 정의하는 클래스를 만들었다.

class FlyableAttackUnit(AttackUnit, Flyable):
    def __init__(self, name, hp, damage, flying_speed):
        AttackUnit.__init__(self, name, hp, damage)
        Flyable.__init__(self, flying_speed)

AttackUnit 클래스와 Flyable 클래스를 상속받아 flyableAttackUnit 클래스를 생성하고
두 클래스 각각의 생성자 부분에서 필요한 인자들을 가져와 flyableAttackUnit 클래스의 생성자에 사용하였다.

이처럼 여러 클래스에서 상속받는 것을 다중상속이라 한다.

wiz1 = FlyableAttackUnit("법사", 200, 6, 5)
wiz1.fly(wiz1.name, "3시")

이번 글을 쓰고 다시 한 번 찬찬히 읽어봤는데 개념정리보다 예시에 취중한 듯한 느낌이 들었다.
다음 글은 개념90 예시10 비율로 적어보는걸로

좋은 웹페이지 즐겨찾기