[조진과제][AP프로그래밍][6월]

2024. 6. 23. 14:27카테고리 없음

오늘의 주제는 "객체지향 프로그래밍(Object-Oriented Programming)" 이다.

백준이나 코드업에 객체지향 관련한 문제가 없어 직접 만들게 되었다. (사실 잘 찾아보면 있지만, 이미 선점당했다.)

아무튼, 소설 속 인물의 지능은 작가를 뛰어넘을 수 없듯이, 문제의 수준 또한 제작자의 지식 수준과 비례하기에 문제가 전체적으로 억지스러움이 있어도 양해 바란다. ('엿장수 맘대로' 라는 말처럼, 나도 '내 마음대로' 문제에 조건을 넣었다.)

 


Ⅰ. 문제 상황 설명

출처: https://m.blog.naver.com/gusdud9463/221945261911


 어엿한 성인이 된 동현이(가명)는 금마카페에서 알바를 한다. 메뉴에는 1000원짜리 ‘데자와’500원짜리 ‘삼다수’, 1750원짜리 ‘슬러시’, 45000원짜리 '매운탕'이 있다. 각각의 손님은 자신의 이름과 함께 주문하고 싶은 음식의 이름을 말하며, 주문한 뒤에 다시 주문했던 음식을 받기 위해 나중에 한 번 더 온다. 동현이는 각 손님들이 처음으로 주문할 때에는 주문 번호를 말하고, 나중에 다시 와서 주문한 음식을 받으러 왔을 때에는 주문한 음식과 함께 가격을 말해야 한다. (동현이가 지금까지 벌은 카페 수익은 계속해서 기억하고 있어야 한다. 그렇지 않으면 사장인 강빈이에게 혼나고 말 것이다.)

 그런데 만약 손님 이름이 “사장님” 이라면 동현이는 지금까지 벌은 수익을 바른대로 불어야 하며, 사장님은 매우 강인하기 때문에 돈을 내지 않고도 그 즉시 카페에서 음식을 그냥 빼올 수 있다. (사장님이 주문한 음식의 가격은 수익에서 제외하며, 사장님은 한 번만 올수도, 여러번 올수도 있다.)

(문제에 등장하는 모든 인물은 가상의 인물이다.)

 

  • 입력 조건:

첫째 줄에 손님의 수 N과 사장님의 방문 횟수 M 입력된다. (1 N, M 10000)

다음 줄부터 손님의 이름, 주문할 음식이 입력된다.

 

  • 출력 조건:

첫 주문하는 손님이라면 손님의 주문 번호를 출력한다. (주문 번호는 1에서 시작하며, 다음 손님마다 1씩 커진다. 사장님은 손님으로 치지 않는다.)

두번째로 주문하는 손님이라면 주문한 음식의 이름과 가격을 출력한다.

만약 손님의 이름이 “사장님”이라면 몇 번째로 방문했든 간에, 지금까지의 수익을 출력한다.

 

  • 도움말:

 손님으로부터 첫번째 주문을 받았을 때 돈을 얻는 것으로 한다. (같은 손님의 두번째 주문에선 돈을 받지 못한다.)

 각 손님은 첫주문과 두번째 주문으로, 총 2번씩만 카페에 온다.

 

  • 입력 예시:

3 1

고강빈 삼다수

김유현 매운탕

고강빈 삼다수

사장님 매운탕

전곽이 슬러시

전곽이 슬러시

김유현 매운탕

 

  • 출력 예시:

1

2

삼다수 500

45500

3

슬러시 1750

매운탕 45000

 

Ⅱ. 문제 풀이 전략

  1. Menu 클래스
class Menu:
 def __init__(self, name, price):
   self.name = name
   self.price = price


 def __str__(self):
   return f"{self.name} {self.price}"

우선 Menu 클래스를 만든다. 메뉴 항목의 이름과 가격을 초기화하며, 메뉴 항목을 문자열로 반환해준다. (이를 넣지 않으면 출력시 메모리 주소가 출력된다.)

 

  1. Order 클래스
class Order:
 def __init__(self, id):
   self.id = id
   self.items = []


 def add(self, item):
   self.items.append(item)


 def revenue(self):
   return sum(item.price for item in self.items)

Order 클래스에서는 손님의 주문 번호와 주문 항목 리스트를 초기화한다. 주문 항목에 주문한 내용을 넣는 리스트와 카페의 수익을 관리한다.

 

  1. Cafe 클래스
