자동판매기 클래스로 구현하기

이전 게시물 중 절차지향적으로 만든 파이썬 자판기 예제가 있습니다.

이번에는 클래스를 이용해 자판기를 만들어 볼 생각입니다.

꼭 자판기를 클래스를 이용해 만들 필요는 없지만, 우리의 목적은 늘 그렇듯이 프로그래밍 스킬 향상이므로 이번에는 클래스를 이용해 객체지향적으로 자판기를 만들어 보고자 합니다.

어제 2개월 진도반 수업시 함께 만들어본 자동판매기 클래스 버전입니다.

판매목록을 보여주고, 투입금액을 입력받은 후 물품을 선택해 구입하는 방식입니다.

[자동판매기 실행화면]

먼저 코드를 한번 살펴보겠습니다.

class Zapangi:

    def __init__(self):
        self.p = {'콜라':500, '사이다':400, '환타':600}
        self.n = {1:'콜라', 2:'사이다', 3:'환타'}
        self.money = 0       

    def showmenu(self):
        print('[메뉴정보]')
        i = 1
        for k, v in self.p.items():
            print(str(i)+'.', k, v, '원')
            i+=1
        print()


    def inputmoney(self):
        while True:
            try:
                self.money += int( input('돈 투입:') )
            except Exception as e:
                print(e)
                continue
            else:
                print('투입금액:', self.money)
                print()
                break


    def buy(self):
        try:
            n = int(input('번호선택(종료:0):'))
        except Exception as e:
            print(e)
        else:
            if n == 0:
                return False        

            if n>=1 and n<=3:

                if self.money >= self.p[ self.n[n] ]:
                    print( self.n[n], '구입완료' )
                    self.money = self.money - self.p[ self.n[n] ]
                    print('잔액:', self.money)
                else:
                    print('잔액부족')
                    self.inputmoney()
              
            else:
                print('잘못된 번호')

        return True


z = Zapangi()
z.showmenu()
z.inputmoney()

while z.buy():    
    print()

print('자판기 종료')
print('반환 : ', z.money, '원')

1번 라인부터 Zapangi 클래스를 선언하고 만들어 갑니다.

3번 라인의 __init__(self) 함수는 Zapangi 클래스에서 객체(변수)가 생성될 때 호출 되는 생성자함수입니다.

생성자에서  자동판매기 판매 물건을 딕셔너리를 이용해 self.p 라는 변수로 선언합니다.

딕셔너리는 key와 value를 쌍으로 갖는 데이터 타입으로, C++ STL 의 map 과 유사합니다

콜라가 키 이고, 500원이 값이 되는 구조이며, 딕셔너리(Dictionary)는 {} 중괄호 기호를 이용해 생성합니다.

self.n 딕셔너리는 차후 설명드리도록 하겠습니다. 일단 이 친구는 숫자와 판매물건 이름을 연결시킨 딕셔너리라는 것만 이해하면 되겠습니다.

클래스 설계 시 개념적으로 선행해야 할 설계 가이드라인은 값을 저장(데이터)하는 속성은 변수로 선언하고, 그 클래스의 행위는 함수로 선언해야한다 사실입니다.

객체지향 프로그래밍에 익숙치 않은 초보자는 이 부분을 많이 어려워 합니다.

즉 판매 물건의 이름이나 가격은 데이터 저장이므로 변수로 저장합니다.

이제 메뉴를 보여줄 수 있어야 하는데 이는 개념적으로 "보여준다"라는 동사이므로 함수로 만들어야 함을 이해할 수 있습니다.

8번 라인의 showmenu(self)함수는 바로 판매 목록을 보여주는 행위를 하는 자판기 클래스의 멤버 함수(메서드)입니다.

반복문을 통해 판매목록 딕셔너리의 key, value를 가져와 메뉴를 출력하는 역할을 담당합니다. 앞에 숫자가 있다면 차후 물품 구입시 편리하므로 정수 변수를 선언해 앞에 숫자를 부여해 줍니다.

17번 라인은 자판기 클래스의 행위 중 돈을 입력받는 역할을 수행하는 inputmoney(self) 함수입니다.

while 무한 반복문으로 돈을 입력받는 input()구문을 시도(try)하고, 만약 숫자가 아닌 다른 타입 (예, 문자 abc 등)의 데이터를 입력받는 경우 except 를 통해 오류를 출력하고 continue 구문을 통해 다시 반복문의 처음으로 이동해 돈을 투입 받도록 시도합니다.

돈 입력에 이상이 없다면 else 구문에서 투입금액을 보여주고 break를 통해 반복문을 탈출합니다.

30번 라인의 buy()함수는 자판기의 물품을 "판매하다" 라는 행위이므로 이 또한 함수로 만듭니다.

물건의 구입은 실제 자판기처럼 버튼이 없으므로, 물품의 번호를 입력받아 구입하는 방식으로 구성하고자 합니다.

