[Python] 12. 객체지향(Object Oriented)

Wonder_Land🛕·2022년 6월 27일
0

[Python]

목록 보기
12/12
post-thumbnail

1.객체지향(Object Oriented)의 이해
2. 클래스(Class) 정의
3. 클래스(Class)와 인스턴스(Instance)의 특징
4. 클래스 상속(Inheritance)
5. Q&A
6. 마치며


우리는 지금까지 파이썬이 제공하는 클래스의 기능을 이용하여 손쉽게 코딩을 할 수 있었습니다.

이건 바로, '코드 재사용'의 효과이죠 😊

그렇다면, 우리가 만든 코드를 재사용할 수 있다면 어떨까요?

불필요한 작업이 축소 되고, 효율성이 향상되는 효과로,
개발의 생산성을 향상할 수 있겠죠.

객체지향 프로그래밍은 이런 것들을 가능하게 해줌으로써,
대규모 프로그램을 효율적으로 코딩할 수 있게 해줍니다.


1. 객체지향(Object Oriendted)의 이해

1) 미리 알아야 하는 개념

(1) 객체(Object)란?

  • 객체(Object)
    : 변수(Variable)과 메서드(Method)를 서로 연관된 것들끼리 묶은 것

  • 변수(Variable) : 값을 가집니다.
  • 메서드(MEthod) : 실행 코드를 가집니다.

예를 들어, 자동차를 볼까요?

변수로는 연료량, 속도계 등이 있겠죠.
Method로는 주행기능 등이 있겠죠.

그런데 주행기능은 변수인 연료량, 속도계 등에 영향을 주는데요,
Method는 변수와 연관된 기능인거죠.

이처럼, 서로 연관된 변수(Variable)와 메서드(Method)를 잘 파악해 묶어 객체를 형성하는 것이 중요합니다.

따라서, 객체(Object)는 하나의 레고블럭처럼 생각할 수 있는데요,
이는 객체지향 프로그래밍의 매우 중요한 개념이며,
다음과 같은 매우 중요한 특징 두 가지를 가집니다.

  1. 부품화 : 객체를 하나의 부품처럼 사용할 수 있습니다.

  2. 재사용성 : 객체는 여러 번 재사용할 수 있습니다.


(2) 클래스(Class)란?

위에서 살펴본 객체는 '클래스(Class)'를 통해서 만들 수 있는데요, 자세히 살펴봅시다.

  • 클래스(Class)
    : 부품 객체를 만들기 위한 템플릿 / 설계도 / 청사진
    : 추상화(Abstraction)의 과정을 통해 만들어집니다.

위에서 예시로 본 자동차를 클래스로 본다면,
자동차 객체가 가져야할 변수는 연료량, 속도가 되고, Method로는 주행기능이 있겠네요.


(3) 객체지향의 구성요소

  • 객체지향의 구성요소
    : 클래스(Class) + 객체(Object) + 메서드(Method)
  • 클래스(Class)
    : 같은 영역에 속하는 속성(attribute)과 행위(behavior)를 정의한 것입니다.
    : 객체지향 프로그램의 기본적인 사용자 정의 데이터 타입입니다.

  • 객체(Object) = 인스턴스(Instance : 실체화된 객체 == 실제로 사용된 객체)
    ex) 객체 : 자동차, 트럭은 자동차의 인스턴스
    : 메모리에 로딩된 클래스를 통해, 클래스를 템플릿으로 하여 메모리 상에 생성된 정보입니다.
    : 자신 고유의 속성을 가지며 클래스에서 정의한 행위를 수행합니다.
    : 객체의 행위는 클래스에서 정의된 행위에 대한 정의를 공유함으로써, 메모리를 효율적으로 사용합니다.

  • 메서드(Method) = 메시지(Message)
    : 클래스로부터 생성된 객체 사용 시, 객체에 명령을 내리는 행위입니다.
    ('객체가 가지고 있는 메서드를 호출한다')
    (= '객체에 메시지를 전달한다')
    : 한 객체의 속성을 조작할 목적으로 사용합니다.
    : 객체 간의 통신은 메시지 전달을 통해 이루어집니다.


2) 객체지향 프로그래밍

  • 객체지향 프로그래밍(Object Oriented Programming)
    : 상태와 행위로 이루어진 객체를 만든 다음, 해당 객체들을 레고 블럭처럼 조립하여, 하나의 프로그램을 만드는 것
    즉, 객체를 만들고, 객체를 이용해 문제를 해결하려는 프로그래밍 방법입니다.

