싱글톤(Singleton) 패턴과 Kotlin에서 object 객체

2025. 1. 22. 17:08개발/소프트웨어 디자인 패턴

728x90
반응형

🌟 들어가기 전

요즘 메모리 최적화 이슈에 대해 관심이 많다.

그렇게 메모리에 대해서 보다가 유독 눈에 띄는 게 있었다.

 

바로 Kotlin에서 Object 객체 !!

 

늘 그냥 쓰라니까 써왔던.

익숙하지만 낯선.

 

그 존재에 대해 파헤쳐봤다.

 

🔍 세부 내용

1. 싱글톤 패턴(Singleton Pattern)

싱글톤 패턴은 객체의 전역적 유일성을 보장하는 디자인 패턴이다.

이를 통해 하나의 인스턴스만 사용하며, 여러 곳에서 동일한 객체를 공유하거나 관리할 수 있다.

  • 전역상태 관리: 애플리케이션에서 하나의 상태를 유지하고 여러 곳에서 공유할 때 사용 (ex. 로그인 세션, 앱 설정, DB 연결)
  • 자원 절약: 객체가 여러번 생성되지 않도록 보장하여 메모리 사용 절감
  • 일관된 상태 유지: 하나의 인스턴스를 통해 상태를 일관되게 관리
object DatabaseManager {
    private val connection = "Database Connection"

    fun connect() {
        println("Connecting to the database: $connection")
    }

    fun disconnect() {
        println("Disconnecting from the database.")
    }
}

fun main() {
    // DatabaseManager 객체는 애플리케이션 전역에서 하나만 존재
    DatabaseManager.connect()   // Connecting to the database: Database Connection
    DatabaseManager.disconnect() // Disconnecting from the database.
}

 

2. object와 companion object의 차이점

Kotlin에서는 싱글톤 패턴을 구현할 때 object 객체를 사용한다.

  • 단일 인스턴스 보장: object로 선언된 객체는 전역적으로 유일한 인스턴스를 생성
  • 자동 초기화: object는 첫 번째 사용 시점에 초기화 (lazy initialization)
  • 상속 불가: object는 상속할 수 없고, 이를 구현하려면 class와 interface를 활용해야 함
  • 동반 객체(Companion Object): 클래스 내에 object를 선언하여 해당 클래스의 인스턴스 없이 접근할 수 있는 정적 멤버

그렇다면 object와 companion object는 어떤 차이가 있을까?

class MyClass {
		/* 클래스 안에 속한 단 하나의 instance */
    companion object {
        const val CONSTANT = "This is a constant"
        
        fun staticMethod() {
            println("This is a static method")
        }
    }
}

fun main() {
    println(MyClass.CONSTANT)  // 접근: MyClass.CONSTANT
    MyClass.staticMethod()     // 접근: MyClass.staticMethod()
}

 

companion object는 클래스 내부에 선언되며, 해당 클래스의 정적 멤버를 구현할 때 사용된다.

반면 object는 클래스 밖에서 독립적인 싱글톤 객체를 정의할 때 사용한다.

따라서 object와 companion은 사용성에 따라 나뉘게 된다.

 

2-1) companion object를 사용하는 이유

  • 클래스와 관련된 정적 멤버를 정의하기 위해

2-2) object를 사용하는 이유

  • 독립적인 싱글톤 객체를 만들고 싶을 때 (특정 클래스와 관련 X)
  • 상속/구현이 필요 없는 전역 유틸리티를 만들 때

2-3) 정리

특징 companion object object
클래스와의 관계 특정 클래스와 강하게 연결됨 독립적이며 특정 클래스와 관계없음
사용 목적 클래스와 관련된 정적 멤버 정의 싱글톤 객체 생성, 전역 유틸리티 또는 독립적인 데이터 관리
접근 방식 ClassName.member처럼 클래스 이름으로 접근 ObjectName.member처럼 객체 이름으로 접근
인스턴스 멤버 접근 클래스의 멤버와 정적 멤버를 함께 사용할 수 있음 독립적이며 클래스 인스턴스와는 별개로 동작

 

결국 companion object를 선택할지는 “이 정적 멤버가 해당 클래스와 강하게 연관된 기능인가?”에 따라 결정하면 된다.

 

3. Java-static과 Kotlin-companion object의 차이점

Java에서는 static으로 정적 변수와 메서드를 구현할 수 있다.

그럼 companion object는 static과 같은 기능인걸까?

 

두 경우 모두 클래스에 속하는 메서드와 속성인스턴스 생성 없이 사용할 수 있다.

하지만 완전히 동일하지는 않다.

class Parent {
    static void greet() {
        System.out.println("Hello from Parent");
    }
}

class Child extends Parent {
    // greet()는 오버라이드 불가
    static void greet() {
        System.out.println("Hello from Child");
    }
}

public class Main {
    public static void main(String[] args) {
        Parent.greet(); // "Hello from Parent"
        Child.greet();  // "Hello from Child"
    }
}
  • 정적 메서드 및 변수는 클래스 자체에 속하며, 클래스 로드 시 메모리에 올라간다.
  • static은 상속은 가능하지만 override는 불가능하다.
open class Parent {
    companion object {
        open fun greet() {
            println("Hello from Parent")
        }
    }
}

class Child : Parent() {
    companion object {
        override fun greet() {
            println("Hello from Child")
        }
    }
}

fun main() {
    Parent.greet()  // "Hello from Parent"
    Child.greet()   // "Hello from Child"
}
  • companion object는 객체이며, 클래스와 연결된 싱글톤 객체이다.
  • 클래스 이름으로 접근 가능하지만, 실제로는 클래스와 연결된 객체(companion object)의 메서드/속성을 호출하는 것이다.
  • 상속이 가능하며, override도 가능하다.

 

여기서 잠깐!

위에서 잠깐 언급했지만 object 객체는 상속과 override 모두 안됨

object Parent {
    open fun display() { // 오류! 'open' 키워드 사용 불가
        println("Parent object")
    }
}

// 불가능: object는 상속할 수 없음
object Child : Parent() // 오류!

 

🤔 배운 점 & 느낀 점

언어의 세계는 무한히 넓고도 깊구나.

그동안 얼마나 대충 사용해왔는지 느끼는 시간이었다.

 

요즘 메모리 최적화에 관심이 많은데 그러면서 싱글톤 객체까지 넘어오게 됐다.

대충 들어만 봤고 그냥 그런가보다 하고 사용해왔던건데 이렇게 자세히 들여다본적은 없다는 걸 느꼈다.

 

하나를 쓰더라도 알고 쓰자.

728x90
반응형