객체지향 with Python

Soogyung Gwon·2026년 1월 25일

구름을잡아라

목록 보기
6/60

1. 프로그래밍 패러다임의 변화

기계 중심 프로그래밍 -> 절차 지향 프로그래밍 -> 구조적 프로그래밍
어셈블리 -> 포트란, 코블 -> C, 파스칼

* 객체지향 프로그래밍의 등장

현실세계 모델링(데이터 + 행위)
프로그램 객체간 상호작용
Simula/Smaltalk
C++/C#
JAVA/Python

2. 절차지향 vs 객체지향 비교(특징 및 코드 비교)

절차지향

항수를 중심으로 프로그램을 절차적으로 구성
알고리즘(How to do)을 순차적으로 표현
프로세스/작업 흐름 중심 모델링
데이터와 기능(함수)이 분리됨 - 데이터는 함수 외부에 존재하며 여러 함수에서 접근
함수 단위 재사용
전역 데이터 수정 -> 전체 영향 (대규모 프로젝트에 취약)
장섬 - 단순하고 직관적, 실행 속도 빠름 (소규모에 적합)
단점 - 코드 규모 커질 수록 관리 어려움, 데이터 무결성 보장 어려우므 코드 중복

객체지향

데이터(상태)와 기능(행위)을 하나의 객체로 캡슐화하여 현실 세계를 모델링
책임 중심 (What to do) - 객체 간 메세지 전달과 책임 분배 중심'
현실 세계의 개체(Entity)를 객체로 직접 매핑하여 모델링
데이터와 행위가 하나의 객체로 캡슐화되어 외부로부터 보호됨
클래스/객체 단위(상속, 합성(Composition), 다형성 활용)
캡슐화와 정보은닉으로 변경 영향 범위 최소화(대규모 시스템에 유리)
코드 재사용성/확장성/유지보수성 높음 - 대규모, 복잡한 시스템에 적합
초기 설계 학습 비용 높음, 런타임 오버헤드 약간 존재
순수 OOP: Simula(기원), Smaltalk
대중화: C++, Java, C#, Python, Ruby 등

3. 왜 객체지향인가?

절차지향에서는 데이터와 기능이 분리되어 있음
데이터가 흩어져서 관리 불가

Simula의 해법

현실개체 -> 객체 (Object)
개체 상태 -> 데이터 (Field)
개체 행위 -> 메서드 (Method)

4. 클래스란?

객체를 만들어내기 위한 설계도(blueprint)이자 틀(mold)
붕어빵 틀 (Class) 하나로 붕어빵 (Object)를 생성

클래스의 구성요소

데이터(속성)
행위(메서드)
생성자(컨스트럭터)

클래스 = 데이터(명사) + 행위(동사) + 탄생(초기화)

클래스를 만든다는 것의 진짜 의미

책임의 단위 - 캡슐화
현실의 모델링 - 추상화
새로운 타입 - 나만의 자료형

클래스와 객체의 관계

클래스를 인스턴스화 하면 객체(오브젝트)가 나온다
객체들은 다른 메모리 상에 존재 (독립적)

5. 객체 간의 구조적 관계: Is-a 와 Has-a (상속이냐 포함이냐)

구조적 관계
Is-a 관계 - 상속/계층 관계
Has-a 관계 - 포함/구성 관계

Is-a

Student is a Person
Dog is a Animal

부모는 일반적인 개념
자식은 구체적인 개념
자식은 부모의 속성과 행동을 그대로 물려받음(재사용)
부모타입을 기대하는 자리에 자식 객체를 넣어도 문제없이 동작해야 함(LSP 원칙)
상속의 목적: 객체의 종류 표현 (부가적으로 코드 재사용)

#부모 클래스
class Person:
	def eat(self):
    	print("밥을 먹습니다")
        
#자식 클래스
class Student(Person):
	def study(self):
    	print("공부를 합니다")

#사용
s = Student()
s.eat() #부모에게 물려받은 기능
s.study() # 자신의 기능

잘못된 상속(is-a)의 예

1) 경찰 is 총?
경찰은 총이 아니라 총을 가져야 함

2) 퓅귄 is 새?
부모는 fly() 기능이 있음
펭귄은 날지 못함
행위(Behavior)가 일치해야 상속 가능

Has-a (Composition)

A has a B
Car has a Engine
Police has a Gun

전체와 부품의 관계
상속보다 유연한 결합
내 기능을 직접 내가 하지 않고 부폼에게 시킴 (위임)
포함의 목적: 객체를 조립하고, 역할을 분리하여 변경에 유연한 구조를 만드는 것

# 부품 클래스
class Engine:
	def start(self):
    	print("부품! 엔진 가동")

# 전체 클래스
class Car:
	def __init__(self):
    	# Car has a engine (엔진을 소유함)
        self.engine = Engine()
    def drive(self):
    	#[위임] 내가 직접 돌리는 게 아니라 엔진에게 시킴
        self.engine.start()
        print("출발합니다")

# 사용
my_car = Car()
my_car.drive()

Has-a의 두 가지 얼굴

합성(Composition) - 강한 결합: 사람 - 심장 -> 생명 주기 동일
집합(Aggregation) - 약한 결합: 학교 - 학생 -> 생명 주기 독립

상속과 포함의 권장 설계 원칙

상속(Is-a)보다는 포함(Has-a)을 우선하라