3) 객체지향 프로그램의 특징

(1) 추상화 (Abstraction)

  • 추상화(Abstraction)
    : 객체에서 공통된 속성과 행위를 추출하여 type을 정의하는 과정입니다.

예를 들어, 학생 A, B, C가 있다고 해봅시다.

이들은 학생이라는 Type으로 정의할 수 있습니다.

공통된 속성으로는, 학번, 이름, 주민번호, 학과 등이 있겠죠.

공통된 행위로는, 수강 신청, 수강 취소 등이 있겠죠.

쉽게 말하면,
불필요한 정보는 숨기고 중요한 정보만 표현해 프로그램을 간단히 만드는 것입니다.

(1.1) 추상 데이터 타입

  • '추상 데이터 타입'
    : 추상화 과정을 통해 만들어진 Type입니다.

추상 데이터 타입은

  1. 데이터 타입의 표현과 연산은 캡슐화

  2. 접근 제어를 통해 데이터의 정보를 은닉

할 수 있습니다.

즉,

  • 추상 데이터 타입은, '클래스(Class)'
  • 추상 데이터 타입의 인스턴스는, '객체(Object)'
  • 추상 데이터 타입에서 정의된 연산은, '메서드(Method)'

입니다.


(2) 상속 (Inheritance)

  • 상속 (Inheritance)
    : 새로운 클래스가 기존의 클래스의 데이터와 연산을 이용할 수 있게 하는 기능입니다.
    : 상위 클래스에서 상속하거나, 하위 클래스에서 상속받음으로써, 속성과 행위를 공유합니다.

상속 관계의 클래스는 다음과 같이 부르기도 합니다.
(상속하는 클래스) - (상속받는 클래스)

: (상위 - 하위), (부모 - 자식), (슈퍼 - 서브), (기반 - 파생)

상속(Inheritance)을 통해

  1. 하위 클래스에서 프로그램의 요구에 맞추어 클래스를 수정

  2. 클래스 간의 종속 관계를 형성하여 객체를 조직화

할 수 있습니다.

상속의 효과로는

  1. 재사용으로 인해 코드가 줄어듭니다.
    (속성이나 행위를 다시 정의할 필요가 없기 때문이죠)

  2. 범용적인 사용을 가능하게 합니다.
    (예를 들어, object 타입의 매개변수에는 string / int 객체가 쓰여도 괜찮습니다.)
    (string/ int 모두 object를 상속받기 때문이죠)

  3. 자료와 Method의 자유로운 사용 및 추가를 할 수 있습니다.


(3) 다형성 (Polymorphism)

  • 다형성 (Polymorphism)
    : 다양한 형태로 나타날 수 있는 특징입니다.
    : 어떤 한 요소에 여러 개념을 넣어 놓는 것입니다.

객체지향 프로그래밍은,

  1. 하나의 클래스에서 같은 이름의 행위를 여러번 정의하거나,

  2. 상위 클래스의 행위를 하위 클래스에서 재정의 할 수 있기 때문에
    다형성을 가집니다.

(3.1) 오버라이딩(Overriding)

  • 오버라이딩(Overriding)
    : 같은 이름의 Method가 여러 클래스에서 다른 기능을 하는 것입니다.

(3.2) 오버로딩(Overloading)

  • 오버로딩(Overloading)
    : 같은 이름의 Method가 인자의 개수나 자료형에 따라, 다른 기능을 하는 것입니다.

(3.3) 매서드 오버라이딩(Method Overriding)

  • 매서드 오버라이딩(Method Overriding)
    : 상속으로 물려받은 자료나 Method를 그대로 사용하지 않고, 하위 클래스에서 새로 정의해 사용하는 방법입니다.

즉, 동일한 이름의 Method가 다른 클래스에서 다르게 작동되는 것 입니다.

단, 상위 클래스의 Method와 동일한 서명(Signature : 매개변수의 타입, 개수, 반환 타입)을 가져야 합니다.

(3.4) 매서드 오버로딩(Method Overloading)

  • 매서드 오버로딩(Method Overloading)
    : 클래스 내부에, 동일한 이름의 행위를 여러 개 정의하는 것

