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 — is-a 인 척 안 하는 상속 패턴

Mixin 은 다른 거랑 결합 위해 디자인된 클래스 — 단독 인스턴스화 X. 보통 집중된 동작 청크 — SerializableMixinto_json() 추가, LoggingMixinlog() 추가. 받는 클래스가 mixin 상속해서 그 동작 받음. 이름 컨벤션 도움 — NameMixin 이 명사 X, 라벨처럼 읽혀.

합성 선호인데 mixin 이 왜 있나

합성이 권장 디폴트면 왜 mixin? 다음일 때 값어치 — (a) 동작이 진짜 모든 메서드 호출에 있어야 할 때, (b) 동작이 호스트 클래스 속성에 의존 (mixin 이 어떤 클래스든 자기 포함한 클래스의 self.x 사용 가능), (c) 다형 dispatch 필요 — 구체 클래스 무관하게 "serializable" 객체와 작동하는 코드.

위험 — diamond 와 의외 override

같은 메서드 정의하는 두 mixin 이 diamond 만들어. 계층에 mixin 추가하면 의도 안 한 메서드 override 가능. MRO 가 누가 이길지 결정. mixin 은 충돌 가능성 낮은 직교 관심사용 — 겹치는 거엔 X.

합성이 이길 때

대부분 시간. self.serializer = JsonSerializer()class MyClass(JsonSerializerMixin): 보다 명확. 클래스 변경 없이 serializer 교체 가능. 계층 평탄하게 유지. 테스트가 fake 주입 가능. 비용은 한 단계 indirection (obj.serializer.serialize() vs obj.serialize()) — 보통 그 값어치.

Pythonic Way: 다음일 때 mixin — (1) 동작이 진짜 cross-cutting (클래스의 모든 메서드가 봐야), (2) 호스트 클래스가 mixin 메서드와 충돌 안 함, (3) isinstance(obj, SerializableMixin) 이 의미 있어야. 아니면 합성.

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

EqualityMixin 정의 — __eq__ (모든 인스턴스 속성 비교) + __hash__ (frozenset 항목). 무관한 세 클래스 (Coordinate(x, y), Color(r, g, b), Tag(name)) 다 EqualityMixin 사용. 셋 다 동등성 정확히 작동 보여. 그 다음 합성 기반 — 같은 로직의 Comparator 헬퍼 클래스, 각 클래스가 comparator 속성 보유. 어느 게 더 깔끔하게 느끼는지 비교.

Progress

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

댓글 0

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

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