다만 물품번호에 엉뚱하게 물품의 이름이나, 잘못된 입력을 받을 수 있으므로, try, except, else 구문을 통해 오류코드를 추가합니다.

try에서 문제 발생하면 except로 ,아니면 (이상없으면) else로 진입하게 됩니다.

물품 구입 번호를 잘 입력했다고 해도 아직 처리해야 할 문제가 남아 있습니다.

돈을 2000원을 넣고 여러개의 물건을 구입하고자 하는 경우를 상상해 보자면 언제든 그만 구입할 수 있어야 하므로, 0번을 입력하면 return False를 통해 함수를 즉시 종료합니다.

그만 구입하는 경우가 아니라면, 물품번호를 체크해 목록에 있는 물품번호라면 구입되어야 하죠.

근데 여기서도 하나 생각해야 할 부분이 있습니다.

자판기에 물품이 구입 가능한 경우는 내가 가진 돈이 사고자하는 물품의 가격보다 크거나 같은 경우에만 물건 구입이 가능해야 하므로 이를 체크합니다.

바로 41번 라인 if self.money(투입금액) >= self.p[self.n[n]] (물품 가격) 구문입니다.

근데, 왜 self.p[self.n[n]] 이라는 복잡한 구문이 필요할까요?

그 이유는 self.p 라는 딕셔너리는 구입 번호를 갖지 않는 비 시퀀스(sequence) 타입의 자료이기 때문입니다.

즉 물품번호를 입력받아 이것이 콜라라는 것을 알 수 없는 구조이죠.

바로 여기서 앞서 만들어둔 self.n 딕서너리가 힘을 발휘합니다.

이 친구는 숫자와 물품이름을 연결시킨 딕셔너리죠.

즉 n이 구입하고자 하는 물품번호 2 라면 self.n[2]은 바로 해당 물품번호의 이름을 의미하게 됩니다. (2:'사이다')

self.n[2]은 사이다입니다.

그렇다면 self.p[self.n[n]] 은 self.p['사이다'] 를 의미하게 되고, self.p['사이다']는 바로 400이라는 딕셔너리의 value가 됩니다.

그럼 최종적으로 41번 라인의 if 구문은 self.money >= 400 이라는 형태로 해석되어 내가 가진돈이 400원보다 크다면의 의미를 갖게 됩니다.

아마 이 예제의 가장 어려운 부분이 아닐까 합니다.

이해가 되지 않는다면 파이썬의 딕셔너리를 다시 한번 공부해 보시기 바랍니다.

그 다음은 쉽습니다.

돈이 있다면 물건을 구입하고 "투입금액 = 투입금액 - 물품구입" 구문으로 돈을 차감하면 되지요.

바로 43번 라인 self.money = self.money - self.p[ self.n[n] ] 코드입니다.

여기까지가 Zapangi 클래스의 코드입니다.


하지만 Zapangi클래스는 붕어빵의 틀일뿐, 메모리에 실체화되지 않습니다.

즉, 클래스는 내가 만들어 쓰는 나만의 타입일 뿐, 그 변수(객체)를 선언하지 않으면(인스턴스화) 아무것도 만들지 않습니다.

55번 라인이 바로 우리가 열심히 만든 클래스의 변수를 선언하는 부분입니다.

와우 드디어 자판기1호가 태어나서 z라는 이름으로 만들어 졌습니다.

그 다음은 누워서 떡먹기죠.

자판기가 가지고 있는 함수를 통해 메뉴를 보여주고(showmenu 함수), 돈을 투입받은 후(inputmoney 함수), 무한 반복을 통해 물품을 구입(buy 함수)하도록 합니다.

59번 라인의 while 반목문은 buy() 함수의 리턴값(참, 또는 거짓) 에 따라서 0번을 물품번호로 입력하면 False를 리턴받아 종료, 아니면 계속 구입 절차가 진행되는 구조입니다.

마지막 62, 63번 라인은 구입과정의 반복이 종료되면 자판기를 종료하고, 남은 잔액을 반환해 보여주는 부분입니다.

이제 모든 코드 분석이 완료되었습니다.

자~ 클래스를 사용해본 소감이 어떤가요?

클래스를 사용하니 자판기 라는 친구가 하나의 타입으로 정의되고, 동작도 함수형태로 내부에 포함시켜 마치 진짜 자판기라는 사물을 창조한 것 같은 느낌이 듭니다.

네, 클래스는 그런 개념입니다.

아직 더 배워야 할 클래스의 멋진 기법(연산자 오버로딩, 상속, 객체변수, 클래스변수, 클래스함수, 정적함수 등) 이 존재하지만 차근 차근 알아가면 됩니다.

감사합니다.

댓글

이 블로그의 인기 게시물

Qt Designer 설치하기

PyQt5 기반 동영상 플레이어앱 만들기