의존성

    의존성(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