상속의 문제점
부모 구현 의존: 부모 구현에 강하게 의존, 부모 변경에 취약
유연성 부족: 실행 중에 부모를 바꿀 수 없음 (정적)
클래스 폭발: 기능 조합을 위해 수많은 클래스가 생김

포함의 장점
블랙박스 재사용: 내부를 몰라도 인터페이스만 알면 됨
런타임 교체: 실행 중에 부품(객체)를 갈아끼울 수 있음
단순성: 클래스 수를 줄이고 관계를 단순화

6. 객체지향 4대 요소

1) 추상화 (Abstract)

공통적이고 본질적인 특징만 추출하는 것, 불필요한 세부 사항은 제거하고 핵심에 집중

2) 캡슐화 (Encapsulation): 감추기

데이터와 기능을 하나의 캡슐(Class)로 묶음
외부로부터 데이터를 보호(정보 은닉)하고 접근을 제어

class Account:
	def __init__(self):
    	#__ 변수명: 비공개(Private) 변수
    	self.__money = 0
    
    def deposit(self, amout):
    	#검증 로직 (보호)
        if amount > 0:
        self.__monney += amount

3) 상속 (Inheritance): 물려받기

객체의 종류와 분류를 표현하기 위한 설계 방법
부모의 속성과 기능을 물려받아 재사용할 수 있음

4) 다형성 (Polymorphism): 갈아끼우기

동일한 메세지에 다른 동작, 같은 메서드를 호출해도 객체마다 다르게 동작

7. 좋은 객체의 조건: 추상화와 캡슐화

1) 추상화: 모델링의 미학

  • 복잡한 객체에서 문맥에 따라 공통적이고 본질적인 속성과 기능만을 추출

문맥에 따라 똑같은 객체라도 어떤 프로그램이냐에 따라 모습이 달라짐

2) 캡슐화: 보호와 책임
관련된 속성과 기느을 하나의 캡슐(클래스)로 묶고, 외부로부터 데이터를 보호하며 접근을 제어

왜? 1. 데이터 보호, 2. 무결성 보장

8. 다형성

하나의 이름으로 여러 가지 형태의 동작을 수행하는 능력
같은 메서드 호출
다른 동작
다른 결과

-> 오버라이딩

다형성의 진짜 효과: 확장성

기존 코드를 건드리지 않고, 새로운 기능을 추가할 수 있다.
OCP - 개방-폐쇄 원칙

Plug & Play
USB 포트처럼 규격(메서드)만 맞으면, 무엇이든 꽂아서 바로 쓸 수 있다.

유연한 유지보수
기능을 추가할 때 메인 로직을 수정할 필요가 없으므로 버그가 줄어든다.

파이썬의 다형성: 오리 타이핑 (Duck Typing)

Duck Typing - 오리처럼 걷고 꽥꽥 거리면 그것은 오리다.

# 상속 받지 않았음
class Dog:
	def speak(self): print("멍멍!")
    
class Cat:
	def speak(self): print("야옹!")

class Robot:
	def speak(self): print("삐빅!")
    
objs = [Dog(), Cat(), Robot()]

for o in objs:
	o.speak()

Java: Animal을 상속받아야만 다형성 가능 (엄격함)
Python: 상속 관계가 없어도 메세드 이름만 같으면 실행됨 (유연함)

객체지향 설계 5원칙 SOLID

유연하고 확장 가능한

  1. SRP: 단일 책임 원칙
    Single Responsibility Principle

하나의 클래스는 하나의 책임(목적)만 가져야한다.
하나의 클래스는 하나의 이유로만 변경되어야 한다.

  1. OCP: 개방-폐쇄 원칙

Open/Closed Principle

확장에는 열려 있고, 변경에는 닫혀 있어야한다.

  1. LSP:리스코프 치환 원칙
    Liskov Substitution Principle

자식 클래스는 언제나 부모 클래스를 대체할 수 있어야 한다.
(부모가 못 하는 것은 자식도 못 해야 함)

  1. ISP: 인처페이스 분리 원칙
    (Interface Segregation Principle)
    내가 사용하지 않는 기능에 의준하게 만들지 마라.

  2. DIP: 의존 역전 원칙
    Dependency Inversion Principle

구체적인 것이 아니라, 추상적인 것(역할)에 의존하라.

장난감 로봇 - 건전지 규격
(장난감 로봇이 에너자이저와 같은 특정 브랜드에 의존하는 것보다는, 규격에 의존하면 무엇이든지 끼울 수 있음)

# 1. 추상화 (Interface/Abstract Class)
class Battery:
	def use(self): pass
    
# 2. 구체적인 구현 (Concreation)
class Energizer(Battery): #상속
	def use(self): print("에너자이저 파워!")
    
# 3. 로봇은 구체적인 건전지가 아니라 Battery 역할에 의존
class Robot:
	#batter: Battery -> 타입 힌트(Type Annotation), 강제가 아니라 사람에게 설계를 보여주는 것
	def __init__(self, battery: Battery): 
    	self.battery = battery
    
    def operate(self):
    	self.battery.use()

#4. 사용: 무엇이든 끼울 수 있다.
robot = Robot(Energizer())
robot.operate()
profile
오랜시간 망설였던 코딩을 다시 해보려고 노력하고 있는 사람

0개의 댓글