Futureskill - OOP

6 minute read

OOP

FutureSkill에서 푼 OOP문제를 정리한 내용입니다.

파이썬은 OOP 패러다임을 가지는 프로그래밍 언어입니다. OOP는 Object Oriented Programming으로 ‘객체지향 프로그래밍’ 이라는 뜻이고, OOP 패러다임을 가진다는 말은 class 기반으로 짜여진 코드라는 뜻 입니다.

OOP의 정의

OOP는 우리가 원하는대로 메모리 상의 객체의 형태를 지정할 수 있는 프로그래밍 방법론입니다.

Object 를 감싸고 있는 Class를 선언해 Object의 속성(함수도 포함)들을 감싸주면 그 Class를 통해 사용하게 될 변수가 식별하고 있는 메모리공간에 메모리주소를 통해 전달해주는 꼴입니다.

  • Procedural programming is about writing procedures or functions that perform operations on the data, while object-oriented programming is about creating objects that contain both data and functions.

→ 절차지향 프로그래밍은 데이터에 수행할 절차(procedures)또는 함수에 대해서 작성하는 프로그래밍인 반면, 객체지향 프로그래밍은 데이터와 함수를 모두 포함하는 object(객체)들을 만들어내는 것에 관한 프로그래밍입니다.

  • Object-oriented programming has several advantages over procedural programming:

객체지향 프로그래밍은 절차적 프로그래밍에 비해 다음과 같은 이점들이 있습니다.

  • OOP is faster and easier to execute

→ 실행하기에 빠르고 쉽다

  • OOP provides a clear structure for the programs

→ 프로그램에 명확한 구조를 제공한다.

  • OOP helps to keep the C++ code DRY “Don’t Repeat Yourself”, and makes the code easier to maintain, modify and debug

→ 같은 일을 반복하는 걸 막아주고 코드를 유지, 수정, 디버깅 하기 쉽게 해준다.

  • OOP makes it possible to create full reusable applications with less code and shorter development time

→ 적은 코드와 짧은 개발 시간으로도 완전히 재사용가능하게 해준다. (완전히 재사용 가능한 applications를 만들게 해준다.)

(동빈나님 블로그, w3Schools - C++ OOP 참고.)

퓨처스킬 피어리뷰로 확인한 ‘비선형적인핑크젤리’님의 답변도 참고해서 정리해보겠습니다.

앞서 말한대로 객체지향 프로그래밍은 절차지향 프로그래밍과 자주 비교됩니다. 이는 객체지향이 절차지향의 한계점으로부터 고안됐다고 볼 수도 있기 때문입니다. (이런식으로 비교를 하는 것이 이해에도 큰 도움이 됩니다.)

절차지향은 말 그대로 Procedure, 순차적인 처리위주로 진행하는 프로그래밍 방식으로 C언어 등이 있습니다. 하지만 시간이 지날수록 실제 세상의 대상(Object)을 본 딴 프로그래밍이 필요하게 됐고, 객체지향 프로그래밍이 탄생하게 됐습니다.

Python과 OOP

우선 ‘특수한데이터엔지니어’ 님의 답변을 참조하겠습니다.

  • 추상화 : 객체에서 공통된 속성과 행위를 추출함
  • 캡슐화 : 세부 구현 내용을 몰라도 원하는 결과를 얻을 수 있음
  • 상속성 : 하나의 객체에서 구현된 속성이나 행동 내용을 다른 객체가 물려받아 사용할 수 있음
  • 다형성 : 하나의 이름으로 여러 명령을 수행할 수 있는 것.

이러한 Python에서 OOP의 특징을 토대로 다음과 같은 장점을 생각해볼 수 있습니다.

  • OOP is faster and easier to execute

→ 실행하기에 빠르고 쉽다

  • OOP provides a clear structure for the programs

프로그램에 명확한 구조를 제공한다.

  • OOP helps to keep the Python code DRY “Don’t Repeat Yourself”, and makes the code easier to maintain, modify and debug

→ 같은 일을 반복하는 걸 막아주고 코드를 유지, 수정, 디버깅 하기 쉽게 해준다.

  • OOP makes it possible to create full reusable applications with less code and shorter development time

→ 적은 코드와 짧은 개발 시간으로도 완전히 재사용가능하게 해준다. (완전히 재사용 가능한 applications를 만들게 해준다.)