즉, 동일한 이름의 Method가 같은 클래스에서 다르게 작동되는 것입니다.

Method 이름을 하나로 통일 가능하며,
같은 이름의 Method에 여러 종류의 매개 변수를 받을 수 있습니다.

따라서,
Method의 1) 이름은 같아야 하며, 2) 매개변수의 타입과 수는 서로 달라야 합니다.

단, 반환 타입은 상관없습니다.


2. 클래스(Class) 정의

  • 클래스 정의
class (클래스명):
...

클래스의 코드 블록 안에 field와 Method를 정의해 사용할 수 있습니다.

  • 객체 생성
(변수) = (클래스명)()	# 생성자 Method

생성자 Method : 클래스 이름과 동일한 Method입니다.

다음은 Person이라는 클래스를 정의하고,
member라는 instance(객체)를 생성하는 코드입니다.

class Person:	# 클래스 정의
    pass

member = Person()    # 객체 생성 : 생성자 Method

if isinstance(member, Person):
    print(f"member는 Person의 instance입니다.")

[Result]
member는 Person의 instance입니다.


1) 객체의 생성

  • 생성자 메서드(Constructor Method)
    : 객체를 생성하기 위해 호출되는 함수입니다.
    : 클래스의 이름과 동일합니다.
    : 실행 시 __init__ Method가 실행됩니다.
class (클래스명):
    def __init__(self, 매개변수 목록):
        ...

2) 객체의 소멸

  • 소멸자 메서드(Deconstructor Method)
    : 객체를 소멸시키기 위해 호출되는 함수입니다.
    : 객체가 소멸되기 전에 호출되고, 매번 정의할 필요는 없습니다.
    : 실행 시 __del__ Method가 실행됩니다.
class (클래스명):
	...
    def __del__(self):
        ...

소멸자 Method는 생성자와 다르게,
self를 제외한 매개변수는 사용하지 않습니다.


3) self

  • self
    : 클래스에 의해 생성된 객체 공간을 가르키는 식별자
    : 객체 공간의 field와 Method에 접근할 경우, self.(식별자) 형식으로 사용합니다.
    : Keyword는 아니지만, 관습적으로 self라는 이름으로 사용합니다.

다음은 Person이라는 클래스에서 생성자와 소멸자를 정의하고,
member라는 객체를 생성하여 정보를 출력하는 코드입니다.

class Person:
    def __init__(self, name, age):	# 생성자 (Constructor)
        self.name = name
        self.age = age
        print("객체가 생성되었습니다.")

    def __del__(self):	# 소멸자 (Deconstructor)
        print("객체가 제거되었습니다.")

member = Person("Wonder_Land", 20)    # 생성자 Method

print(member.name, member.age, sep = " ")
print(dir(member))

[Result]
객체가 생성되었습니다.
Wonder_Land 20
['__class__', '__del__', '__delattr__', ..., 'age', 'name']
객체가 제거되었습니다.


3. 클래스(Class)와 인스턴스(Instance)의 특징

1) 인스턴스 메서드(Instance Method)

  • 인스턴스 메서드(Instance Method)
    : self가 가르키는 객체의 field 정보에 접근해, 특정 기능을 수행하도록 정의된 Method입니다.

다음은 위에서 살펴본 코드에서,
to_str이라는 인스턴스 Method를 정의하는 코드입니다.

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        print("객체가 생성되었습니다.")

    def __del__(self):
        print("객체가 제거되었습니다.")

    def to_str(self):   # 인스턴스 메서드 (Instance Method)
        return f"{self.name} {self.age}"

member = Person("Wonder_Land", 20)    # 생성자 Method

print(member.to_str())	# 인스턴스 매서드 호출

[Result]
객체가 생성되었습니다.
Wonder_Land 20
객체가 제거되었습니다.


2) 인스턴스 변수(Instance Variable)

  • 인스턴스 변수(Instance Variable)
    : 클래스 내에서 self.(변수) 형식를 가지는 변수입니다.
    : 객체마다 가지고 있는 객체 고유의 정보입니다.

(1) 멤버 Field의 접근 제한이 없는 경우

다음은 위에서 살펴본 코드에서,
Field의 접근 제한이 없는 경우입니다.

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        print("객체가 생성되었습니다.")

    def __del__(self):
        print("객체가 제거되었습니다.")

    def to_str(self):   # 인스턴스 메서드 (Instance Method)
        return f"{self.name} {self.age}"

