Search

OOP(객체지향)

대분류
언어
소분류
Python
유형
SOLID
주요 레퍼런스
https://www.youtube.com/watch?v=x6uB1XKqDNQ&t=5s
https://inpa.tistory.com/entry/OOP-%F0%9F%92%A0-%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%EC%84%A4%EA%B3%84%EC%9D%98-5%EA%B0%80%EC%A7%80-%EC%9B%90%EC%B9%99-SOLID
최종 편집 일시
2024/10/27 15:30
생성 일시
2024/07/26 00:33
14 more properties

클래스 & 객체 & 인스턴스

클래스 : 특정 객체를 생성하기 위해 변수와 메소드를 정의하는 일종의 틀
객체 : 데이터 또는 식별자에 의해 참조되는 공간
인스턴스 : 클래스의 현재 생성된 오브젝트
인스턴스 변수 : 인스턴스내 생성된 변수 = 클래스에서 상속받아 적을 수도 있고, 인스턴스 내에서 새로 생성도 가능
인스턴스 메소드 : 인스턴스의 변수를 다루는 메소드, 첫번째 파라미터로 자신이 자동으로 전달(self)

매직메소드

클래스에서 특정한 역할로 지정된 메소드 (__init__)

클래스 변수 & 인스턴스 변수

클래스 변수 : 클래스 자체 내에서 생성하는 변수
클래스 변수와 인스턴스 변수가 같은 이름인 경우 → 인스턴스 변수를 읽어옴.
class User: user_count = 0 # 클래스 변수 def __init__(self, name, pw): self.name = name # 인스턴스 변수 self.pw = pw User.user_count += 1 def check_pw(self, pw): # 인스턴스 메소드 if self.pw == pw: print("pass") else: print("fail") user1 = User("kim","1234") user2 = User("lee","1234") user3 = User("park","1234") User.user_count = 70 user1.user_count = 77 print(user1.user_count) # 77 print(user2.user_count) # 70 print(user3.user_count) # 70
Python
복사

데코레이터

어떤 것을 꾸미는 것
함수의 앞 뒤를 꾸며주는 함수

클래스 메소드 & 인스턴스 메소드

인스턴스 메소드 : 인스턴스 변수 값을 읽거나 설정하는 메소드
클래스 메소드 : 클래스 변수 값을 읽거나 설정하는 메소드
첫번째 파라미터가 클래스 자기 자신을 자동 전달
해당 파라미터를 cls라 판달
@classmethod라는 데코레이터로 꾸며줌
... @classMethod def check_user_count(cls): return cls.user_coun User.check_user_count() # 4 user1.check_user_count() # 4
Python
복사

정적 메소드(스태틱메소드)

자동 전달되는 파라미터 X
메서드 실행이 외부 상태에 영향을 끼치지 않는 순수 함수(pure function)를 만들 때 사용
부수 효과가 없고 입력 값이 같은면 언제나 같은 출력 값을 반환
인스턴스의 상태를 변화시키지 않는 메서드를 만들 때 사용
단순 계산 → 결과값 도출되는 순수함수를 만들 때 좋다.
... @staticmethod def add_two_parameter(param1, param2): return param1 + param2 User.add_two_parameter(3,3)
Python
복사

추상 클래스

메서드의 목록만 가진 클래스, 상속받는 클래스에서 메서드 구현을 강제하기 위해 사용
상속받은 클래스는 추상메소드를 구현하지 않아도, import할 때까지 에러는 발생하지 않으나 객체를 생성할 시 에러가 발생
오로지 상속에만 사용해야함
추상클래스를 만들기 위한 형식
반드시 abc 모듈을 import 해야한다.
추상메소드는 생략하면 기본적인 클래스 기능은 동작, 하지만 추상메소드를 추가한 후에는 객체를 생성하면 에러가 발생
from abc import * class 추상클래스명(ABC): # 추상 클래스 선언 @abstractmethod # 추상 메서드 정의 def 추상메소드1(self): pass # 추상 메서드는 호출할 일이 없으므로 빈 메서드로 만듦 @abstractmethod def 추상메소드2(self): pass
Python
복사

데이터 클래스