class Cafe:
 def __init__(self):
   self.menu = {}
   self.orders = {}
   self.order_counter = 1
   self.revenue = 0


 def add(self, item):
   self.menu[item.name] = item
  def receive(self, customer, item_name):
   if customer in self.orders:
     order = self.orders[customer]
     return str(order.items[-1])
  
   else:
     if customer == "사장님":
       return f"{self.revenue}"


     else:
       order = Order(self.order_counter)
       order.add(self.menu[item_name])
       self.orders[customer] = order
       self.order_counter += 1
       self.revenue += self.menu[item_name].price
       return f"{order.id}"

메뉴에 새 항목을 넣거나, 주문을 받는 메소드가 정의되어 있다. 이 문제 풀이 코드에서의 핵심적인 부분이다. 만약 손님이 첫주문이라면 손님의 이름과 주문한 음식을 기억하고, 주문 번호를 1만큼 올린다. 만약 두번재 주문이라면 손님이 주문했던 음식의 이름을 이용해 음식 이름과 가격 값을 반환한다.

 

  1. 메뉴 추가
cafe = Cafe()
cafe.add(Menu("데자와", 1000))
cafe.add(Menu("삼다수", 500))
cafe.add(Menu("슬러시", 1750))
cafe.add(Menu("매운탕", 45000))

문제에 제시된 기본 메뉴들을 추가한다.

 

  1. 입력 처리
N, M = map(int, input().split())
for i in range(0, N*2+M, 1):
 name, item = input().split()
 print(cafe.receive(name, item))

입력 조건을 바탕으로 입력을 처리한다. 각 손님은 2번씩 카페에 방문하므로 손님의 방문 횟수에는 2를 곱하고, 사장님의 방문 횟수와 합친만큼 입력을 처리하도록 코드를 작성하였다.

 

Ⅲ. 문제에 적용한 개념 설명

 객체 지향 프로그래밍(Object-Oriented Programming, OOP)이란 프로그래밍에서 필요한 데이터를 추상화시켜 상태와 행위를 가진 객체로 만들고, 객체들간의 상호작용을 통해 로직을 구성하는 프로그래밍 방법이다.

 간단히 말해, 기존에는 로봇에게 앞으로 10m 갔다가 우로 30도 돌고 5m 직진 후 좌로 15도… 등과 같이 그때그때 컴퓨터가 해야할 일을 알려주는 것이 일반적인 방식이었다(절차 지향 프로그래밍의 경우). 그러나 객체 지향 프로그래밍에서는 로봇에게 이동 클래스를 정의하고, 클래스 내부에 직진이나 우회전, 좌회전 등을 담당하는 메서드를 추가해준 후에 이를 이용해서 나중에 로봇에게 해야할 일을 알려주는 것과 같다.

 다음은 객체 지향 프로그래밍와 관련된 예시이다.

 car라는 클래스를 정의하고, 그 안에 속성(__init__)과 메서드(cor, vel)를 생성하였다. 이후에 morning이라는 객체를 생성하고 메소드를 호출하였다.

 

1. 클래스(Class)

 객체를 정의하고 만들어 내기 위한 설계도 혹은 틀로써, 연관되어 있는 변수와 메서드의 집합을 의미한다.

 

2. 객체(Object)

 클래스를 기반으로 생성된 실체로, 실제 프로그램에서 사용되는 데이터를 의미한다. 위 코드에서 morning이 그 예시이다.

 

3. 인스턴스(Instance)

 클래스를 기반으로 생성된 객체가 메모리에 할당되어 실제 사용될 때 인스턴스라고 부른다. 즉, 이러한 특징을 통해 인스턴스는 객체에 포함된다고 할 수 있다.

 

4. 특징

가. 추상화(Abstraction)

 객체에서 공통된 속성과 행위를 추출하는 것을 말한다. 불필요한 정보는 숨기고, 중요한 정보만을 표현함으로써 프로그램을 간단하게 만들어 준다. 예를 들어, 사과나 파인애플, 수박 모두 과일의 한 종류이므로 이를 ‘과일'이라는 한 클래스로 추상화할 수 있다.

 추상화는 이용자들은 프로그래머들이 만든 객체를 더 쉽게 사용할 수 있도록 한다.

 