외부접근의 제한을 막기 위해,
변수 name, age캡슐화된 필드로 만드는 것이 필요할 수 있습니다.

또한 위의 코드처럼,
외부에 노출된 field는 입력 시 유효성 검사를 할 수 없으므로 잘못된 값을 저장할 수 있습니다.

예를 들어, age에 음수가 들어올 수도 있는거죠.

따라서, 입력 데이터의 검증을 위해,
적절한 멤버 field의 접근 제한이 필요합니다.


(2) 멤버 Field의 접근 제한이 있는 경우

Python에서는
외부에서 field에 접근하는 것을 제한하는
인스턴스 변수의 접근 제한 기능을 지원하고 있습니다.

  • 인스턴스 변수의 접근 제한 기능
    인스턴스 변수 앞에 __를 붙여줌으로써, 프라이빗 필드(Private Field)를 생성할 수 있습니다.
class (클래스명):
	...
    self.__(변수명)

이렇게 프라이빗 필드를 만들어줄 때는,
getter / setter Method의 제공 여부에 대한 고민이 필요합니다.

  1. gettersetter 모두 제공
  2. getter만 제공
  3. setter만 제공

(3) gettersetter

  • getter Method
    : 멤버를 읽어오는 Method

  • setter Method
    : 멤버를 변경하는 Method

다음 코드에서는,
인스턴스 변수 name, age를 프라이빗 필드로 구성하고,
gettersetter를 활용합니다.

class Person:
    ...
    def get_name(self): # getter : __name 필드의 값을 반환
       return self.__name

    def get_age(self):  # getter :  __age 필드의 값을 반환
        return self.__age

    def set_age(self, age):  # setter : __age 필드의 값을 변경
        if age < 0:
            raise TypeError("나이는 0 이상의 값만 허용")
        self.__age = age

member = Person("Wonder_Land", 20)    # 생성자 Method
member.set_age(-20)
print(member.to_str())

[Result]
TypeError: 나이는 0 이상의 값만 허용

get_name Method는,
프라이빗 필드인 __name를 반환하는 getter Method이며,
__name field에서는 getter Method만 제공하는 것을 알려줍니다.

set_age Method는,
프라이빗 필드인 __age를 변경하는
setter Method이며,
만약 음수가 들어오면, TypeError를 일으킵니다.


(4) 데코레이터(Decorator) 기능

Python에서는,
getter와 setter를 대신할 수 있는 기능인
'데코레이터(Decorator)'를 지원합니다.

  • 데코레이터(Decorator)
    : 변수 이름과 같은 Method를 만들어 사용할 수 있습니다.
    : @property를 이용합니다.

  • getter로 사용할 때
class (클래스):
	...
    @property	# getter : 데코레이터 (Decorator)
    def (변수)(self):
    	...
  • setter로 사용할 때
class (클래스):
	...
    @property의 이름.setter	# setter : 데코레이터 (Decorator)
    def (변수)(self):
    	...

다음은 위의 코드를,
데코레이터 기능을 이용하여 만들어보았습니다.

name Method는,
Method이지만 변수처럼 사용할 수 있으며,
__name 필드값을 반환하는 getter의 역할입니다.

age Method는,
Method이지만 변수처럼 사용할 수 있으며,
__age 필드값을 반환하는 getter
__age 필드값을 변경하는 setter 역할입니다.

class Person:
    ...
    
    # 데코레이터 (Decorator) - getter
    @property	
    def name(self):
        return self.__name

	# 데코레이터 (Decorator) - getter
    @property
    def age(self):
        return self.__age
        
    # 데코레이터 (Decorator) - setter
    @age.setter
    def age(self, age):
        if age < 0:
            raise TypeError("나이는 0 이상의 값만 허용")
        self.__age = age

member = Person("Wonder_Land", 20)    # 생성자 Method
member.age = 30 # 변수처럼 사용
print(member.to_str())

[Result]
객체가 생성되었습니다.
Wonder_Land 30
객체가 제거되었습니다.


3) 클래스 변수(Class Variable)

  • 클래스 변수(Class Variable)
    : 클래스 내에서 (클래스명).(변수) 형식를 가지는 변수입니다.
    (인스턴스 변수(Instance Variable)는, 클래스 내에서 self.(변수) 형식를 가지는 변수입니다.)