Python class

본격적으로 객체를 만들고, 이를 위해 클래스를 정의하는 구현과정을 보겠습니다. Python에서는 class라는 키워드를 통해 클래스를 정의합니다. 이 예제에서는 Notebook개념을 클래스로 정의하고, 객체를 생성하려고 합니다. 다음 조건에 맞춰 Notebook class를 만들고 객체를 생성해보겠습니다.

  • Notebook(Macbook, Surface Pro, ..)들은 여러 제조사에서 생산됩니다.
  • 같은 제조사에서 생산된 Notebook들은 여러 모델명을 가집니다.
  • 같은 모델의 Notebook도 서로 다른 하드웨어 스펙을 가집니다.
  • manufacturer 변수에 제조사 이름을 저장합니다.
  • model 변수에서 해당 제조사가 생산한 노트북의 모델명을 저장합니다.
  • memory1, memory2 변수에 각각 RAM의 용량과 SSD의 용량을 GB 단위로 저장합니다. (e.g. RAM - 8GB, SSD - 256GB → memory1 = 8, memory2 = 256)
class Notebook: # Notebook class를 정의합니다.
    def __init__(self, manufacturer, model, memory1, memory2):
        # manufacturer, model, memory1, memory2 변수 정의
        self.manufacturer = manufacturer
        self.model = model
        self.memory1 = memory1
        self.memory2 = memory2

위와 같이 간단하게 정의할 수 있습니다. 여기서 정의된 __init__()함수는 클래스가 생성(initiated)될 때 마다 실행되는 생성자 함수입니다. 객체의 properties에 값을 주거나, 객체가 만들어질 때 필요한 operation을 객체에 부여합니다.

(All classes have a function called init(), which is always executed when the class is being initiated.

Use the __init__() function to assign values to object properties, or other operations that are necessary to do when the object is being created)

  1. 다음으로 위 코드에 노트북에 설치된 OS를 확인하는 is_UNIX함수를 만들어보겠습니다.
  2. 그리고 모델명을 출력해주는 print_model method 또한 만들어보겠습니다.
UNIX = set(["macOS X", "CentOS", "Ubuntu", "iOS", "Android", "webOS"])

class Notebook:
    def __init__(self, manufacturer, model, memory1, memory2, os):
        self.manufacturer = manufacturer
        self.model = model
        self.memory1 = memory1
        self.memory2 = memory2
        self.os = os

    def is_UNIX(self):
        return self.os in UNIX
		
		def print_model(self):
    print(f"Model : {self.model}")

nb = Notebook("Apple", "MacBook Pro", 16, 512, "macOS X")
nb2 = Notebook("Dell", "Dell XPS", 16, 1024, "Windows 10")

