python

객체 지향 프로그래밍 [Python]

Like_Me 2023. 1. 29. 17:56
반응형

객체 지향 프로그래밍(OOP, Object-Oriented Programming)은 객체들의 집합으로 프로그램의 상호 작용을 표현하는 프로그래밍 방법 중 하나다. Python을 처음 배울 때 기본적인 내용을 배웠지만 객체 지향 방법의 장, 단점과 어떨 때 사용하고 어떻게 사용하는 것이 좋은지 잘 알지 못해 현업에서 사용하는 데 애를 먹었다. 그래서 이 글에서는 객체 지향 프로그래밍을 사용하는데 필요한 내용들과 지켜야하는 원칙들을 예시와 함께 남긴다.

  • 클래스(class) VS 객체(object) VS 인스턴스(instance)
    • 클래스는 만들고자하는 객체 들의 공통적 성질을 묶어 추상화하여 표현한 것을 말한다. 
      • 이 말이 어렵게 느껴질 수 있는데, 예를 들면 현실에서 '사람'이라는 개념도 여러 사람들의 공통적 성질을 묶어 추상화하여 표현한 것이라고 할 수 있다.
      • '사람'은 이름과 나이라는 특성을 가지고 있고 노래를 부르거나 자기 소개를 하는 등의 행동을 할 수 있는 것을 우리는 알고 있다. 그런 공통적 특성을 가진 것을 '사람'이라는 추상적 표현으로 정의했기 때문이다.
      • 이와같이 아래의 코드처럼 'Person'이라는 class에 공통적으로 가지는 특성과 행동을 정의하여 표현할 수 있다.
    • 객체는 개별적 실체로 존재하는 것들을 말한다.
      • 나 혹은 이 글을 읽고 있는 독자는 모두 '사람'이지만 각각 이름과 나이 등의 다른 특성을 가진다. 이런 개별적 존재를 객체라고 부른다.
      • 아래 코드에서도 'brian'과 'jenny'라는 서로 다른 특성을 갖는 객체를 구현한 것을 볼 수 있다.
    • 인스턴스는 컴퓨터 메모리에 올라온 '객체'를 표현하는 말이다.
      • 인스턴스는 소프트웨어적 관점에서 객체를 표현하는 말이라고 생각할 수 있다. 즉, 실제 메모리를 점유하고 있는 객체이고, 객체는 클래스의 인스턴스라고 표현하기도 한다.
      • 인스턴스는 객체에 속하는 표현이다.
      • 아래 코드에서 'brian'과 'jenny'는 메모리를 가지고 있으므로 인스턴스다.
class Person:
	def __init__(self, name, age):
		self.name = name
		self.age = age
    
	def introduce(self):
		print(f"My name is {self.name} and I'm {self.age} years old")
        
# instance of Person class
brian = Person("brian", 27)
jenny = Person("jenny", 25)
  • 객체 지향 프로그래밍 장점
    • 상속성, 다형성, 캡슐화 덕분에 재사용성이 높은 코드를 작성할 수 있다.
    • 설계 원칙만 잘 지킨다면 코드의 확장과 디버깅이 쉽다.
    • 실제 세계를 표방한 프로그래밍 방법이라 직관적이고 이해가 쉽다.
  • 객체 지향 프로그래밍 단점
    • 설계 자체가 까다로워 개발 시간이 많이 소요될 수 있다.
    • 처리 속도가 다른 프로그래밍 패러다임에 비해 상대적으로 느리다.
    • 추상 객체, 상속 등이 많아지면 코드가 복잡해서 전체 구조를 파악하기 어려워진다.
  • 객체 지향 프로그래밍 특징
    • 추상화
      • 핵심적 개념이나 기능을 간추려내는 것을 말한다.
        • ex) 사람은 나이, 이름을 가지고 있고 걷거나 말하는 행동을 한다.
        • ex) 은행나무는 은행을 열매로 가지고 가을에 노란 잎을 갖는 특성을 갖는다.
      • 추상화는 객체 지향 프로그래밍 설계 원칙에서 중요한 역할을 한다.
      • ex) 아래 코드1에서 여러 총기에 대한 공통적 기능인 shot을 Gun이라는 클래스에서 추상화하여 표현하였다.
    • 캡슐화
      • 객체의 속성과 메서드를 하나로 묶고 일부를 외부에 감추어 은닉하는 것이다.
    • 상속성
      • 상위 클래스의 특성을 하위 클래스가 이어받아서 재사용하거나 추가, 확장하는 것을 말한다.
        • 상위 클래스의 특성을 변경하진 않는다!
      • 코드의 재사용성, 계층적 관계 생성, 유지 보수성 측면에서 중요하다.
    • 다형성
      • 하나의 메서드나 클래스가 다양한 방법으로 동작하는 것을 말한다. 대표적으로 오버로딩, 오버라이딩이 있다.
        • 오버로딩은 같은 이름을 가진 메서드를 여러 개 두는 것을 말한다.(파이썬은 지원하지 않음!)
        • 오버라이딩은 상위 클래스로부터 상속받은 메서드를 하위 클래스가 재정의하는 것을 의미한다.