클래스를 더 간결하고 가독성 있게 정의할 수 있도록 도와주는 기능
@dataclass라는 데코레이터로 꾸며줌
특징
__init__메소드 불필요
# 일반 클래스 class Book_UserDefined: def __init__(self, title, author, pages, genre): self.title = title self.author = author self.pages = pages self.genre = genre # 데이터 클래스 @dataclass class Book: title: str author: str pages: int genre: str
Python
복사
불변성 지정 가능 @dataclass(frozen=True)
# 일반 클래스 class Book_UserDefined: __slots__ = ('title', 'author', 'pages', 'genre') # ... (다른 메소드 및 __init__) def __setattr__(self, name, value): raise AttributeError("이 클래스는 불변입니다.") # 데이터 클래스 @dataclass(frozen=True) class Book: title: str author: str pages: int genre: str
Python
복사
데이터 클래스는 repreq 메소드를 자동으로 구현
# 일반 클래스 class Book_UserDefined: # ... (다른 메소드) def __eq__(self, other): return self.title == other.title and self.author == other.author and self.pages == other.pages and self.genre == other.genre book1_ud = Book_UserDefined("1984", "George Orwell", 328, "Dystopian") book2_ud = Book_UserDefined("1984", "George Orwell", 328, "Dystopian") print(book1_ud == book2_ud) # Output: True (직접 __eq__ 구현) # 데이터 클래스 book1 = Book("1984", "George Orwell", 328, "Dystopian") book2 = Book("1984", "George Orwell", 328, "Dystopian") print(book1 == book2) # Output: True (자동으로 __eq__ 구현)
Python
복사

OOP 설계 5 원칙 (SOLID)

SRP(Single Responsibility Principle) : 단일 책임 원칙 = 원포원

클래스 단일 책임
하나의 클래스 & 메서드는 단 하나의 기능(책임)을 가져야 한다.
단 하나의 기능만 유니크하게 가지는 것을 의미
해당 기능이 변경될 때만 클래스가 변경될 수 있다.
재사용성은 상승하고 결합도는 최소가 된다.
예제
class BookService: def find_book_by_id(query): # DB에서 찾아오기 # Log 파일에서 찾은 정보 쓰기 # 해당 클래스는 다음과 같이 분리되어야 한다. class BookService: def find_book_by_id(query): # DB에서 찾아오기 def write_log(log): # Log 파일에 찾은 정보 쓰기.
Python
복사

OCP(Open Closed Principle) : 개방-폐쇄 원칙 = 추상화

소프트웨어의 entity(클래스, 모듈, 메서드)들의 확장은 권장하지만 기존 모듈의 수정은 권장하지 않는다는 의미
확장에 대해서는 개방적(open), 수정에 대해서는 폐쇄적(closed)
확장 : 새로운 기능의 추가
수정이 되지 않을 부분은 상위 클래스나 인터페이스로, 수정이 될 부분은 하위클래스로 관리
새로운 기능을 추가할 때 기존 코드를 수정하지 않고, 코드를 확장할 수 있어야 한다.
[ 확장에 열려있다 ] - 모듈의 확장성을 보장하는 것을 의미한다. - 새로운 변경 사항이 발생했을 때 유연하게 코드를 추가함으로써 애플리케이션의 기능을 큰 힘을 들이지 않고 확장할 수 있다. [ 변경에 닫혀있다 ] - 객체를 직접적으로 수정하는건 제한해야 한다는 것을 의미한다. - 새로운 변경 사항이 발생했을 때 객체를 직접적으로 수정해야 한다면 새로운 변경사항에 대해 유연하게 대응할 수 없는 애플리케이션이라고 말한다. - 이는 유지보수의 비용 증가로 이어지는 매우 안좋은 예시이다. - 따라서 객체를 직접 수정하지 않고도 변경사항을 적용할 수 있도록 설계해야 한다. 그래서 변경에 닫혀있다고 표현한 것이다.
추상화 설계
1.
먼저 변경(확장)될 것과 변하지 않을 것을 엄격히 구분
2.
이 두 모듈이 만나는 지점에 추상화(추상클래스 or 인터페이스)를 정의
3.
구현체에 의존하기보다 정의한 추상화에 의존하도록 코드를 작성
예제
from abc import ABC, abstractmethod # 추상화 class Animal(ABC): @abstractmethod def speak(self): pass class Cat(Animal): # 상속 def speak(self): print("냐옹") class Dog(Animal): # 상속 def speak(self): print("멍멍") # 추상클래스를 상속만 하면 메소드 강제 구현 규칙으로 규격화만 하면 확장에 제한 없다 (opened) class Sheep(Animal): def speak(self): print("매에에"); class HelloAnimal: def hello(self, animal: Animal): animal.speak() def main(): hello = HelloAnimal() cat = Cat() dog = Dog() sheep = Sheep() hello.hello(cat) # 냐옹 hello.hello(dog) # 멍멍 hello.hello(sheep) # 매에에 if __name__ == "__main__": main()
Python
복사

LSP(Liskov Substitution Principle) : 리스코프 치환 법칙 = 다형성