나. 캡슐화(Encapsulation)

 객체의 속성과 행위를 하나로 묶고, 실제 구현 내용 일부를 외부에 감추어 은닉하는 것을 말한다. 이를 통해 객체의 내부 구현을 숨겨 객체가 반드시 정해진 메소드를 통해 상호작용하도록 유도한다.

 객체지향에서는 클래스로 만들어 모듈화하기 때문에, 캡슐화를 이용하면 코드의 수정을 최소화시킬 수 있다.

 

다. 상속(Inhertiance)

 클래스의 속성과 행위를 하위 클래스에 물려주거나, 하위 클래스가 상위 클래스의 속성과 행위를 물려받는 것을 말한다. 새로운 클래스가 기존의 클래스의 데이터와 연산을 이용할 수 있게 한다.

 상속은 기존 클래스에 있던 기능을 가져와 재사용할 수 있으면서도, 새롭게 만든 기능을 추가할 수 있게 만들어준다. 이를 통해 코드의 중복을 막아 코드를 간단하게 해 유지보수를 더 수월하게 한다.

 

라. 다형성(Polymorphism)

 하나의 변수명, 함수명이 상황에 따라 다른 의미로 사용될 수 있게 해준다. 같은 형태더라도 다른 기능을 하는 것을 의미한다. 이를 통해 코드의 재사용과 유지보수가 용이하도록 한다.

 예를 들어 과일이라는 클래스로 정의되어 있는 사과와 바나나 모두 색깔이라는 속성이 정의되어 있다고 하자. 사과의 색은 (일반적으로) 빨간색인 반면, 바나나의 색은 노란색이다. 이런 것을 다형성이라고 한다.

 

마. 정보은닉(Information Hiding)

 캡슐화와 관련하여, 필요가 없는 정보는 외부에서 접근하지 못하도록 제한한다. 즉, 메소드를 통해서만 간접적으로 접근이 가능하도록 하는 것이다.

 정보 은닉은 잘못된 데이터가 들어가지 못하도록 사전에 방지해준다.

 

5. 장단점

 객체지향 프로그래밍의 장단점을 표로 정리하여 보면, 다음과 같다.

장점 단점
협업에 용이하다 개발 속도가 느리다
코드 재사용이 용이하다 실행 속도가 느리다
유지보수가 쉽다 용량이 크다

 

Ⅳ. 시행착오

 비슷한 변수명이 많아서 이를 코드에서 구분하는데 조금 어려웠다. 또, 하나의 클래스에서 다른 클래스를 사용하는데 이 부분에서 코드를 작성하는 것이 꽤 어려웠다.

 

Ⅴ. 소스 코드

전체 소스 코드는 다음과 같다.

class Menu:
 def __init__(self, name, price):
   self.name = name
   self.price = price


 def __str__(self):
   return f"{self.name} {self.price}"


class Order:
 def __init__(self, id):
   self.id = id
   self.items = []


 def add(self, item):
   self.items.append(item)
  def revenue(self):
   return sum(item.price for item in self.items)


class Cafe:
 def __init__(self):
   self.menu = {}
   self.orders = {}
   self.order_counter = 1
   self.revenue = 0


 def add(self, item):
   self.menu[item.name] = item
  def receive(self, customer, item_name):
   if customer in self.orders:
     order = self.orders[customer]
     return str(order.items[-1])
  
   else:
     if customer == "사장님":
       return f"{self.revenue}"


     else:
       order = Order(self.order_counter)
       order.add(self.menu[item_name])
       self.orders[customer] = order
       self.order_counter += 1
       self.revenue += self.menu[item_name].price
       return f"{order.id}"


cafe = Cafe()
cafe.add(Menu("데자와", 1000))
cafe.add(Menu("삼다수", 500))
cafe.add(Menu("슬러시", 1750))
cafe.add(Menu("매운탕", 45000))


N, M = map(int, input().split())
for i in range(0, N*2+M, 1):
 name, item = input().split()
 print(cafe.receive(name, item))

Ⅵ. 느낀점

 객체지향 프로그래밍 자체를 해본 적이 많지 않아 이와 관련해 개념을 익히고, 이를 응용하는 것이 많이 어려웠다. 또한 백준이나 코드업에 이와 관련한 문제가 잘 보이지 않아 직접 문제를 만드는 부분도 꽤나 어려웠다. 그렇지만 어려웠던만큼, 많이 뿌듯했던 기회가 될 수 있었다고 생각한다.