본문 바로가기
Artificial Intelligence/60. Python

[PYTHON] 클래스 내부 인스턴스화 방법 3가지와 의존성 해결 및 합성 차이

by Papa Martino V 2026. 4. 7.
728x90

상속(Inheritance) vs 합성(Composition)
상속(Inheritance) vs 합성(Composition)

 

파이썬 객체 지향 프로그래밍(OOP)을 설계할 때 초보 개발자들이 가장 많이 던지는 질문 중 하나는 "클래스 안에서 다른 클래스의 객체를 생성해도 괜찮은가?"입니다. 결론부터 말씀드리면, 이는 '합성(Composition)'이라는 매우 강력하고 권장되는 설계 기법입니다. 하지만 무분별한 클래스 내부 인스턴스화는 클래스 간의 결합도를 높여 유지보수를 어렵게 만드는 '스파게티 코드'의 원인이 되기도 합니다. 오늘 이 글에서는 클래스 내부에서 다른 클래스를 인스턴스화하는 정석적인 방법 3가지와 강한 결합 문제를 해결하는 의존성 주입 전략의 차이를 심도 있게 분석합니다.


1. 클래스 내부 인스턴스화의 본질: 상속보다 합성

객체 지향 설계의 오랜 격언 중 하나는 "상속(Inheritance)보다는 합성(Composition)을 선호하라"는 것입니다. 상속이 'A는 B이다(Is-A)' 관계라면, 합성은 'A는 B를 가지고 있다(Has-A)' 관계입니다. 클래스 내부에서 다른 클래스를 인스턴스화하여 속성으로 사용하는 것은 객체의 기능을 유연하게 확장할 수 있는 가장 표준적인 해결 방법입니다.


2. 인스턴스화 위치에 따른 설계적 차이 비교

다른 클래스를 어디서 인스턴스화하느냐에 따라 객체의 생명 주기와 유연성이 달라집니다. 주요 방식 3가지를 비교해 보았습니다.

구분 생성자 내부 생성 (Hard-Coded) 의존성 주입 (Dependency Injection) 지연 로딩 (Lazy Loading)
구현 방식 __init__에서 직접 Other() 호출 외부에서 생성된 객체를 인자로 전달받음 메서드가 처음 호출될 때 인스턴스화
결합도 매우 높음 (강한 결합) 낮음 (유연한 구조) 중간
테스트 용이성 어려움 (Mocking 필요) 매우 쉬움 (가짜 객체 주입 가능) 보통
추천 사례 매우 단순하고 불변적인 관계 대규모 프로젝트, 테스트가 중요한 기능 생성 비용이 큰 무거운 객체

3. 유연한 아키텍처를 위한 3가지 실무 해결 전략

3.1. 생성자 주입(Constructor Injection)을 통한 결합도 완화

클래스 내부에서 직접 다른 클래스명을 언급하는 대신, 생성자의 파라미터로 객체를 전달받으십시오. 이는 클래스가 특정 구현체에 종속되는 것을 막고, '추상화'에 의존하게 만드는 고도화된 해결 방법입니다.

3.2. 팩토리 메서드(Factory Method) 활용

객체 생성 로직이 복잡하다면 별도의 클래스 메서드(Factory)를 통해 인스턴스화 과정을 캡슐화하십시오. 이는 '객체 생성'과 '객체 사용'의 책임을 명확히 분리하는 전략입니다.

3.3. 프로퍼티(Property)를 이용한 지연 초기화

내부 클래스가 항상 필요한 것이 아니라면 @property를 사용하여 실제 데이터가 필요한 시점에 인스턴스화하십시오. 이는 메모리 자원을 효율적으로 관리하는 지능적인 해결 방법입니다.


4. Sample Example: 엔진과 자동차의 합성 구조

다음은 자동차 클래스가 엔진 클래스를 내부에서 소유하는 전형적인 합성 구조와 의존성 주입의 차이를 보여주는 실전 예제입니다.


class Engine:
    def __init__(self, type="가솔린"):
        self.type = type

    def start(self):
        return f"{self.type} 엔진이 가동됩니다."

# 1. 강한 결합 (안좋은 사례: 클래스 내부에서 직접 인스턴스화)
class BadCar:
    def __init__(self):
        self.engine = Engine() # Engine 클래스가 바뀌면 Car도 수정해야 함

# 2. 약한 결합 (좋은 사례: 의존성 주입 활용)
class GoodCar:
    def __init__(self, engine: Engine):
        self.engine = engine # 외부에서 엔진을 결정하여 주입함

    def drive(self):
        print(f"자동차 이동 시작: {self.engine.start()}")

# 실행 테스트
gasoline_engine = Engine("가솔린")
electric_engine = Engine("전기")

my_ev = GoodCar(electric_engine) # 자동차 수정 없이 엔진만 교체 가능
my_ev.drive()

5. 내부 인스턴스화 시 주의해야 할 해결 과제

클래스 안에서 다른 클래스를 생성할 때 가장 주의해야 할 점은 '순환 참조(Circular Reference)'입니다. 클래스 A가 B를 인스턴스화하고, 클래스 B가 다시 A를 인스턴스화하려 하면 무한 루프에 빠지거나 메모리 누수가 발생할 수 있습니다. 이를 해결하기 위해 타입 힌팅 시 from __future__ import annotations를 사용하거나, 객체 간의 참조 흐름을 단방향으로 설계하는 지혜가 필요합니다.

6. 결론: 언제 내부에서 인스턴스화해야 할까?

클래스 내부에서 다른 클래스를 인스턴스화하는 것은 잘못된 것이 아닙니다. 하지만 "그 클래스가 없어도 이 클래스가 독립적으로 존재할 수 있는가?"를 자문해 보십시오. 만약 그렇다면 의존성 주입을 통해 외부에서 객체를 전달받는 것이 정답입니다. 반대로 두 클래스가 생명 주기를 함께하는 떼려야 뗄 수 없는 관계라면 내부 인스턴스화(합성)를 통해 캡슐화를 강화하십시오. 이러한 균형 잡힌 설계가 곧 파이썬 전문가로 가는 길입니다.


내용 출처 및 참고 문헌

  • Python Design Patterns: "Composition vs Inheritance in Practice" (2026)
  • Clean Code in Python by Mariano Anaya: "Dependency Injection and Decoupling"
  • Real Python: "Object-Oriented Programming (OOP) in Python 3"
728x90