본문 바로가기
코딩 연습

파이썬 클래스 연습 - 이즈리얼, 리신, 몬스터 예시로 알아보기

by good4me 2022. 6. 4.

goodthings4me.tistory.com

[파이썬 클래스 연습] 객체 지향 개발 언어 스터디 중 난해하다고 하는 클래스 개념을 이해하기 위해 패스트캠퍼스의 파이썬 웹 개발에서 이즈리얼, 리신, 몬스터 예시로 설명하는 '클래스 개념' 강의 내용을 정리했다.

 

 

클래스를 사용하는 이유 - RPG 게임을 활용하여 설명

 

■ 클래스를 사용하지 않았을 때

- 공격 함수를 하나 만들고 챔피언 '이즈리얼', '리신'이 이를 사용한다.

# 공격 함수
def basic_attack(name, attack):
    print(f'{name} 기본 공격: {attack}')

champion1_name = '이즈리얼'
champion1_health = 700
champion1_attack = 90
print(f'{champion1_name}님 소환사의 협곡에 오신 것을 환영합니다.')
basic_attack(champion1_name, champion1_attack)

champion2_name = '리신'
champion2_health = 800
champion2_attack = 95
print(f'{champion2_name}님 소환사의 협곡에 오신 것을 환영합니다.')
basic_attack(champion2_name, champion2_attack)


[실행 결과]
이즈리얼님 소환사의 협곡에 오신 것을 환영합니다.
이즈리얼 기본 공격: 90
리신님 소환사의 협곡에 오신 것을 환영합니다.       
리신 기본 공격: 95

 

- 챔피언 '야스오' 하나를 더 만든다.

champion3_name = '야스오'
champion3_health = 800
champion3_attack = 95
print(f'{champion3_name}님 소환사의 협곡에 오신 것을 환영합니다.')
basic_attack(champion3_name, champion3_attack)


[실행 결과]
야스오님 소환사의 협곡에 오신 것을 환영합니다.     
야스오 기본 공격: 95

 

클래스를 사용하지 않을 때는 챔피언을 추가할 때마다 5줄의 코딩이 추가되었다.

 

 

■ 클래스를 사용할 때

## Champion 클래스
class Champion:
    def __init__(self, name, health, attack):
        self.name = name
        self.health = health
        self.attack = attack
        print(f'{self.name}님 소환사의 협곡에 오신 것을 환영합니다.')
    
    def basic_attack(self):
        print(f'{self.name} 기본 공격: {self.attack}')


# 챔피언 이즈리얼 객체 생성
champion1_ezreal = Champion('이즈리얼', 700, 90) 
champion1_ezreal.basic_attack()

# 챔피언 리신 객체 생성
champion2_leesin = Champion('리신', 800, 95)
champion2_leesin.basic_attack()


[실행 결과]
이즈리얼님 소환사의 협곡에 오신 것을 환영합니다.   
이즈리얼 기본 공격: 90
리신님 소환사의 협곡에 오신 것을 환영합니다.       
리신 기본 공격: 95

 

- 챔피언 '야스오' 객체를 만든다.

# 객체 추가
champion3_yasuo = Champion('야스오', 770, 96)
champion3_yasuo.basic_attack()


[실행 결과]
야스오님 소환사의 협곡에 오신 것을 환영합니다.     
야스오 기본 공격: 96

 

챔피언 1,000개를 구현한다면, 
클래스 미사용 시, 코드수 5줄 * 1,000개 = 5,000줄
클래스 사용 시, 2줄 * 1,000개 = 2,000줄

 

클래스를 사용하지 않는 경우는 챔피언을 추가할 때마다 동일한 코드량을 추가하지만, 클래스를 사용할 때는 객체만 만들면 되기 때문에 수백수천의 많은 챔피언을 만든다고 가정하면 클래스를 이용하는 방식이 더 효율적이다. (중복 코드 최소화)

 

 

■ 클래스의 효용성 측면과 클래스 생성자, 상속, 오버라이딩에 대해 설명

# Monster 클래스
class Monster:
    def __init__(self, health, attack, speed):  # 속성 초기화
        self.health = health
        self.attack = attack
        self.speed = speed

    def decrease_health(self, num):  # 체력 감소
        self.health -= num

    def get_health(self):  # 남은 체력 얻기
        return self.health


