본문 바로가기

python

python 객체지향 개발 4가지 특성

객체지향 개발 4가지 특성

객체지향 프로그램을 다루기 위해선 4가지 기본개념이 잡혀있어야한다.

이 네가지 기본을 반드시 지켜야 좋은 객체지향코드가 나온다.

만약, 이 중 한 가지라도 무너지면 객체 지향의 설계가 무너진다.

(1) 상속

일반적으로 우리는 상속이라는 말을 많이 들어봤을것이다.

가족간에 재산을 상속한다는 말처럼 프로그래밍에서도

한사람이 다른사람에게 권리와 의무등 일체를 넘겨주는것으로 생각하면 된다.

그럼 이제 프로그래밍적으로 알아보면 상속을 해주는 클래스는 부모 클래스,

상속을 받는 클래스는 자식 클래스라고 한다.

그리고 자식 클래스는 부모 클래스에게 모든 것을 물려 받는다.

물려 받는것은 당연히 변수와 함수일것이다.

그리고 필요하다면 부모 클래스에서 상속 받은 변수와 함수를 바꿔 사용할 수 도 있다.

이 처럼 바꿔 사용하는 것을 오버라이딩(overriding) 이라고부른다.

코드로 예시를 들어보면

부모(Parents) 클래스를 상속받아 자식(Child) 클래스를 만들어 보는 코드를 작성해 본다.

class Parents:
    def __init__(self, money, home):
        self.money = money
        self.home = home

    def to_give(self):
        print(f"너희에게 물려줄것은 {self.money}, {self.home} 이것이다.")

    def __str__(self):
        return f"난 {self.money}, {self.home}을 상속 받았다!"


class Child(Parents):
    def __init__(self, money, home, car):
        super().__init__(money, home)
        self.car = car

    def to_give(self):
        super().to_give()
        print(f"그래서 상속 받은걸로 {self.car} 샀다.")

    def like(self):
        return "그래서 좋다."     

if __name__ == "__main__":
    c = Child(100000000, '아이파크', '람보르기니')
    c.to_give()
    print(c)
    print(c.like())        

Child 인스턴스는 Parents 클래스의 함수와 변수도 가지고 있기때문에

부모 클래스의 메서드 또한 사용 가능하다.

해당 코드에선 to_give()를 오버라이딩해서 사용해보았다.

(2) 추상화

추상화라는 사전적 정의를 짚어보면

추상화는 필요한 부분, 중요한 부분을 통합하여 하나로 만드는 것을 말한다.

더 쉽게 이해하자면 중요한 특징을 찾아낸 후 간단하게 표현하는 것이다.

예를 들어 말하자면, 원룸의 방 설계를 해본다고 하면

일단 기본적으로 있어야 하는것은 지붕, 창문, 화장실, 주방 등등이 있을것이다.

이러한 내용을 토대로 설계할때 지붕, 창문, 화장실, 주방등이 들어가는 과정들이 추상화 과정이다.

추상적인 것을 구체적으로 모델링 하는 모든 과정

(=객체를 만드는 모든 과정)

사물들 간의 공통점은 취하고 차이점은 버린다.

중요한 부분의 강조를 위해 불필요한 세부사항은 제거한다.

프로그래밍적으로 말하자면 어떠한 class를 만들기 전에 그 class를 어떻게 만들 것인지 미리 설계하고

들어가는 과정으로 추상화 클래스(abstract class) 라는 것을 만드는것이다.

추상화 클래스란 추상화 메서드가 하나라도 있는 클래스를 추상화 클래스라고 한다.

추상화 클래스에서 만들어준 추상화 메서드나 변수는 다음에 상속 받을 자식 클래스안에서

반드시 오버라이딩 되어야 한다.

예시 코드를 보며 이해해보자

from abc import ABC, abstractmethod


class OneRoom(ABC):

    @abstractmethod
    def create_roof(self, roof_color):  # 지붕
        pass

    @abstractmethod
    def create_window(self, window_count):  # 창문
        pass

    @abstractmethod
    def create_toilet(self, toilet_color):  # 화장실
        pass

    @abstractmethod
    def create_kitchen(self, kitchen_color):  # 주방
        pass

일단 추상 클래스를 만들기 전에 ABC모듈과 abstractmethod를 import하고
class에 ABC를 상속 받는다.
@abstractmethod로 데코레이터되어있는 메서드는 pass라고 되어있는데,
이 부분은 OneRoom에게 상속 받는 class에서 오버라이딩하여 완성 시켜주기 위함이다.

from abc import ABC, abstractmethod


class OneRoom(ABC):

    @abstractmethod
    def create_roof(self, roof_color):  # 지붕
        pass

    @abstractmethod
    def create_window(self, window_count):  # 창문
        pass

    @abstractmethod
    def create_toilet(self, toilet_color):  # 화장실
        pass

    @abstractmethod
    def create_kitchen(self, kitchen_color):  # 주방
        pass


