Java에서 다른 클래스를 효과적으로 관리하는 방법
Java 애플리케이션을 개발하다 보면 여러 객체를 체계적으로 관리해야 하는 상황이 자주 발생한다. 학생 정보를 관리하는 시스템, 게임의 플레이어를 관리하는 매니저, 데이터베이스 연결을 관리하는 풀 등이 대표적인 예다. 이번 글에서는 Java에서 다른 클래스를 관리하는 클래스를 효과적으로 구현하는 방법을 알아보겠다.
기본 접근: 컬렉션 활용
가장 기본적인 방법은 List나 Set 같은 컬렉션을 사용하는 것이다. ArrayList를 내부 필드로 두고, 추가, 삭제, 조회 메서드를 제공하는 매니저 클래스를 만들면 된다. 이 방식은 구현이 간단하고 직관적이지만, 특정 객체를 찾을 때 선형 탐색이 필요하다는 단점이 있다.
public class StudentManager {
private List<Student> students;
public StudentManager() {
this.students = new ArrayList<>();
}
public void addStudent(Student student) {
students.add(student);
}
public void removeStudent(String studentId) {
students.removeIf(s -> s.getId().equals(studentId));
}
public Student findStudent(String studentId) {
return students.stream()
.filter(s -> s.getId().equals(studentId))
.findFirst()
.orElse(null);
}
public List<Student> getAllStudents() {
return new ArrayList<>(students); // 방어적 복사
}
}
효율성을 위한 Map 활용
ID나 고유 키로 객체를 자주 조회해야 한다면 HashMap을 사용하는 것이 좋다. 키-값 구조로 객체를 저장하면 O(1) 시간 복잡도로 빠르게 찾을 수 있다. 특히 대량의 데이터를 다루거나 조회 성능이 중요한 경우에 적합하다.
public class ProductManager {
private Map<String, Product> products;
public ProductManager() {
this.products = new HashMap<>();
}
public void addProduct(Product product) {
products.put(product.getId(), product);
}
public Product getProduct(String productId) {
return products.get(productId);
}
public void removeProduct(String productId) {
products.remove(productId);
}
public Collection<Product> getAllProducts() {
return new ArrayList<>(products.values());
}
}
싱글톤 패턴 적용
애플리케이션 전체에서 하나의 매니저 인스턴스만 필요한 경우 싱글톤 패턴을 적용할 수 있다. 생성자를 private으로 만들고 정적 메서드로 인스턴스를 제공하면, 전역적으로 접근 가능하면서도 단일 인스턴스를 보장할 수 있다. 게임 매니저나 설정 관리자처럼 전역 상태를 관리하는 경우에 유용하다.
public class GameManager {
private static GameManager instance;
private List<Player> players;
private GameManager() {
this.players = new ArrayList<>();
}
public static GameManager getInstance() {
if (instance == null) {
instance = new GameManager();
}
return instance;
}
public void addPlayer(Player player) {
players.add(player);
}
// 기타 관리 메서드들...
}
팩토리 패턴과의 결합
관리 클래스가 객체의 생성까지 담당한다면 팩토리 패턴을 함께 사용할 수 있다. 연결 풀이나 스레드 풀처럼 리소스의 생명주기 전체를 관리해야 할 때 이 패턴이 빛을 발한다. 최대 개수 제한, 재사용 로직 등을 매니저에 포함시켜 리소스를 효율적으로 관리할 수 있다.
public class ConnectionManager {
private List<Connection> connections;
private int maxConnections;
public ConnectionManager(int maxConnections) {
this.connections = new ArrayList<>();
this.maxConnections = maxConnections;
}
public Connection createConnection(String host) {
if (connections.size() >= maxConnections) {
throw new IllegalStateException("최대 연결 수 초과");
}
Connection conn = new Connection(host);
connections.add(conn);
return conn;
}
public void closeConnection(Connection conn) {
conn.close();
connections.remove(conn);
}
public void closeAll() {
connections.forEach(Connection::close);
connections.clear();
}
}
설계 시 주의사항
관리 클래스를 설계할 때는 몇 가지 원칙을 지켜야 한다. 첫째, 내부 컬렉션을 직접 노출하지 않고 메서드를 통해서만 접근하도록 캡슐화해야 한다. 둘째, getter에서 컬렉션을 반환할 때는 원본이 아닌 복사본을 반환하는 방어적 복사를 적용해야 한다. 셋째, 관리 클래스는 객체의 생성, 조회, 수정, 삭제라는 명확한 책임만 가져야 한다. 마지막으로 멀티스레드 환경이라면 ConcurrentHashMap이나 synchronized 키워드를 활용해 스레드 안전성을 확보해야 한다.
이러한 패턴들을 상황에 맞게 선택하고 조합하면 유지보수하기 좋고 확장 가능한 코드를 작성할 수 있다.
'ETC > 1. Today I Learned' 카테고리의 다른 글
| [Java] 제네릭 공부하기 - 1장 (0) | 2025.12.19 |
|---|---|
| [Git] 내 코드를 다른 레포지토리의 코드로 덮어씌우기 (0) | 2025.12.16 |
| [Java] 스레드, Runnable, join() (1) | 2025.12.12 |
| [Git 기초] Fork & Clone, Fetch & Pull 배워보기 (0) | 2025.12.11 |
| [Java] 특정 문자열을 입력해야만 무한루프를 종료하는 기능 구현 (0) | 2025.12.08 |