(1) 클래스 변수의 정의

class (클래스명):
	(클래스변수) = ()
    ...

(2) 클래스 변수의 접근

(클래스명).(클래스변수)

다음은 클래스 변수를 활용한 예시입니다.

class Person:
    count = 0   # 클래스 변수 (Class Variable)

    def __init__(self, name, age):
        self.__name = name
        self.__age = age
        Person.count += 1	# 클래스 변수의 접근
        print("객체가 생성되었습니다.")

   ...

member = Person("Wonder_Land", 20) 
print(member.to_str())
print(f'Person 클래스의 instance는 {Person.count}개 입니다.')

[Result]
객체가 생성되었습니다.
Wonder_Land 30
Person 클래스의 instance는 1개 입니다.
객체가 제거되었습니다.


4) 클래스 메서드(Class Method)

  • 클래스 메서드(Class Method)
    : 클래스가 소유하는 Method입니다.

(1) 클래스 메서드의 정의

class (클래스명):
	...
    @classmethod
    def (클래스메서드)(cls, (매개변수목록)):
    	...

데코레이터(Decorator)인 @classmethod를 사용하며,
cls를 통해 클래스 자신에 대한 참조를 전달합니다.

(2) 클래스 변수의 사용

(클래스명).(클래스매서드)(매개변수목록)

다음은 클래스 메서드를 활용한 예시입니다.

class Person:
    ...
    @classmethod	# 클래스 메서드
    def get_info(cls):
        return f"Person 클래스의 instance는 총 {cls.count}개 입니다."

member = Person("Wonder_Land", 20)
print(Person.get_info())

[Result]
객체가 생성되었습니다.
Person 클래스의 instance는 1개 입니다.
객체가 제거되었습니다.

클래스 메서드인 get_info()에서
cls를 인자로 하여, 클래스 자신을 참조하게 됩니다.

따라서 cls.countPerson.count와 동일하다고 볼 수 있습니다.


조금 헷갈릴수도 있는데요,

인스턴스 변수 / 메서드는 Instance 단위의 변수 / 메서드를 사용할 때,
클래스 변수 / 메서드는 Class 단위의 변수 / 메서드를 사용할 때
각각 사용한다고 생각하면 됩니다.

그리고 getter / setter, 데코레이터는 인스턴스 변수를 다루기 위해 사용한다고 생각하면 되겠죠?


5) 연산자 오버로딩(Operator Overloading)

Python에서 기본적으로 제공하는 클래스들은,
클래스 내에 정의된 각 Method은 기본적으로 연산자들이 mapping되어 있습니다.

하지만, 우리가 직접 만드는 사용자 정의 클래스에서는 그렇지 않기 때문에(ㅠㅠ),
연산자를 중복해서 정의해줘야 합니다.

  • 연산자 오버로딩(Oerator Overloading)
    : 기존에 사용하는 연산자를 재정의 하여 사용자 정의 클래스로 사용하는 것입니다.

다음은 비교연산자에 대해 오버로딩을 진행한 예시입니다.

class Person:
    ...
    def __gt__(self, other):	# Greater than
        return self.age > other.age

    def __ge__(self, other):	# Greater than or Eqaul
        return self.age >= other.age

    def __lt__(self, other):	# Less than
        return self.age < other.age

    def __le__(self, other):	# Less than or Eqaul
        return self.age <= other.age

    def __eq__(self, other):	# Eqaul
        return self.age == other.age

    def __ne__(self, other):	# Not Eqaul
        return self.age != other.age

member1 = Person("A", 20)
member2 = Person("B", 30)

# 비교 연산자 (__le__) 사용
print(member1 <= member2)

[Resutl]
True

비교연산자 말고도,
+, -, *와 같은 연산자도 오버로딩할 수 있습니다.


6) __str()__ 메서드

  • str() Method
    : __str()__ Method를 통해, str()함수에 객체를 전달하여 문자열로 변환할 수 있습니다.
class Person:
    ...
    
    # __str()__ 매서드를 통해 문자열 반환
    def __str__(self):
    	return f"{self.__name} {self.__age}"

member = Person("A", 20)