class MyOneRoom(OneRoom):
    def __init__(self):
        self.roof_color = None
        self.window_count = None
        self.toilet_color = None
        self.kitchen_color = None

    def create_roof(self, roof_color):
        self.roof_color = roof_color

    def create_window(self, window_count):
        self.window_count = window_count

    def create_toilet(self, toilet_color):
        self.toilet_color = toilet_color

    def create_kitchen(self, kitchen_color):
        self.kitchen_color = kitchen_color

    def __str__(self):
        return f"지붕색 : {self.roof_color}, 창문갯수 : {self.window_count}, 화장실색 : {self.toilet_color}, " \
               f"주방색 : {self.kitchen_color}"


if __name__ == "__main__":
    m = MyOneRoom()
    m.create_roof("초록")
    m.create_window(1)
    m.create_toilet("하양")
    m.create_kitchen("파랑")
    print(m)

이런식으로 구성 할 수 있다.

(3) 캡슐화

캡슐화란 인스턴스를 생성했을때 일부 구현 내용에 대한 외부로 부터의 직접적인 엑세스 차단하는것이다.

쉽게 말해서 캡슐처럼 객체 내부를 숨겨 외부로부터의 엑세스를 차단하는 것이다.

python에선 객체 내부를 숨기는 법은 크게 두가지가 있다.

첫째는 __ (언더바를 두개) 사용하게 되면 외부로 부터 접근을 막을 수 있다.

간단하게 코드로 알아보면

class Citizen:
    def __init__(self, name, age, id):
        self.name = name
        self.set_age(age)
        self.__id = id

    def authenticate(self, id):
        return self.__id == id

    def get_age(self):
        return self.__age

    def set_age(self, age):
        if age > 0:
            self.__age = age
        else:
            print("나이가 0보다 작습니다. 나이를 0으로 설정하겠습니다.")
            self.__age = 0

if __name__ == "__main__":
    person = Citizen('김철수', 25, '12345-678990')
    print(person.get_age()) # 25
    person.__age = 10
    print(person.get_age()) # 위에서 값을 변경시키려고 했지만 25가 출력될것이다.

10이 아닌 25가 출력된 이유는 __ 때문에 그렇다. 위에서 말했듯이 외부로 부터의 직접적인 엑세스 차단을 하였기 때문이다.

하지만 age의 값을 원하는대로 바꿔야 한다면 어떻게 해야햐나?

getter와 setter 메서드 를 이용해서 값을 설정 하는 방법이 있다.

위의 코드에서 set_age() 메서드로 설정 해두었다.

그리고 한가지 개발자들끼리 약속이 있다면

변수 앞에 _ (언더바 1개)가 있다면 그 변수는 함부로 건드리지 말라는 뜻이다.

해당 부분을 무시하고 건드렸다가 클래스 자체가 내가 원하는것과 다르게 움질일 수 도 있기 때문이다.

그래서 캡슐화된 변수를 지정 하고 싶다면 getter와 setter를 이용하는것이 가장 이상적이다.

다른 언어에선 getter, setter를 메서드 이름에 붙여 사용하지만 python같은 경우는 다르게 표시할 수 있다.

바로 property 데코레이터를 사용해서 getter와 setter를 만든 후 age 값을 설정 할 수 있다.

class Citizen
    def __init__(self, name, age, id):
        self.name = name
        self.age = age
        self.id = id

    def authenticate(self, id):
        return self.__id == id

    @property
    def age(self):
        print('나이 리턴)
        return self._age

    @age.setter
    def age(self, age):
        print('나이를 지정한다.')
        if age > 0:
            self._age = age
        else:
            print('나이가 0보다 작습니다. 나이를 0으로 설정하겠습니다.')
            self._age = 0

if __name__ == "__main__":
    person = Citizen("가나다", 30, "123456-789123")
    print(person.age)
    person.age = 10
    print(person.age)

age 메서드를 두번 지정해주었는데, 처음에 선언된 age 메서드는 getter의 역할을 하고,

두번째 선언된 age 메서드는 setter의 역할을 하기 때문에 메서드 위의 데코레이터 형태를 잘 기억 해두어야 한다.

(4) 다형성

다형성이란, 여러가지 형태를 갖는 성질을 의미한다.

하나의 객체가 여러개의 타입을 가르킬 수 있다는 점이 있다.

그림판 클래스를 만들고 그림판에 도형들을 추가 하고 넓이와 총 둘레의 길이를 구해보는 예제를 코드로 나타내오볼 수 있다.

from abc import ABC, abstractmethod


class Shape(ABC):
    @abstractmethod
    def get_area(self) -> float:
        pass

    @abstractmethod
    def get_perimeter(self) -> float:
        pass


class Rectangular(Shape):  # 직사각형
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def get_area(self):
        return self.x * self.y

    def get_perimeter(self):
        return 2 * (self.x + self.y)

    def __str__(self):
        return f"가로:{self.x}, 세로:{self.y}"


if __name__ == "__main__":
    rec = Rectangular(2, 2)
    print(rec.get_area())
    print(type(rec.get_area()))
    print(rec.get_perimeter())
    print(rec)

다형성을 공부하면서 오버라이딩(overriding)에 대한 개념을 얻어 갈 수 있는데, 위 코드에서 사용한것이 오버라이딩이다.

슈퍼 클래스에 구현된 메소드를, 서브 클래스에서 동일한 이름으로 자신의 특징에 맞게 다시 구현하는것이다.

또 따로 오버로딩이라는 개념이 있는데, 해당 개념은 python에선 지원하지 않는다.