'객체(Object)' 하면 무엇이 떠오르시나요
어떤 분들은 일반적인 사물을 뜻하는 단어를 떠올릴 수도 있고,
어떤 분들은 예술용어인 오브제를 떠올릴 수도 있겠네요.
프로그래밍 분야에서의 객체는, -한 문장으로 표현하기는 어렵지만-
연관성있는 데이터 정보들의 집합이 되는 데이터타입(출처링크) 이라고 할 수 있습니다.
보통 클래스를 통해 생성되며, 다른 말로는 인스턴스(instance)라고 표현되기도 하죠.
객체지향 프로그래밍은 데이터를 이 객체를 중심으로 관리하는 방법을 뜻합니다.
보통 객체는 그 특성이 되는 속성(properties)과
객체관련 함수인 메소드(method) 등을 포함하고 있습니다.
따라서 연관성있는 데이터의 정보를 묶어서 관리하기 때문에 코드의 재사용, 유지보수가 용이합니다.
또한 객체지향 프로그래밍은 크게 추상화, 캡슐화(은닉성), 다형성, 상속성의 특징을 가집니다.
이 특징들을 설명하기 전에 우선 클래스와 객체의 관계에 대해 짚고 넘어가려 합니다.
#파이썬의 경우 dataclass 모듈로도 클래스 구현.
#아래 예제에서는 사용하지 않았습니다.
class Dog():
def __init__(self, name, age, breed):
self.name = name
self.age = age
self.breed = breed
def say(self):
print('월우러워러루러ㅜ러뤌ㄹ!!')
def call(self, calling_name):
if calling_name == self.name:
print('나 불렀어?')
else:
print('?')
dog_1 = Dog('바둑이', '시고르자브종', 5)
#dog_1이 class Dog의 인스턴스인지 확인
print(isinstance(dog_1, Dog))
# 결과 <- True
class Dog()이라는 하나의 클래스가 만들어졌습니다.
이 클래스를 이용해 dog_1이 생성되었는데 여기서 이 dog_1가 객체가 됩니다.
이 dog_1을 통해 우리는 class Dog()의 속성들을 불러올 수 있습니다.
class Dog():
def __init__(self, name, age, breed):
self.name = name
self.age = age
self.breed = breed
def say(self):
print('월우러워러루러ㅜ러뤌ㄹ!!')
def call(self, calling_name):
if calling_name == self.name:
print('나 불렀어?')
else:
print('?')
class Cat():
def __init__(self, name, age, breed):
self.name = name
self.age = age
self.breed = breed
def say(self):
print('애옹')
def call(self, calling_name):
if calling_name == self.name:
print('나 불렀어?')
else:
print('?')
class Cat()에는 고양이와 관련된 데이터,
class Dog()에는 강아지와 관련된 데이터가 묶여있습니다.
하지만 코드를 들여다보면 겹치는 부분이 많죠.
바로 cat이나 dog이나 같은 pet이기 때문입니다.
따라서 우리는 cat과 dog에서 겹치는 부분만 따로 추출해
class Pet()이라는 클래스로 해당 데이터를 관리할 수 있습니다.
이렇게 데이터나 프로세스를 의미, 수행과정이 비슷한 개념으로 묶어
정의(선언)하는 것을 추상화라고 합니다.
프로퍼티(변수)와 메소드(함수)가 하나의 캡슐 안에 묶인 특성을 말합니다.
그리고 그 캡슐 안 정보들은 밖에서 접근이 불가능함을 은닉화라고 표현합니다.
그런데 파이썬은 캡슐화는 지원하지만(class 기능) 그 내부 데이터는 public으로 제공합니다.
#기본적으로 파이썬의 클래스 속성,메소드들은 public 접근이 허용됨
class Dog():
def __init__(self, name, age, breed):
self.name = name
self.age = age
self.breed = breed
dog_1 = Dog('바둑이', '시고르잡종', 5)
print(dog_1.name)
# 결과 <- '바둑이'
하지만 underscore, double undercore를 이용해 protected, private으로 변경이 가능합니다.
class Dog():
def __init__(self, name, breed, age):
self.__name = name
self.__age = age
dog_1 = Dog('바둑이', '시고르잡종', 5)
print(dog_1.__name)
#결과 <- AttributeError: 'Dog' object has no attribute '__name'
print(dog_1.name)
#마찬가지로 no attribute 에러 발생
기존의 클래스에 새로운 기능을 추가해 새로운 클래스를 만들 수도 있습니다.
이때 기존에 있던 클래스는 부모클래스(parent class), 슈퍼클래스(super class),
새로 만들어진 클래스는 자식클래스(child class), 서브클래스(sub class)입니다.
앞선 추상화 설명때 언급된 class Pet()을 만들어볼까요.
class Pet():
def __init__(self, name, age, breed):
self.name = name
self.age = age
self.breed = breed
def call(self, calling_name):
if calling_name == self.name:
print('나 불렀어?')
else:
print('?')
그리고 class Pet()을 상속받아 자식클래스 class Bird()를 생성해봅시다.
class Bird(Pet):
def __init__(self, name, age, breed, color):
#👇클래스 Pet에서 상속
Pet.__init__(self, name, age, breed)
#or
super().__init__(name, age, breed)
#👇상속된 prop 외 새로운 data 추가
self.color = color
def fly():
print('파닥파닥')
bird_1 = Bird('앵무', 2, 'parrot', 'red')
print(dir(bird_1))
# 결과 <- ['__class__', ...중간생략..., 'age', 'breed', 'call', 'color', 'name', 'fly']
bird_1 객체는 부모클래스의 속성과 메소드를 그대로 물려받았음을 확인할 수 있습니다.
이렇듯 객체지향 프로그래밍이 가지는 상속성은 코드의 재사용을 원활하게끔 돕습니다.
하나의 객체가 여러 형태를 가질 수 있음을 의미합니다.
상속을 이용하여 기능을 확장/변경하는 것도 다형성을 나타내고,
이런 다형성은 코드의 길이를 짧게 유지하는데 도움이 됩니다.
다형성을 이해하기 위해 등장하는 대표적인 개념이 있는데
바로 오버라이딩(overriding)와 오버로딩(overloading)입니다.
override는 '무시하다'는 사전적 의미를 가지고 있습니다.
상속받은 메소드를 재정의하는 걸 오버라이딩이라 합니다.
아래는 부모로부터 상속받은 메소드가
자식클래스에서 재정의된 메소드로 덮어씌워진 모습입니다.
class Dog():
def say(self):
print('멍멍!')
class Big_dog(Dog):
def say(self):
print('컹컹!')
big_dog = Big_dog()
big_dog.say()
# 결과 <- '컹컹!'
메소드의 이름은 동일한데 오브젝트마다 다른 작업을 수행할 수 있음을 알 수 있습니다.
아래처럼 부모클래스와 동일한 작업 +α 를 처리해야 할 경우
이런 오버라이딩의 성질을 통해 기능을 확장하면서, 코드의 중복을 줄일 수 있습니다.
class Driver():
def ready(self):
print('차 문을 열고 시동을 켠다.')
class Delivery_man(Driver):
def ready(self):
print('물건을 싣는다')
# 👇부모의 ready() 메소드 실행
super().ready()
our_hero = Delivery_man()
our_hero.ready()
# 👇결과
# 물건을 싣는다.
# 차 문을 열고 시동을 켠다.
과부하를 뜻하는 'overload' 라는 단어로 표현되는 오버로딩은
하나의 클래스 안에 같은 이름의 메소드를 여러개 지닐 수 있음을 뜻합니다.
따라서 하나의 메소드가 가질 수 있는 인자의 데이터타입이나 개수가 다양해질 수 있습니다.
참고로 파이썬에서는 오버로딩기능이 지원되지 않습니다.
마지막으로 선언된 메소드만 유효하게 취급되기 때문에 비슷하게 따라하려는 일종의 '꼼수'가 필요합니다.
아래는 꼼수 방법 중 하나입니다.
class Rating():
def __init__(self, total_score=0, count_score=0, comment=[]):
self.total_score = total_score
self.count_score = count_score
self.comment = comment
def add(self, *args):
for arg in args:
if type(arg) == int:
self.total_score += arg
self.count_score += 1
elif type(arg) == str:
self.comment.append(arg)
def status(self):
print(f'평균 평점: {round(self.total_score / self.count_score, 1)}')
def see_comments(self):
print(*self.comment, sep='\n')
rating = Rating()
rating.add(5, 3, 5, '맛있어요', 4, 4, 3, 5, 1, '좀 짜요', 5)
메소드 add()에서 *를 활용해 인자의 값을 다양하게 받을 수 있는 걸 볼 수 있습니다.
어차피 연관성 있는 작업을 처리하는건데
인자의 타입이 무엇인지, 그 개수가 얼마인지에 따라 메소드를 일일이 새로 만들어줘야 한다면
관리도 어렵고, 쓰는 사람 입장에서도 상당히 불편할 것입니다.
혹은 dispatch 모듈을 사용해서 메소드 오버로드를 구현할 수도 있습니다.
multiple-dispatch.readthedocs.io/en/latest/resolution.html
참고
www.fun-coding.org/PL&OOP1-2.html
dojang.io/mod/page/view.php?id=2372
[python]함수의 인자 형태와 순서 / (non)default value parameter, *args, **kwargs (0) | 2021.03.17 |
---|---|
[Python] 문자열을 붙이는 다양한 방법 (string concatenation) (1) | 2021.03.17 |
파이썬 매직 메소드(던더 메소드) (2) | 2021.02.28 |
파이썬 예외처리 (try, except, else, finally, assert) (0) | 2021.02.26 |
가변객체(mutable)와 불변객체(immutable) / 파이썬 (2) | 2021.02.19 |
댓글 영역