# nb가 UNIX인지 아닌지 확인하는 코드
print(nb.is_UNIX())
# nb2가 UNIX인지 아닌지 확인하는 코드
print(nb2.is_UNIX())
  1. 처음에 풀 때, method를 만들어야한다는 내용을 못 보고 os정보를 저장하는 변수를 만들고, 따로 set에 있는 함수를 사용해 풀었습니다. 따로 method 구현없이 self.os = os를 사용하고 nb.os in UNIX를 출력했습니다.
    • Objects can also contain methods. Methods in objects are functions that belong to the object.

    → Objects 는 method들을 포함할 수 있는데, method들은 object에 속해있는 함수들이다.

    • The self parameter is a reference to the current instance of the class, and is used to access variables that belongs to the class.

    → self 파라미터는 class에 있는 인스턴스 변수들을 참조하는 파라미터로 클래스에 속해있는 변수에 접근할 때 사용된다.

    • It does not have to be named self , you can call it whatever you like, but it has to be the first parameter of any function in the class.

    → self 라고 이름 붙여질 필요는 없지만, class내 생성되는 함수의 첫번째 파라미터에 이름을 따라야한다.

    • 클래스 변수와 인스턴스 변수의 차이 :

    → 여러 인스턴스 간에 서로 공유해야 하는 값은 클래스 변수를 통해 바인딩한다. 왜냐하면 파이썬은 인스턴스의 네임스페이스에 없는 이름은 클래스의 네임스페이스에서 찾아보기 때문에 이러한 특성을 이용하면 클래스 변수가 모든 인스턴스에 공유될 수 있다.

    (w3schools(https://www.w3schools.com/python/python_classes.asp), https://wikidocs.net/1744 참고)

  2. 출력(print)과 반환(return)을 착각하고 return 후 print 를 했다가 수정했습니다. print("Model :",self.model) 처럼 간단하게 출력을 할 수도 있습니다.

클래스 상속(class inheritance)

클래스의 상속은 상위 개념에서 정의한 내용자체를 그대로 상속받아, 하위 개념에도 적용하기 위해서 사용합니다.

Notebook class를 상속받는 두 개의 새로운 하위 class인 MacBook class와 DellLaptop class를 선언하는 코드를 작성해보겠습니다.

UNIX = set(["macOS X", "CentOS", "Ubuntu", "iOS", "Android", "webOS"])

class Notebook: # Notebook class를 정의합니다.
    def __init__(self, manufacturer, model, memory1, memory2, os):
        # manufacturer, model, memory1, memory2 변수 정의
        self.manufacturer = manufacturer
        self.model = model
        self.memory1 = memory1
        self.memory2 = memory2
    # os 정보를 저장하는 변수
        self.os = os

class MacBook(Notebook):
    def __init__(self, manufacturer, model, memory1, memory2, os, period, size):
        Notebook.__init__(self, manufacturer, model, memory1, memory2, os)
        self.period = period
        self.size = size

class Dell_Laptop(Notebook):
    def __init__(self, manufacturer, model, memory1, memory2, os, naming, size):
        super().__init__(manufacturer, model, memory1, memory2, os)
        self.naming = naming
        self.size = size

MacBook class선언처럼 Notebook.__init__ 을 사용해 상속받을 수 있습니다. 이 경우 필요한 period, size 변수는 추가적으로 설정해줬습니다. 자세히 보면 대부분의 성질(properties) 들을 그대로 상속받기 때문에 좀 더 간편하게 super().__init__을 사용해보겠습니다.

Dell_Laptop class 에서 처럼 super().__init__ 후 self 없이 변수들만 적어주고 추가적으로 필요한 성질만 선언해줬습니다.

하위클래스에 __init__을 추가하면 더 이상 상위클래스의 __init__을 상속받는 것이 아니라 override 하게 됩니다. 이를 상속받기 위해서는 위와 같은 방법을 사용해야 합니다.

Method Overriding

하위클래스에서 필요에 따라 상위 클래스에서 정의한 method를 다시 정의해서 사용합니다. 예를 들어서 하위 클래스에서 모델을 출력할 때는 세부모델명 까지 포함해서 출력한다고 가정하고, print_model 함수를 재정의해보겠습니다.

UNIX = set(["macOS X", "CentOS", "Ubuntu", "iOS", "Android", "webOS"])

class Notebook: # Notebook class를 정의합니다.
    def __init__(self, manufacturer, model, memory1, memory2, os):
        # manufacturer, model, memory1, memory2 변수 정의
        self.manufacturer = manufacturer
        self.model = model
        self.memory1 = memory1
        self.memory2 = memory2
    # os 정보를 저장하는 변수
        self.os = os
    def is_UNIX(self):
        return self.os in UNIX

    def print_model(self):
        print("Model: ", self.model)

class MacBook(Notebook):
    def __init__(self, manufacturer, model, memory1, memory2, os, period, size):
        Notebook.__init__(self, manufacturer, model, memory1, memory2, os)
        self.period = period
        self.size = size
    def print_model(self):
        print("MacBook Model : {}({} inch, {})".format(self.model, self.size, self.period))

class DellLaptop(Notebook):
    def __init__(self, manufacturer, model, memory1, memory2, os, naming, size):
        super().__init__(manufacturer, model, memory1, memory2, os)
        self.naming = naming
        self.size = size
    def print_model(self):
        print("DellLaptop Model : {} {} {}".format(self.model, self.size, self.naming))

macbook = MacBook("Apple", "MacBook Pro", 16, 512, "macOS X", "Late 2015", 15)
delllaptop = DellLaptop("Dell", "Dell XPS", 16, 1024, "Windows 10", 9500, 15)

macbook.print_model()
delllaptop.print_model()

출력을 예쁘지 않게 해서 복잡해보여도 간단하게 method를 override할 수 있습니다.

나중에 기회가 되면 C++ 로 class를 생성하는 과정도 정리해야겠습니다.

Leave a comment