print(str(member)

[Resutl]
A 20


4. 클래스 상속(Inheritance)

1) 클래스 상속(Inheritance)

  • 클래스 상속(Inheritance)
    : 부모 클래스의 동작을 자식 클래스에서 재사용, 확장, 수정하는 것을 말합니다.
class (클래스명)(부모클래스)
  • 부모 클래스(Base / Parenet Class)
    : 멤버가 상속되는 클래스
  • 자식 클래스(Derived / Child Class)
    : 멤버를 상속하는 클래스

다음은 부모 클래스인 Parent
자식 클래스인 Child를 작성하는 코드입니다.

class Parent:
    def __init__(self, family_name):
        self.__family_name = family_name
        print("Parent 클래스의 __init__ 실행")

    @property
    def family_name(self):
        return self.__family_name

class Child(Parent):    # Parent 클래스를 상속
    def __init__(self, first, last):
        Parent.__init__(self, last)
        # super().__init__(last)	# super() 이용
        self.__first = first
        print("Child 클래스의 __init__ 실행")

child = Child("Wonder", "Land")

[Result]
Parent 클래스의 __init__ 실행
Child 클래스의 __init__ 실행

이 때, Parent.__init__(self, last)
클래스 이름을 사용하지 않고,
부모 클래스를 반환하는 super() 호출과 생성자 호출을 결합해 동일한 효과를 얻을 수 있습니다.


2) 매서드 오버라이딩(Method Overriding)

  • 매서드 오버라이딩(Method Overriding)
    : 부모 클래스의 Method와 동일한 서명(Signature)를 가진 Method를, 자식 클래스에서 다시 정의해 사용하는 것입니다.
class Parent:
    ...
    def print_info(self):
        print(f'Parent : {self.family_name}')

class Child(Parent):    # Parent 클래스를 상속
    ...
    def print_info(self):	# 매서드 오버라이딩
        Parent.print_info(self)
        # super.print_info()
        print(f'Child: {self.name}')

child = Child("Land", "Wonder")
child.print_info()

[Result]
Parent 클래스의 __init__ 실행
Child 클래스의 __init__ 실행
Parent : Wonder
Child: Wonder Land

이 때, child.print_info()
Parent Class의 print_info()가 아닌,
Child Class의 print_inof()를 실행합니다.

또한 이 때도 마찬가지로,
부모 클래스를 반환하는 super() 호출을 이용해 동일한 효과를 얻을 수 있습니다.


5. Q&A

-


6. 마치며

오늘은 'Programming Beginner' - '파이썬 프로그래밍 기초(2) 파이썬의 기본 응용'의 마지막 수업인,
객체지향을 배웠습니다.

이론적인 부분이 많아서 헷갈릴 수 있지만,
실제로 사용해보면 금방 알 수 있는 개념입니다.

이 객체지향을 배우기 전과 후에 프로그래밍을 한 결과를 보면 상당히 다를 수 있는데요,

객체를 이용해 조직적으로 짜 본 프로그램은
훨씬 보기도 좋고 이해하기도 훨씬 수월하죠

객체 단위로 프로그래밍을 하다보면,
객체 단위로 생각할 수 있고,
이는 독자로 하여금 이해하기 쉽게하기 때문이죠.

객체지향의 특징 중 하나인 상속도 마찬가지죠.
프로그램이 훨씬 구체적이고 조직적입니다.

이제 Python의 기본적인 이론에 대해 공부해봤는데요,
백준 문제들을 풀면서 Python의 감을 잃지 않고 더 익숙해져야 겠네요!

아마 다음 공부는 'C언어'가 되지 않을까 싶네요.

C -> C++ -> JAVA 순으로 공부하고,
언어들의 기본적인 사용법을 배우고 난 후,
(사실 이미 배워봤고, Python도 다시 복귀해봤으니 아마 더 빨리 복귀할 수 있을 것 같아요! 😊)

자료구조 / 알고리즘을 Python, C++로 배워볼까 합니다.

그리고 그 다음은 정말 분야(전공)를 하나 정해서 공부해야겠어요.
(복학하기 전까지 할 수 있겠죠...?? 🤣)

그 때까지 열심히 해야겠네요...
안 놀게요...
할게 많아요 😂

[Reference] : 위 글은 다음 내용을 참고, 인용하여 만들어졌습니다.

  • 전반적 내용 : 삼성 SW Expert Academy
profile
아무것도 모르는 컴공 학생의 Wonder_Land

0개의 댓글