goblin = Monster(800, 100, 300)
wolf =  Monster(1000, 300, 400)

goblin.decrease_health(50)
print(goblin.get_health())



[실행 결과]
750
  • Monster 클래스 내에는 __init__() (이를 생성자라 함), decrease_health(), get_health() 메서드가 있다. 
  • goblin과 wolf 객체를 만들 때, 속성 초기화를 위해서 생성자에 매개변수 3개(health, attack, speed)를 넣어 생성했다.
  • 생성된 객체 goblin이 decrease_health() 메서드를 호출(체력 50 사용)하였고, 이를 확인하기 위해 get_health() 메서드를 호출했다.
  • wolf도 goblin처럼 객체를 만들어서 메서드를 호출할 수 있다.

 

# 클래스 생성자

  • 생성자 메서드 __init__()은 클래스로 인스턴스(객체)를 만들 때 자동으로 가장 먼저 호출되는 메서드이다.
  • 생성자 메서드에는 매개변수가 0 ~ n개로 설정할 수 있다.
  • 생성자 메서드의 매개변수를 통해 클래스의 속성들을 초기화시킨다.
  • 매개변수 self는 클래스로 생성되는 인스턴스(객체) 자신, 즉 goblin 또는 wolf를 지칭하는 특수한 매개변수이다.

 

good4me.co.kr

 

상속(inheritance)과 메서드 오버라이딩

# 상속을 활용하는 이유

 

- 캐릭터를 키워서 몬스터를 잡고 레벨업을 하고 아이템을 얻는 RPG 게임에서 몬스터의 종류(땅 몬스터, 물 몬스터, 공중 몬스터,...)가 여러 개 있다고 했을 때, 

  • 각각의 몬스터에 대해 속성과 메서드를 지정하는 코드를 만든다면 엄청난 코드를 작성해야 하고, 코드에 문제가 발생했을 때 유지보수를 위한 많은 노력과 시간이 들게 된다.
  • 이러한 문제점을 해결하는 방법으로 이 몬스터들의 공통적인 특징을 하나의 클래스로 만들어서 사용하면 중복된 코드를 제거할 수 있다. 즉, 상속은 클래스들의 중복된 코드를 제거(코드의 효율적 재사용성 제고)하고 유지보수를 편리하게 할 수 있다.

- 상속을 위해 부모 클래스와 자식 클래스를 만든다.

## 부모 클래스 정의
class Parent_Monster:
    def __init__(self, name, health, attack):
        self.name = name
        self.health = health
        self.attack = attack

    def move(self):
        print(f'{[self.name]} 지상에서 이동하기')
    

## 자식 클래스 정의
class Dragon(Parent_Monster):
    def move(self):
        print(f'{[self.name]} 날기')

class Shark(Parent_Monster):
    def move(self):
        print(f'{[self.name]} 헤엄치기')

class Wolf(Parent_Monster):
    pass
    

dragon = Dragon('드래곤', 1000, 300)
dragon.move()

shark = Shark('샤크', 1500, 400)
shark.move()

wolf = Wolf('울프', 1200, 200)
wolf.move()



[실행 결과]
['드래곤'] 날기
['샤크'] 헤엄치기
['울프'] 지상에서 이동하기
  • Parent_Monster 클래스의 속성과 메서드 전체를 Dragon, Shark, Wolf 클래스가 상속을 받기 때문에 Parent_Monster의 속성인 name, health, attack을 Dragon, Shark, Wolf에서 그대로 사용할 수 있고,
  • Dragon과 Shark의 이동은 다르기 때문에 Parent_Monster의 move() 메서드를 각 자식 클래스에서 다시 만들어 사용할 수 있다. 이를 메서드 오버라이딩(메서드 재정의)라고 한다.

 

생성자(__init__()) 오버라이딩, 클래스 변수

# 생성자 오버라이딩(메서드 오버라이딩)

  • 드래곤의 스킬 향상을 위해 자식 클래스인 드래곤 클래스의 속성(인스턴스 속성)에 스킬 3개 추가하되, 드래곤이 스킬을 쓰면 속성 중에 무작위로 하나가 사용되도록 한다.
  • 스킬 속성을 주기 위해 드래곤 생성자 활용함 (부모 생성자 오버라이딩 포함)

 