# 코드1
from abc import ABCMeta, abstractmethod

class Gun(metaclass=ABCMeta):
    name = 'gun'

    @abstractmethod
    def shot(self, hp):
        pass


class AK(Gun):
    damage = 30
    bullet = 50

    def shot(self, hp):
        self.bullet -= 1
        return hp-self.damage


class Snipe(Gun):
    name = 'Snipe'

    @abstractmethod
    def zoom(self):
        pass


class TRG(Snipe):
    damage = 100
    bullet = 5

    def shot(self, hp):
        self.bullet -= 1
        return hp-self.damage

    def zoom(self):
        return print("Zoom in")


class User:
    def __init__(self, hp, gun: Gun):
        self.hp = hp
        self.gun = gun

    def attack(self, user):
        user.hp = self.gun.shot(user.hp)
        return user


user1 = User(hp=100, gun=AK())
user2 = User(hp=100, gun=TRG())

user2 = user1.attack(user2)
user2.hp  # 70
  • 객체 지향 프로그래밍 설계 원칙(SOLID)
    • 단일 책임 원칙(SRP, Single Responsibility Principle)
      • 모든 클래스는 각각 하나의 책임만 가져야 하는 원칙이다.
      • 예를 들어, 데이터 처리를 하는 클래스가 있다면 그 클래스는 데이터 처리에 관한 것만 수행하며 수정할 때도 데이터 처리에 관련된 수정이어야 한다.
    • 개방-폐쇄 원칙(OCP, Open Closed Principle)
      • 유지 보수 사항이 생기면 코드를 쉽게 확장할 수 있도록 하고 수정할 때는 닫혀 있어야 하는 원칙이다.
      • 즉, 기존의 코드는 잘 변경하지 않으면서도 확장은 쉽게할 수 있어야 한다.
      • 이는 이상적인 원칙인 듯 보이는데, 추상화와 상속을 잘 이용하여 구현해야 지킬 수 있다.
        • 변화가 예상되는 것을 미리 추상화하여 유연함을 얻고 상속을 적극 이용한다.
      • ex) 코드1을 보면, User가 받는 무기는 언제든 변할 수 있다. 그러므로 이를 미리 추상화하여 'shot'이라는 공통의 기능을 갖는 'Gun'이라는 클래스를 만들면, 무기를 다양하게 확장&변화시키려고 할 때 기존의 코드는 변경하지 않으면서 확장은 쉽게 할 수 있게 된다.
    • 리스코프 치환 원칙(LSP, Liskov Substitution Principle)
      • 클래스는 '상속'이라는 특성을 가지고 있고, 상속을 통해 부모-자식 이라는 계층이 만들어진다. 이때 부모 객체 자리에 자식 객체를 넣어도 시스템이 문제없이 돌아가게 만들어야 하는 원칙이다.
      • 위에서 '상속성'이라는 특성에 대해 말할 때 상위 클래스의 특성을 하위 클래스가 이어받아 재사용하거나 확장할 수 있는 것이라고 했다. 즉, 원래 부모 클래스에서 있던 특성 자체는 그대로 자식 클래스에도 있어야 정상이므로 부모 객체 자리에 자식 객체를 대신 넣어도 프로그램은 정상적으로 돌아가야 하는 것이 맞다.
    • 인터페이스 분리 원칙(ISP, Interface Segregation Principle)
      • 하나의 구체적인 인터페이스보다 구체적인 여러 개의 인터페이스를 만들어야 하는 원칙이다.
        • 여기서 인터페이스는 python에서는 추상클래스라고 이해해도 될 듯 하다.
        • 즉 General한 추상클래스 하나를 만들지 말고 각 특성별로 추상클래스를 만들라는 것이다.
      • ex) 코드1에서 AK는 Gun을 상속받지만, TRG는 Snipe 클래스를 상속받는다. Snipe 클래스 또한 Gun을 상속받는 것은 마찬가지지만 zoom이라는 추가 기능이 있기 때문에 Gun과는 따로 추상클래스를 만들어준 것이다.
    • 의존 역전 원칙(DIP, Dependency Inversion Principle)
      • 자신보다 변하기 쉬운 것에 의존 하던 것을 추상하된 인터페이스나 상위 클래스를 두어 변하기 쉬운 것의 변화에 영향을 받지 않게 하는 원칙이다.
        • 상위 계층은 하위 계층의 변화에 대한 구현으로부터 독립해야 한다! 
        • 이 원칙은 개방-폐쇄 원칙과 관련되어 보인다. 위에서 보았던 예시처럼 User는 Gun이라는 추상화된 클래스를 input으로 받게 되어 변하기 쉬운 것의 변화에 영향을 받지 않게 한다.
          • 원칙이 지켜지기 위해서 추상화 계층은 다른 구현이 변해도 변하면 안된다는 기본 규칙을 지켜야한다.
반응형