다형성을 지원하기 위한 원칙
다형성 : 하나의 객체가 여러 가지 타입을 가질 수 있는 것을 의미
협업하는 개발자 사이의 신뢰를 위한 원칙
자식 클래스는 언제나 자신의 부모 클래스로 교체될 수 있다는 원칙
자식 클래스가 어떻게 구현이 되었든 부모 클래스를 알면 사용이 가능하여야 한다.
⇒ A is B라는 상속 관계가 있을때, B는 자식 클래스 이고 A는 부모클래스
이때 B의 행위는 A의 행위의 예상할 수 있는 범위내에서 이루어져야 한다.
자식 클래스는 부모 클래스와 호환되어야 하며, 부모 클래스로 교체해도 프로그램의 동작이 일관되게 유지되어야 한다.
다형성 설계
1.
매개변수의 타입이 부모/자식 클래스가 같아야 한다.
overriding을 한다고 해서 매개변수 타입이 달라지면 일관성이 떨어짐
2.
반환되는 타입이 같아야 한다.
잘못된 상속 관계 구성으로 인한 메서드 정의
올바르게 별도 인터페이스로 지정 설계한 메서드 정의
1. 매개변수의 타입이 부모/자식 클래스가 같아야 한다.
overriding을 한다고 해서 매개변수 타입이 달라지면 일관성이 떨어진다.
2. 반환되는 타입이 같아야 한다.
1. 매개변수의 타입이 부모/자식 클래스가 같아야 한다.
overriding을 한다고 해서 매개변수 타입이 달라지면 일관성이 떨어진다.
2. 반환되는 타입이 같아야 한다.
1. 매개변수의 타입이 부모/자식 클래스가 같아야 한다.
overriding을 한다고 해서 매개변수 타입이 달라지면 일관성이 떨어진다.
2. 반환되는 타입이 같아야 한다.
예제
from abc import ABC, abstractmethod # 추상 클래스 class Animal(ABC): pass # 인터페이스 역할을 하는 추상 클래스 class Speakable(ABC): @abstractmethod def speak(self): pass # Cat 클래스는 Animal을 상속받고 Speakable을 구현 class Cat(Animal, Speakable): def speak(self): print("냐옹") # Dog 클래스는 Animal을 상속받고 Speakable을 구현 class Dog(Animal, Speakable): def speak(self): print("멍멍") # Fish 클래스는 Animal을 상속받지만 Speakable을 구현하지 않음 class Fish(Animal): pass # 테스트 코드 def main(): cat = Cat() dog = Dog() fish = Fish() cat.speak() # 냐옹 dog.speak() # 멍멍 # fish.speak() # AttributeError: 'Fish' object has no attribute 'speak' if __name__ == "__main__": main()
Python
복사

ISP(Interface Segregation Principle) : 인터페이스 분리 원칙 = 원포원

인터페이스 단일 책임
하나의 일반적인 인터페이스보다는, 여러개의 구체적인 인터페이스를 구현해서 사용해야 한다는 원칙
인터페이스는 python에서는 추상클래스라고 이해
General한 추상클래스 하나를 만들지 말고 각 특성별로 추상클래스
한 번 인터페이스를 분리하여 구성해놓고 이후 수정사항이 생겨서 또 인터페이스들을 분리하는 행위는 금지
인터페이스를 클라이언트에 맞게 분리하여, 클라이언트가 불필요한 메서드에 의존하지 않도록 해야 한다.
설계
각각의 기능에 맞게 인터페이스(추상 클래스)를 잘게 분리하도록 구성
예제
# 잘못된 예시: 모든 클라이언트가 모든 메서드를 구현해야 함 class Worker: def work(self): pass def eat(self): pass class Robot(Worker): def work(self): # 작업 수행 pass def eat(self): # 로봇은 먹지 않음 pass # 올바른 예시: 인터페이스를 분리하여 필요한 메서드만 구현 class Workable: def work(self): pass class Eatable: def eat(self): pass class Human(Workable, Eatable): def work(self): # 작업 수행 pass def eat(self): # 식사 pass class Robot(Workable): def work(self): # 작업 수행 pass
Python
복사
주의사항
책임을 준수하더라도 실무에서는 ISP가 만족되지 않을 수 있는 케이스가 존재

DIP(Dependency Inversion Principle) : 의존 역전 법칙 = 결합도 ↓

어떤 Class를 참조해서 사용해야하는 상황이 생긴다면, 그 Class를 직접 참조하는 것이 아니라 그대상의 상위 요소(추상 클래스 or 인터페이스)로 참조
고수준 모듈은 저수준 모듈에 의존해서는 안 되며, 둘 다 추상화에 의존해야 한다.
구체적인 클래스보다 인터페이스나 추상 클래스에 의존해야 한다.
설계
추상클래스가 가장 메인이 되어야 한다. 나머지 클래스들은 추상클래스에 의존해야 한다.
# 잘못된 예시: 고수준 모듈이 저수준 모듈에 의존 class LightBulb: def turn_on(self): pass def turn_off(self): pass class Switch: def __init__(self, light_bulb): self.light_bulb = light_bulb def operate(self, on): if on: self.light_bulb.turn_on() else: self.light_bulb.turn_off() # 올바른 예시: 추상화에 의존 class Switchable: def turn_on(self): pass def turn_off(self): pass class LightBulb(Switchable): def turn_on(self): pass def turn_off(self): pass class Switch(Switchable): def __init__(self, device: Switchable): self.device = device def operate(self, on): if on: self.device.turn_on() else: self.device.turn_off()
Python
복사