카테고리 없음

[OVERVIEW] IOC(Inversion of Control)

기록해연 2024. 10. 25. 22:34

기술면접때 의존성 역전(IoC)에 대해 설명해달라는 질문을 받았는데 대답을 못했었다. 근데 대답을 못하는 내가 좀 당황스러웠음. 이런 기초적인 것도 설명을 못한다고...? 스스로 너무 어이가 없었음. 암튼 그래서 정리.


IoC란?

Inversion of Control (IoC)는 객체의 생성과 제어를 개발자가 직접 하지 않고, 객체의 생성 의존성 관리 책임 외부로 넘겨, 코드의 결합도를 낮추고 유연성을 높이는 소프트웨어 설계 원칙이다.

 

객체지향 프로그래밍을 지향하는 프로그램에서 객체는 다른 객체와 협력하여 동작한다. 가령, 클래스A가 클래스B의 기능을 필요로 하면, A는 B를 생성하여 사용한다. 그러나 이 과정에서 A가 B에 의존을 하게되어 결합도가 높아진다. Spring 같은 많은 프레임워크는 A가 직접 B를 생성하지 않고 외부에서 B를 주입하는 방식 등으로 이러한 문제를 해결한다.

 

IoC의 장점

  • 결합도 감소
  • 테스트 용이
  • 유지보수성 향상: 객체 생성 로직이 외부로 분리되어 코드가 더 간결해지고 수정시 유연한 대처가 가능하다.

 

IoC 구현 방법

IoC는 주로 두 가지 방식으로 구현된다.

  • 의존성 주입 (Dependency Injection, DI) : 가장 널리 사용되는 IoC의 구현 방법으로, 필요한 객체를 생성자메서드, 또는 필드를 통해 외부에서 주입하는 방법이다.
  • 서비스 로케이터 패턴(Service Locator Pattern) : 객체가 필요한 의존성을 직접 요청하는 방식으로, 외부의 특정 위치(서비스 로케이터)에서 의존성을 가져오는 방법이다.

 



(이하 내용은 예시 코드)

 

 예시 

1. 의존성 주입

/* 
 투수와 포수는 독립적인 클래스.
 다른 클래스에서 사용되지만, 직접적으로 서로를 의존하지 않음.
*/

// 투수 객체
public class Pitcher {
    private String name;

    // 생성자에서 이름을 받아서 설정
    public Pitcher(String name) {
        this.name = name;
    }

    public void pitch() {
        System.out.println(name + "가 공을 던진다.");
    }
}


// 포수 객체
public class Catcher {
    private String name;

    // 생성자에서 이름을 받아서 설정
    public Catcher(String name) {
        this.name = name;
    }

    public void catchBall() {
        System.out.println(name + "가 공을 받는다.");
    }
}
public class BaseballTeam {
    private Pitcher pitcher;
    private Catcher catcher;

    // 의존성 주입: 생성자 주입
    public BaseballTeam(Pitcher pitcher, Catcher catcher) {
        this.pitcher = pitcher;
        this.catcher = catcher;
    }

    public void playGame() {
        pitcher.pitch();
        catcher.catchBall();
    }
}
public class Main {
    public static void main(String[] args) {
        // 외부에서 의존성 주입
        Pitcher pitcher = new Pitcher("동주");
        Catcher catcher = new Catcher("인서");
        
        // 팀에 필요한 선수들을 주입
        BaseballTeam team = new BaseballTeam(pitcher, catcher);
        
        // 경기 진행
        team.playGame();  // 출력: "동주가 공을 던진다." "인서가 공을 받는다."
    }
}

 

2. 서비스 로케이터 패턴

public interface Player {
    void play();
    String getPosition();
}

 

public class Pitcher implements Player {
    private String name;

    // 생성자를 통해 이름을 설정
    public Pitcher(String name) {
        this.name = name;
    }

    @Override
    public void play() {
        System.out.println(name + "가 공을 던진다.");
    }

    @Override
    public String getPosition() {
        return "Pitcher";
    }
}
public class Catcher implements Player {
    private String name;

    // 생성자를 통해 이름을 설정
    public Catcher(String name) {
        this.name = name;
    }

    @Override
    public void play() {
        System.out.println(name + "가 공을 받는다.");
    }

    @Override
    public String getPosition() {
        return "Catcher";
    }
}
public class Fielder implements Player {
    private String name;

    // 생성자를 통해 이름을 설정
    public Fielder(String name) {
        this.name = name;
    }

    @Override
    public void play() {
        System.out.println(name + "가 공을 친다.");
    }

    @Override
    public String getPosition() {
        return "Fielder";
    }
}

 

 

관객은 선발 선수가 누군지 모르고, 단순히 PlayerLocator를 통해 서비스를 호출하여 동작한다.

import java.util.HashMap;
import java.util.Map;

public class PlayerLocator {
    private static Map<String, Player> cache = new HashMap<>();

    public static Player getPlayer(String position) {
        Player player = cache.get(position);
        if (player != null) {
            return player;
        }

        // 포지션에 맞는 선수 객체 생성 및 캐싱
        // 캐시에 있으면 해당 서비스 인스턴스를 반환.
        if ("투수".equals(position)) {
            player = new Pitcher("현진");
        } else if ("포수".equals(position)) {
            player = new Catcher("재훈");
        } else if ("야수".equals(position)) {
           player = new Fielder("태연");
        }
        
        // 캐시에 없으면 새로 인스턴스를 생성한 후 캐시에 저장하고 반환.
        if (player != null) {
            cache.put(position, player);
        }

        return player;
    }
}
public class Audience {
    public static void main(String[] args) {
        // 관람객이 서비스 로케이터를 통해 포지션별로 요청
        Player pitcher = PlayerLocator.getPlayer("투수");
        System.out.println("오늘 선발투수가 누구더라?");
        pitcher.play();  // "현진이 공을 던진다."

        Player catcher = PlayerLocator.getPlayer("포수");
        System.out.println("오늘 선발포수가 누구더라?");
        catcher.play();  // "재훈이 공을 받는다."
        
        Player fielder = PlayerLocator.getPlayer("야수");
        System.out.println("오늘 3번 타자가 누구더라?");
        fielder.play();  // "태연이 공을 친다."
    }
}