의존성
의존성(Dependency) 이란?
한 클래스가 다른 클래스의 객체를 직접 생성하거나 사용하는 관계
의존성이 높으면 코드 변경이 어렵고, 테스트가 어려움
의존성이 높은 코드
class MySQLDatabase {
void connect() { System.out.println("MySQL 연결"); }
}
class DataManager {
MySQLDatabase database = new MySQLDatabase(); // 특정 구현체에 직접 의존
}
➡︎ DataManger 가 MySQLDatabase 와 강하게 결합되어 다른 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가지 방식
- 생성자 주입 (Constructor Injection): 생성자를 통해 의존 객체를 주입 ex) new DataManager(new MySQLDatabase())
- 세터 주입 (Setter Injection): 세터 메서드를 통해 의존 객체 주입 ex) manager.setDatabase(new MySQLDatabase())
- 필드 주입 (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 블록을 사용하여 멀티스레드 환경에서도 안전한 싱글톤 유지
'백엔드 기본 개념 정리 > 객체지향 프로그래밍 (OOP)' 카테고리의 다른 글
객체지향의 한계와 대안, 구성(Composition) (0) | 2025.03.10 |
---|---|
객체지향 설계 원리(SOLID) & 추상화(Abstraction) (1) | 2025.03.07 |
상속(Inheritance)과 다형성(Polymorphism) (0) | 2025.03.06 |
객체지향의 기본 개념(클래스, 객체, 캡슐화) (0) | 2025.03.05 |