[Java] 의존 관계, IoC/DI 이해와 전략(Strategy) 패턴, 파서드(Facade) 패턴 이해하기

    스프링을 이해하는데 POJO(Plain Old Java Object)를 기반으로 스프링 삼격형이라는 애칭을 가진 IoC/DI, AOP, PSA라고 하는 스프링 3대 프로그래밍 모델에 대한 이해가 필수다.

     

    IoC/DI 이해와 전략 패턴

    IoC(Inversion of Control)는 제어의 역전 DI(Dependency Injection) 의존성 주입 이라고 한다. 의존 관계라는 것은 프로그래밍에서 new 객체 생성을 뜻한다.

     

    아래 소스를 보면 Car 클래스에서 생성자를 통해 tire 객체를 new 로 생성해서 필드에 주입한다.

    이 과정에서 Car 클래스는 Tire 클래스에 의존한다 라는 뜻이 된다.

    public class Car {
        Tire tire;
    
        public Car() {
            tire = new KoreaTire();
        }
    }

     

    위 관계처럼 의존 관계가 형성 되면 다른 타이어로 교체를 하려고 할 때 Car 클래스를 수정 해야 된다는 것이 되는데 이건 OCP 개방폐쇄 원칙에 어긋난다. (확장에는 유연해야 되지만 수정은 안되어야 하기 때문에)

     

    1) Car와 Tire 의존 관계가 있는 코드

    public interface Tire {
        String getBrand();
    }
    public class KoreaTire implements Tire {
        @Override
        public String getBrand() {
            return "코리아 타이어";
        }
    }
    public class AmericaTire implements Tire {
        @Override
        public String getBrand() {
            return "미국 타이어";
        }
    }
    
    public class Car {
        Tire tire;
    
        public Car() {
            tire = new KoreaTire();
            // tire = new AmericaTire();
        }
    
        public String getTireBrand() {
            return "장착된 타이어: " + tire.getBrand();
        }
    }
    public class Driver {
        public static void main(String[] args) {
            Car car = new Car();
            System.out.println(car.getTireBrand());
        }
    }
    

     

    Tire 인터페이스를 상속받아 새로운 타이어를 계속 만들어내는 구조다. 그리고 Car 클래스의 생성자에서 새로운 Tire 객체를 생성해서 넣는다.  앞서 설명했듯이 새로운 타이어가 계속 생길수록 Car에서는 새로운 타이어로 교체하려고 할 때 Car 클래스도 고쳐야 된다는 문제점이 있다. 그래서 Car와 Tire 클래스의 의존관계를 없애야 되는 것이 해결 관점이다.

     

     

    2) Car와 Tire 의존 관계가 없는 코드 및 전략 패턴

    public class Car {
        Tire tire;
    
        public Car(Tire tire) {
            this.tire = tire;
        }
    
        public String getTireBrand() {
            return "장착된 타이어: " + tire.getBrand();
        }
    }
    public class Driver {
        public static void main(String[] args) {
            Tire tire = new KoreaTire();
            //Tire tire = new AmericaTire();
            Car car = new Car(tire);
    
            System.out.println(car.getTireBrand());
        }
    }

     

    위 코드에서 Car, Driver 클래스만 수정하였다. Car클래스에서 Tire 객체를 생성하는 것이 아닌 Driver에서 Tire 객체를 생성해서 Car에 집어 넣었다. 이것이 의존성을 주입하는 것이 돼서 DI가 된다. 

    Car와 Tire의 의존관계를 굳이 없애는 이유는 Tire의 유연한 확장을 위해서 이다. Tire는 확장할때 Car는 그대로 재사용 가능하다는 것이다. 이것은 OCP를 지키는 설계가 된다(Tire 확장에는 열려있지만 Car 변경에는 닫혀있어야 한다.)

     

    전략 패턴(Strategy Pattern)

    위 코드는 Driver의 main() 메서드에서 자신이 원하는 Tire 객체를 생성해서 상황에 맞게 변경해서 사용할 수 있어서, 디자인 패턴의 꽃이라고 하는 전략 패턴을 응용하고 있 전략 패턴은 아래 3가지 요소를 충족해야 한다.

     

    전략 패턴의 3요소 

     - 전략 메서드를 가진 전략 객체 : Tire, KoreaTire, AmericaTire

     - 전략 객체를 사용하는 컨텍스트 : Car의 getTireBrand() 메서드

     - 클라이언트: Driver의 main() 메서드

     

     

    파서드 패턴(Facade Pattern)

    위 코드는 전략패턴이고 아래 코드는 파서드 패턴이다. 전략패턴과의 큰 차이점은 OCP원칙을 지키지 않고, CarFacade 클래스에서 Tire와 Engine 객체를 직접 생성한다는 것이다. 

    파서드 패턴은 시스템의 복잡성을 숨기고 클라이언트단 에서는 구조를 단순화 하는 패턴이다.

    Car 구조의 복잡성을 위해 위 코드에서 Engine을 추가 하였다.  Engine 인터페이스와 KoreaEngine, AmericaEngine 클래스를 추가하고 Driver에서 Engine 클래스를 만들어서 Car 클래스에 주입 시켰다.

     

    public interface Engine {
        String getBrand();
    }
    
    public class KoreaEngine implements Engine {
        @Override
        public String getBrand() {
            return "코리아 엔진";
        }
    }
    public class AmericaEngine implements Engine {
        @Override
        public String getBrand() {
            return "미국 엔진";
        }
    }
    
    public class CarFacade {
        Tire tire;
        Engine engine;
    
        public CarFacade() {
            this.tire = new AmericaTire();
            this.engine = new KoreaEngine();
        }
    
        public String getTireBrand() {
            return "장착된 타이어: " + tire.getBrand();
        }
    
        public String getEndgineBrand() {
            return "장착된 엔진: " + engine.getBrand();
        }
    }
    public class Driver {
        public static void main(String[] args) {
            CarFacade car = new CarFacade();
    
            System.out.println(car.getTireBrand());
            System.out.println(car.getEndgineBrand());
        }
    }

     

    CarFacade 클래스에서 생성자를 통해 Engine와 Tire 객체를 생성해서 의존관계가 있다. OCP를 지키지 않았지만 클라이언트 Driver Class에서는 Car의 구조가 복잡한지 모른다. 단순히 CarFacade 의 객체를 생성하면 된다.

    즉 클라이언트 Driver는 Car의 구조의 복잡도를 모르고 단순하게 CarFacade 하나만 객체 생성을 통해 Car를 생성할 수 있다. 이것이 파사드 패턴이다.(Facade Pattern)

     

    파서드 패턴은 OCP 원칙을 지키지 않았지만 디자인 패턴 공부는 코드를 어떤식으로 쓰는지 구조를 파악하는 것이여서 나중에 상황에 맞게 잘 사용할 수 있으면 된다.

     

    참고 : 스프링 입문을 위한 자바 객체 지향의 원리와 이해 / 김종민 / 위키북스

    댓글

    Designed by JB FACTORY