# 클래스 변수(클래스 자체의 속성)

  • 클래스 자체의 속성 변수이며, 인스턴스(객체)들이 모두 공유하는 변수임
  • 부모 클래스에서 사용하는 이유 : 몬스터가 너무 많으면 서버에 부하가 될 수 있으니 몬스터 개수에 제한을 걸기 위해 사용하는 변수
import random

## 부모 클래스 정의
class Parent_Monster:
    monster_max_num = 1000
    def __init__(self, name, health, attack):
        self.name = name
        self.health = health
        self.attack = attack
        self.get_monster_count()
        Parent_Monster.monster_max_num -= 1
        print(f'몬스터 생성수: {Parent_Monster.monster_max_num}')

    def move(self):
        print(f'{[self.name]} 지상에서 이동하기')

    def get_monster_count(self):
        if Parent_Monster.monster_max_num == 0:
            print('더이상 몬스터를 만들 수 없습니다!!')
            

## 자식 클래스 정의
class Dragon(Parent_Monster):
    # 부모 생성자 오버라이딩
    def __init__(self, name, health, attack, skills):
        super().__init__(name, health, attack)  # 부모 생성자 호출        
        self.skills = skills[random.randint(0, len(skills)-1)]

    def move(self):
        print(f'{[self.name]} 날기')

    def skill(self):
        print(f'{[self.name]} 스킬 사용 - {self.skills}')


class Shark(Parent_Monster):
    def move(self):
        print(f'{[self.name]} 헤엄치기')


class Wolf(Parent_Monster):
    pass


dragon = Dragon('드래곤', 1000, 300, ('불뿜기', '꼬리치기', '날개치기'))
# 호출할 때 스킬을 입력할 수 있음
dragon.move()
dragon.skill()

shark = Shark('샤크', 1500, 400)
shark.move()

wolf = Wolf('울프', 1200, 200)
wolf.move()



[실행 결과]
몬스터 생성수: 999
['드래곤'] 날기
['드래곤'] 스킬 사용 - 날개치기
몬스터 생성수: 998
['샤크'] 헤엄치기
몬스터 생성수: 997
['울프'] 지상에서 이동하기
  • 클래스 변수에 접근할 때는 '클래스명.클래스변수명'으로 접근함(변수가  '__변수명'으로 선언하면 private 속성 변수)
  • 이 코드에서는 변수가 private 속성이 아니기 때문에 객체에서도 접근이 가능하다.(print(wolf.monster_max_num) 등으로 접근 가능)
  • 부모 생성자 오버라이딩 부분을 보면, 자식의 생성자에서 부모 생성자를 호출할 때 매개변수가 기본적으로 들어가고 skills라는 매개변수도 들어간다. 이 skills가 dragon만의 스킬 리스트가 되며, random.randint() 함수로 임의 지정이 되도록 했다.
  • dragon은 shark, wolf가 없는 skill() 메서드가 있고 이를 사용할 수 있다.
  • 몬스터의 생성 수를 체크하기 위해 부모 클래스에 클래스 변수(monster_max_num)를 삽입하고, 이를 고지하기 위해 생성자 메서드에서 출력하도록 했다.
  • 부모 클래스에 추가한 get_monster_count() 메서드는 메인 함수에서 몬스터 수를 확인한 후 프로그램을 종료할 때 출력하는 용도로 사용할 수 있다. (메인 함수에서 이 기능을 작성해도 됨)

 

결론적으로, 클래스를 만들어서 사용하면, 몬스터를 수없이 쉽게 만들 수 있고 몬스터마다 특징을 줄 수 있다.

 

[관련 포스팅 더보기]

파이썬 클래스 연습 - 게임 아이템의 종류 구입, 사용, 버리기 메서드

 

파이썬 클래스 연습 - 게임 아이템의 종류 구입, 사용, 버리기 메서드

[파이썬 클래스 예제] 기 포스팅 한 [파이썬 클래스 연습 - 이즈리얼, 리신, 몬스터 예시로 알아보기]에 이어 패스트캠퍼스의 연습 문제(게임 아이템의 종류를 구분 문제)를 포스팅함 파이썬 클래

goodthings4me.tistory.com

 

댓글