의존성

의존성(Dependency) 이란?

한 클래스가 다른 클래스의 객체를 직접 생성하거나 사용하는 관계

의존성이 높으면 코드 변경이 어렵고, 테스트가 어려움

 

의존성이 높은 코드

class MySQLDatabase {
    void connect() { System.out.println("MySQL 연결"); }
}

class DataManager {
    MySQLDatabase database = new MySQLDatabase(); // 특정 구현체에 직접 의존
}

 

➡︎ DataMangerMySQLDatabase 와 강하게 결합되어 다른 DB 로 변경하려면 기존 코드 수정 필요

➡︎ 의존성 주입(DI) 을 사용하여 DataManager 가 특정 DB 구현체에 직접 의존하지 않도록 변경


의존성 주입(Dependency Injection) 이란?

객체가 직접 다른 객체를 생성하는 것이 아니라, 외부에서 주입받는 방식

객체 간 결합도를 낮추고 유연한 코드를 만들 수 있음

 

의존성 주입 적용 코드

interface Database {
    void connect();
}

class MySQLDatabase implements Database {
    public void connect() { System.out.println("MySQL 연결"); }
}

class PostgreSQLDatabase implements Database {
    public void connect() { System.out.println("PostgreSQL 연결"); }
}

class DataManager {
    private Database database;

    // 생성자를 통한 의존성 주입 (Constructor Injection)
    DataManager(Database database) {
        this.database = database;
    }

    void connectDatabase() {
        database.connect();
    }
}

// DI 적용 후 사용
public class Main {
    public static void main(String[] args) {
        Database db = new MySQLDatabase(); // MySQL 사용
        DataManager manager = new DataManager(db);
        manager.connectDatabase(); // 출력: MySQL 연결
    }
}

 

➡︎ DataManager 가 특정 DB 에 직접 의존하지 않음

➡︎ 인터페이스를 사용하여 유연한 확장 가능

➡︎ Mock 객체를 주입하여 테스트 가능 ➔ 테스트 용이성 향상

 

의존성 주입의 3가지 방식

  1. 생성자 주입 (Constructor Injection): 생성자를 통해 의존 객체를 주입 ex) new DataManager(new MySQLDatabase())
  2. 세터 주입 (Setter Injection): 세터 메서드를 통해 의존 객체 주입 ex) manager.setDatabase(new MySQLDatabase())
  3. 필드 주입 (Field Injection): 필드에 직접 주입 ex) @Autowired private Database db; (Spring 의 경우)

⭐️ 실무에서는 주로 생성자 주입을 사용 - 테스트 용이성 & 불변성 유지 가능


싱글톤 패턴

싱글톤 패턴(Singleton Pattern) 이란?

애플리케이션 전체에서 하나의 객체만 유지하는 디자인 패턴

객체가 불필요하게 여러 개 생성되는 것을 방지하여 메모리 낭비를 줄일 수 있음

 

싱글톤 패턴 적용 코드

class Singleton {
    private static Singleton instance;

    private Singleton() { } // private 생성자 (외부에서 객체 생성 차단)

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

    public void showMessage() {
        System.out.println("싱글톤 인스턴스 실행");
    }
}

// 싱글톤 객체 사용
public class Main {
    public static void main(String[] args) {
        Singleton obj1 = Singleton.getInstance();
        Singleton obj2 = Singleton.getInstance();

        obj1.showMessage();  // 출력: 싱글톤 인스턴스 실행

        System.out.println(obj1 == obj2); // true (같은 객체)
    }
}

 

➡︎ getInstance() 를 호출하면 항상 같은 인스턴스 반환

➡︎ new Singleton() 을 직접 호출할 수 없음 (private 생성자)

➡︎ 전역적으로 단 하나의 객체만 유지


싱글톤 패턴의 장단점

장점

  • 객체의 공유: 모든 클래스가 같은 인스턴스를 공유하여 메모리 사용 절약
  • 객체 생성 제한: new 키워드로 객체를 여러 번 생성하는 것을 방지

단점

  • 멀티스레드 환경에서 동기화 문제 발생 가능: 여러 스레드가 동시에 getInstance() 를 호출하면 동시에 객체가 여러 개 생성될 위험
    ➡︎ 이중 체크 락킹(Double-Checked Locking) 기법 사용

멀티스레드 안전한 싱글톤

class ThreadSafeSingleton {
    private static volatile ThreadSafeSingleton instance;
    
    private ThreadSafeSingleton() { }

    public static ThreadSafeSingleton getInstance() {
        if (instance == null) {
            synchronized (ThreadSafeSingleton.class) {
                if (instance == null) {
                    instance = new ThreadSafeSingleton();
                }
            }
        }
        return instance;
    }
}

 

➡︎ synchronized 블록을 사용하여 멀티스레드 환경에서도 안전한 싱글톤 유지

+ Recent posts