본문 바로가기
C.W.K.
Stream
Lesson 02 of 05 · published

Mixin과 합성 — 행동을 덧붙이는 두 길

~18 min · mixin, composition, behavior-injection

Level 0호기심
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete

Mixin은 홀로 존재하지 않는 작은 능력이야

로그 출력, JSON 변환처럼 한 가지 행동을 여러 클래스에 제공하고 자체 정체성이나 큰 상태를 갖지 않아. 이름도 역할이 드러나게 하고, 다른 mixin과 겹치는 메서드가 없는지 살펴야 해.

상속이 늘수록 숨은 결합도 늘어

여러 mixin이 같은 이름이나 super 체인에 기대면 MRO를 알아야만 동작을 이해할 수 있어. 직교하는 작은 능력에는 쓸 만하지만, 상태와 수명이 있는 부품이라면 합성이 더 정직해.

합성은 소유권을 호출부에 드러낸다

객체가 도우미를 속성으로 가지고 명시적으로 일을 맡기면 교체와 시험이 쉽고, 메서드가 어디서 왔는지도 바로 보여. 간접 호출 한 단계의 비용 대신 경계가 선명해져.

5% 관점: mixin은 상속의 모양을 빌리지만 정체성의 계보를 만들려는 도구는 아니야. 본체가 아니라 횡단 능력을 주입하는 특성에 가까워. 호스트 내부를 많이 알아야 하거나 mixin끼리 순서를 다투기 시작하면 합성이 더 정직한 경계라는 신호야.

Code

한 가지 행동만 더하는 mixin·python
import json

class JsonMixin:
    def to_json(self):
        return json.dumps(self.__dict__)

    @classmethod
    def from_json(cls, s):
        return cls(**json.loads(s))

class User(JsonMixin):
    def __init__(self, name, email):
        self.name = name
        self.email = email

u = User("alice", "a@x.com")
print(u.to_json())                  # {"name": "alice", "email": "a@x.com"}
u2 = User.from_json(u.to_json())
print(u2.name, u2.email)

# JsonMixin 은 단순 속성 가진 모든 클래스에 재사용
서로 겹치지 않는 여러 mixin·python
class TimestampMixin:
    def __init__(self, **kwargs):
        import datetime
        self.created_at = datetime.datetime.now()
        super().__init__(**kwargs)

class IdMixin:
    _next_id = 1
    def __init__(self, **kwargs):
        self.id = type(self)._next_id
        type(self)._next_id += 1
        super().__init__(**kwargs)

class Order(TimestampMixin, IdMixin):
    def __init__(self, item, **kwargs):
        self.item = item
        super().__init__(**kwargs)

o1 = Order("book")
o2 = Order("pen")
print(o1.id, o1.item, o1.created_at)
print(o2.id, o2.item, o2.created_at)
명시적인 합성으로 같은 문제 풀기·python
import json

class JsonSerializer:
    def serialize(self, obj):
        return json.dumps(obj.__dict__)

    def deserialize(self, cls, s):
        return cls(**json.loads(s))

class User:
    serializer = JsonSerializer()      # 합성

    def __init__(self, name, email):
        self.name = name
        self.email = email

u = User("alice", "a@x.com")
print(User.serializer.serialize(u))
# 교체 쉬움 — User.serializer = XmlSerializer()
mixin 이름 충돌이 만드는 문제·python
class LoggerMixin:
    def log(self, msg):
        print(f"[LOG] {msg}")

class MetricsMixin:
    def log(self, msg):              # 같은 이름 — 충돌
        print(f"[METRIC] {msg}")

class Service(LoggerMixin, MetricsMixin):
    pass

s = Service()
s.log("hello")     # [LOG] hello   — LoggerMixin 이김 (MRO 첫 번째)

# 리팩터 팁 — 충돌 피하려고 하나 rename
# class LoggerMixin: def log_event(self, ...)
# class MetricsMixin: def emit_metric(self, ...)

External links

Exercise

인스턴스 속성을 비교해 eq와 hash를 제공하는 EqualityMixin을 만들고 Coordinate, Color, Tag에 적용해. 같은 기능을 Comparator 도우미를 소유하는 합성으로도 구현한 뒤 어느 쪽이 메서드의 주인과 의존성을 더 분명하게 보여주는지 비교해.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.