POJO에 대한 읽을거리[펌]

자바답게! 더 슬림하게! 다이어트 POJO 프로그래밍

지난 몇 년간 EJB3를 포함한 많은 자바의 새로운 기술과 제품들은 저마다 POJO 프로그래밍의 지원을 주요 장점으로 내세우며 등장했다. 그러나 POJO 프로그래밍이 과연 무엇이고 어떤 이득을 안겨줄 수 있는지에 대해 명확히 설명하는 것은 사실 쉽지 않다. 글에서는 POJO 프로그래밍이 왜 중요한지 살펴보고 그것을 통해 기존의 무겁고 지저분한 코드를 어떻게 깔끔하고 군살 없는 코드로 만들 수 있는지를 알아보자.

 

이일민 tobyilee@gmail.com|오픈소스 기술을 엔터프라이즈 시스템에 적용할 수 있는 전략과 기술연구에 많은 관심을 가지고 있다. Spring Framework와 Hibernate 등의 오픈소스 프레임워크의 교육, 컨설팅, 기술지원을 제공하는 Epril의 대표 컨설턴트로 활동하고 있다. 한국스프링사용자모임(KSUG)의 기술고문으로 활동하며 토비의 이프릴(toby.epril.com)이라는 스프링 관련 기술정보를 제공하는 블로그를 운영하고 있다.

 

POJO는 ‘Plain Old Java Object’의 약자이다. 이를 번역하면 ‘평범한 구식 자바 오브젝트(객체)’라고 할 수 있겠다. 도대체 평범하고 구식인 자바 오브젝트가 뭐가 다르고 특별해서 POJO라고 부르는 것일까? 그럼 평범하지 않은 최신의 자바 오브젝트는 또 무엇인가?

 

 

POJO란 무엇인가?

POJO를 이해하려면 POJO라는 단어가 만들어진 역사적 배경을 살펴볼 필요가 있다. POJO는 마틴 파울러가 2000년 가을에 열렸던 어느 컨퍼런스의 발표를 준비하면서 처음 만들어낸 말이다. 마틴 파울러는 EJB(Enterprise JavaBean)보다는 단순한 자바 오브젝트에 도메인 로직을 넣어 사용하는 것이 여러 가지 장점이 있는데도 왜 사람들이 EJB가 아닌 ‘평범한 자바 오브젝트’를 사용하기를 꺼려하는지에 대해 의문을 가졌다. 그리고 그는 단순한 오브젝트에는 EJB와 같은 그럴듯한 이름이 없어서 그 사용을 주저하는 것이라고 결론 내렸다. 그래서 만든 단어가 POJO라는 용어인 것이다. POJO 기반의 기술을 사용한다고 말하면 왠지 첨단 기술을 사용하는 앞선 개발자인 듯한 인상을 주기 때문이다.
이 POJO라는 용어를 만들어낸 전략은 매우 성공적이었다. EJB의 문제점과 한계를 느낀 많은 개발자들은 새로운 기술처럼 보이는 POJO 프로그래밍이라는 것에 관심을 가지게 되었고, POJO 기반의 프로그래밍 기술이 EJB의 강력한 대안으로 등장하기 시작했다. POJO 기반의 프레임워크, POJO 애플리케이션을 위한 플랫폼 등이 점점 인기를 끌게 되었고, 결국 POJO가 배제하려고 했던 EJB는 POJO 기반의 기술에 밀려 이제 레거시 기술로 사라질 위기에 처했다. 그렇다면 단지 EJB를 사용하지 않으면 모두 POJO라고 할 수 있을까? 그렇지는 않다. POJO 프로그래밍이라는 개념은 단지 ‘EJB가 아닌 자바’ 이상의 특징을 가지고 있는 프로그래밍 모델이다. POJO 기반의 개발은 생각보다 단순하지 않다.
POJO를 좀더 이해하려면 EJB의 장단점을 함께 이해해야 한다. 그것은 POJO 프로그래밍이 다시 EJB 시대 이전으로 돌아가자는 것이 아니고, EJB를 넘어 그보다 더 앞으로 나아가자는 것이기 때문이다.


EJB와 엔터프라이즈 서비스

자바에서 EJB 기술의 등장은 필연적인 것이었다. 기업의 IT 시스템은 점점 그 중요성이 증대되고 그에 따라 점점 복잡한 기술이 요구되었으며 자바의 기초적인 JDK만으로는 그것을 충족시킬 수 없었다. 서버 기반의 자바 기술인 J2EE(Java2 Enter pise Edition)가 등장했지만 Servlet, JSP 레벨의 최소한의 서버 프로그래밍 인터페이스만 가지고는 복잡한 엔터프라이즈 애플리케이션을 제작하는 데 부담이 적지 않았다.
엔터프라이즈 시스템의 복잡도는 두 가지 다른 영역에서 증대되었다. 하나는 기업 업무처리의 IT 시스템에 대한 의존도가 높아지면서 시스템이 다뤄야 하는 비즈니스 로직 자체가 점차로 복잡해진 것이다. 또 다른 하나는 많은 사용자의 처리요구를 빠르고 안정적이면서 확장 가능한 형태로 유지하기 위해 필요한 로우레벨의 기술적인 처리 요구들이다. 단순히 DB와 연동하는 수준의 C/S 기반의 스탠드얼론(standalone) 애플리케이션과는 달리 서버에서 동작하는, 그것도 웹 기반으로 많은 처리 요구를 받는 시스템에는 감당해야 중요한 기술적인 요구사항들이 많다. 대표적으로 트랜잭션 처리, 상태 관리, 멀티스레딩, 리소스 풀링, 보안 등이 있다.
애플리케이션 로직의 복잡도와 상세 기술의 복잡함을 개발자들이 한 번에 다룬다는 것은 쉬운 일이 아니었다. 한 개발자가 보험업무와 관련된 계산 로직을 자바로 어떻게 구현해야 하는지에 집중하면서 동시에 시스템 레벨에서 멀티 DB로 확장 가능한 트랜잭션 처리와 보안 기능을 멀티스레드 세이프하게 만드는 것에 신경 써야 한다면 여간 부담되는 게 아닐 것이다.
EJB는 이런 문제를 다루기 위해 등장했다. EJB 1.0의 스펙이 제시한 EJB의 비전은 ‘EJB는 애플리케이션 개발을 쉽게 만들어 준다. 애플리케이션 개발자는 로우레벨의 기술들에 관심을 가질 필요도 없다’였다. 애플리케이션 개발자들은 다뤄야 하는 해당 도메인과 비즈니스 로직에만 집중하면 된다는 것이었다. 게다가 EJB는 독립적으로 개발한 컴포넌트들을 서버에 자유롭게 배포하고 서로 연동해 사용하게 하는 컴포넌트 기반의 개발 모델을 제시할 뿐더러, 여러 대의 서버에 분산되어 있는 모듈간의 리모팅 처리도 개발자들이 거의 신경 쓰지 않고 개발할 수 있게 했다. 더 나아가 벤더별로 제각각 발전시켜 혼란에 빠지기 쉬운 자바의 서버 기술을 일관성 있게 구현하게 지원하므로 특정 서버에 종속되지 않고 서버간의 이동성(portability)을 보장해 준다고 약속했다.
과연 EJB는 스펙을 통해 제시한 그런 환상적인 기대를 충족시켰는가? 이제는 누구나 다 잘 알고 있듯이 현실에서는 그렇지 못했다. EJB는 불필요할 만큼 과도한 엔지니어링으로 실패한 대표적인 케이스였다. EJB에서는 현실에서 1% 미만의 애플리케이션에서만 필요한 멀티 DB를 위한 분산 트랜잭션을 위해 나머지 99%의 애플리케이션도 무거운 JTA 기반의 글로벌 트랜잭션 관리 기능을 사용해야 했다. EJB의 혜택을 얻기 위해 모든 기능이 다 필요하지도 않은 고가의 WAS(CPU 당 몇 백에서 몇 천만 원씩 하는)를 구입해야 했고, 고급 IDE의 도움 없이는 손쉽게 다룰 수 없는 복잡한 설정 파일 속에서 허우적대야 했다. EJB 컴포넌트는 컨테이너 밖에서는 정상적으로 동작할 수 없으므로 개발자들은 끝도 없이 반복되는 수정-빌드-배포-테스트의 지루한 과정으로 많은 시간을 낭비해야 했고, 간단한 기능에 대해서조차 자동화된 테스트를 만드는 것은 거의 불가능에 가까웠다. 테스트는 서버에 배치 후에 대부분 수동으로 해야 했고, 느린 배포 작업 탓에 그나마도 자주 반복되기 힘들게 만들었다. 특별한 경우가 아니라면 그다지 장점이 없는 EJB의 원격분산 모델은 성능을 떨어뜨리고 서버의 복잡도만 증가시켰다. 가장 최악의 문제점은 EJB 스펙을 따르는 비즈니스 오브젝트들은 객체지향적인 특징과 장점을 포기해야 했다는 것이다. EJB빈은 상속과 다형성 등의 혜택을 제대로 누릴 수 없었다. 간단한 기능 하나를 위해서도 많은 인터페이스와 EJB 의존적인 상속 등을 사용해야 했다. EJB 옹호자들 사이에서조차 외면 받아온 엔티티빈에 대해 말하는 것은 시간 낭비일 것이다. 상태 있는 세션빈(SFSL)도 기술적인 한계와 비효율성으로 인해 현장에서는 많이 사용되고 있지 않다. 그나마 오랫동안 사용된 EJB는 상태 없는 세션빈(SLSF) 뿐이다.
그럼에도 EJB가 계속 사용되었던 이유는 무엇일까? 그것은 EJB가 스펙에서 제시한 그 몇 가지 장점 때문이었다. 엔터프라이즈 애플리케이션에서 반드시 필요로 하는 주요한 엔터프라이즈 서비스들을 애플리케이션 코드와 분리해서 독립적인 서비스로 사용할 수 있게 만들어줬다는 점이다. 비록 불완전하고 불필요한 복잡도가 남아있긴 했지만 선언적인 트랜잭션 관리(Declarative Transaction Management)나 역할 기반의 보안(Role based Security) 기능들을 제공했다. 비즈니스 오브젝트를 배포하고 관리하는 컨테이너를 제공하고 기본적인 스레드 관리, 인스턴스/리소스 풀링을 제공하는 등의 것들이다. 한편으로는 ‘개발자들이 로우레벨의 기술적인 문제에 신경 쓰지 않고 비즈니스 로직에 충실히 개발하게 함으로써 애플리케이션 개발을 손쉽게 만들어 준다’는 처음 약속을 어느 정도 지켰다고 볼 있다. 하지만 EJB의 문제는 앞서 지적한 것처럼 한편으로는 애플리케이션 개발의 복잡도를 제거하면서 다른 한편으로는 더 많은 문제와 복잡성을 가지고 왔다는 것이다.
결국 EJB는 형편없는 생산성과 느린 성능, 불필요한 기술적인 복잡도, 벤더 사이의 알력으로 과도하게 높아진 스펙 등으로 인해 자바의 엔터프라이즈 개발에 대한 불신을 가중시켰다. 마침내 마틴 파울러를 비롯한 많은 오피니언 리더들은 EJB와 같은 잘못 설계된 과도한 기술을 피하고, 객체지향 원리에 따라 만들어진 자바 언어의 기본에 충실하게 비즈니스 로직을 구현하는 일명 POJO 방식으로 돌아서야 한다고 지적하고 나섰다. POJO 방식의 개발은 EJB가 잃어버린 소중한 가치인 객체지향적인 설계와 자동화된 테스트의 편의성, 개발생산성 등을 회복시켜 줄 수 있는 길이기 때문이다.

 

 

POJO 프레임워크

앞에서 강조한 것처럼 EJB를 사용하지 말고 POJO를 쓰자는 것이 EJB 이전의 방식으로 돌아가는 것을 의미한다면 다른 문제가 발생할 수밖에 없다. 여전히 복잡한 로우레벨의 API를 이용해 코드를 작성해야 하고, 많은 기술적인 문제를 애플리케이션 코드에 그대로 노출시켜 개발해야 한다면 기껏 POJO로의 복귀 덕분에 얻은 많은 장점들을 놓칠 수밖에 없다.
그래서 등장한 것이 바로 POJO 기반의 프레임워크이다. POJO 프레임워크는 POJO를 이용한 애플리케이션 개발이 가진 특징과 장점을 그대로 살리면서 EJB에서 제공하는 엔터프라이즈 서비스와 기술을 그대로 사용할 수 있도록 도와주는 프레임워크이다. 나아가 이는 기존의 EJB에서보다 훨씬 더 세련되고 나은 방법이다. 많은 POJO 프레임워크가 있지만 그 중에서 가장 대표적인 것을 꼽으라면 하이버네이트와 스프링을 들 수 있다.
하이버네이트는 EJB의 엔티티빈이 제시했던 컨테이너가 관리하는 퍼시스턴스 기술(Container Managed Persistence)과 오브젝트-관계형 DB 매핑(Object Relational Mapping) 기술을 순수한 POJO를 이용해 사용할 수 있게 하는 POJO 기반의 퍼시스턴스 프레임워크이다. 하이버네이트는 사실 엔티티빈이 제공했던 것과는 비교할 수 없을 만큼 더 편리하고 세련된 방식으로 퍼시스턴스 기능을 제공한다. 굳이 컨테이너 위에서 동작시킬 필요도 없고, 이상적인 매핑을 위해 포기했던 많은 기능과 성능을 객체지향 DB에 적합하게 최적화한 기능을 통해 JDBC API를 직접 사용해 개발하는 못지않은 성능과 복잡한 퍼시스턴스 로직을 개발 가능하게 해줬다. 가장 중요한 점은 하이버네이트가 사용하는 POJO 엔티티들은 EJB의 엔티티빈과 달리 객체지향적인 다양한 설계와 구현이 가능하다는 점이다. 엔티티의 상속, 다형성, 밸류 오브젝트, 사용자정의 타입 등을 어떠한 기술적인 손해 없이도 그대로 퍼시스턴스 매핑용 오브젝트로 사용할 수 있게 한다.
스프링은 세션빈이 제공하던 중요한 엔터프라이즈 서비스들을 POJO 기반으로 만든 비즈니스 오브젝트에서 사용할 수 있게 한다. 대표적인 것이 선언적인 트랜잭션 서비스와 보안이다. 또한 EJB와 마찬가지로 오브젝트 컨테이너를 제공해서 인스턴스의 라이프사이클을 관리하고 필요에 따라 스레딩, 풀링 및 서비스 인젝션 등의 기능을 제공한다. 또한 OOP를 OOP답게 사용할 수 있게 하는 AOP 기술을 적용해서 POJO 개발을 쉽게 만든다.


POJO vs. 짝퉁 POJO

POJO 프로그래밍의 진정한 가치는 자바의 객체지향적인 특징을 살려 비즈니스 로직에 충실한 개발이 가능하도록 하는 것이다. 그러면서 복잡한 요구조건을 가진 엔터프라이즈 개발의 필요조건을 충족시킬 있도록 POJO 기반의 프레임워크를 적절히 사용하는 것이 요구된다. 문제는 단지 POJO 프레임워크로 잘 알려진 제품을 사용하기만 하면 자동으로 POJO 개발을 하고 있다고 생각하는 경우가 많다는 것이다.
스프링과 하이버네이트가 한창 인기를 끌기 시작할 때 EJB 기반으로 구현된 애플리케이션에 스프링과 하이버네이트를 적용했다고 해서 주목받던 어느 오픈소스 제품이 있었다. POJO 기반 서버 애플리케이션의 좋은 샘플이라고 생각한 필자와 동료들은 그 소스를 가져다가 한번 살펴봤는데, 정작 그 소스를 보고는 놀라지 않을 수 없었다. EJB를 제거하고 POJO 기반의 프레임워크를 적용했다고는 하지만 사라진 것은 단지 EJB 관련 클래스와 인터페이스 정도이고 그 외에 EJB로 개발할 때 가졌던 모든 코드와 스타일을 그대로 가져다가 억지로 POJO 프레임워크에 구겨 넣은 것을 발견했기 때문이다. 그들이 말하는 POJO 프레임워크 기반의 개발이란 단지 POJO 프레임워크 위에서 동작하기만 하면 그 오브젝트의 설계와 구현은 어찌되든 상관없는 형태였다. 이는 자바가 처음 등장했을 때 자바로 개발한다고 하면서, 실제로는 C에서 쓰던 절차적인 방식(객체지향적인 모든 특성 무시)으로 구현했던 경우와 다를 게 없었다.
필자는 그런 POJO 애플리케이션을 짝퉁 POJO라고 부르고 싶다. 결과적으로 그런 개발은 POJO 프로그래밍이 주는 장점을 제대로 살릴 수 없고 오히려 배제되어야 또 다른 복잡함을 낳기 때문이다. <리스트 1>은 최근에 어느 프로젝트에서 스프링을 이용해 개발했다고 소개한 코드를 간략하게 옮겨놓은 것이다.

<리스트 1> 잘못된 POJO 기반 코드의 예

class MyService {
   private MyDAO myDAO;
   private XXDao xxDAO;
   …
     public MyVo foo(UrVO urVo) throws MyException
    {
        Connection con = null;
        try {
            con = DBUtil.getConnection();
       con.setAutoCommit(false);

       if (xxDAO.check(urVo))
            return myDAO.foo(con, urVo);
       
      else
        return myDAO.boo(con, urVo);
      }
 catch (MyException e) {
   con.rollback();
     throw e;
  }
   catch (Exception e) {
     throw new MyException(e, "XXX00001");
      con.rollback();
         }
          finally {
           con.commit();
             DBUtil.close(con);
         }
     }

}


class MyDAO {
public void foo(Connection con, UrVO urVO) throws MyException {

           PreparedStatement pstmt = null;
           try {
              pstmt = new LoggablePreparedStatement(con, QueryFactory.getInstance().getQuery("FOO"));
          pstmt.setInt(1, urVO.getSeq());
          if (pstmt.executeUpdate() != 1) {
                throw new MyException("SVM00009");
}
} catch (MyException e) {
       throw kble;
} catch (Exception e) {
       throw new MyException(e);
} finally {
       DBUtil.close(pstmt);
}
}

}

코드는 물론 스프링의 IoC/DI 컨테이너에 올라가서 빈(bean)으로 등록되어 Setter 인젝션 방식으로 사용되기는 할 것이다. 그럼 과연 이 코드가 POJO라고 말할 수 있을까? 물론 EJB를 사용하지 않았고 특정 환경에 종속적인 것처럼 보이지도 않는다. 또한 POJO 프레임워크의 대표격인 스프링 프레임워크를 사용해 만들어졌다. 하지만 이 코드는 POJO 프로그래밍의 장점을 거의 살리지 못한 레거시 코드일 뿐이다. 필자는 POJO 기반의 코드인지 아닌지를 확인하기 위해 다음의 중요한 두 가지 기준을 적용한다.
첫째는 객체지향적인 설계원칙에 충실하도록 개발되어 있는지 여부이다. POJO의 자바 오브젝트라는 것은 단지 자바 언어 문법을 지켜 만들었다는 뜻이 아니다. 객체지향 언어로서의 자바 오브젝트의 특징을 가지고 있는지가 중요하다. POJO 코드가 객체지향적인 원리에 따라 설계되어 있지 않고 더 나아가 적절하게 리팩토링하는 것도 어려운 구조라면 POJO로 개발했다는 것이 주는 가치는 거의 없다고 본다. EJB를 사용하지 않으므로 오히려 코드가 <리스트 1> 같은 식으로 바뀌었다면 그것은 POJO 개발이라고 볼 수 없다. 끊임없이 반복적으로 등장하는 템플릿 코드와 테스트하기 힘든 구조, 확장이나 재활용의 어려움 등이 코드에 그대로 남아있다면 EJB의 문제점을 여전히 안고 있는 것이다.
둘째는 테스트 코드 개발의 용이성이나 테스트 코드를 잘 작성했는지의 여부이다. EJB를 버리고 POJO 기반으로 개발한다고 하면서도 여전히 수정-빌드-배포-테스트라는 방식을 탈피하지 못하고 있다면 EJB로 개발했던 시절과 대체 무엇이 다를까? 잘 만들어진 POJO 애플리케이션은 자동화된 테스트 코드 작성이 편리하다. 코드 작성이 편리하면 좀더 자주 꼼꼼하게 만들게 되고 반복적으로 실행할 수 있으므로 코드 검증과 품질 향상에 유리하다. 또한 만들어진 테스트 코드베이스가 있다면 리팩토링할 여유가 생겨 POJO 코드를 더 나은 설계구조로 변경할 가능성도 높아진다.

 

POJO로의 변환

<리스트 1>의 코드를 스프링의 POJO 프로그래밍 모델에 따라 수정해 보자. DAO 코드의 가장 큰 문제점은JDBC의 오래된 코드 스타일을 그대로 사용하고 있다는 것이다. 따라서 try/catch/ finally의 전형적인 템플릿코드가 실제 DAO 로직보다 더 많은라인을 차지하고 있다. 또 Query를 불러오는 부분이 싱글톤을 사용하고 있다. 그나마 JDBC 코드를 간략하게 작성하도록 돕는유틸리티 클래스를 사용했지만 정적 메소드를 이용했다. 싱글톤과 정적메소드는 테스트 코드를 만드는 데 가장 큰 장애물이다.테스트를 쉽게 하기 위해 모의객체(mock object)로 변환하는 것이 불가능하기 때문이다. 객체지향적인 설계방식을 따른다면Query를 가져오는 기능을 인터페이스로 만들어 사용하게 하고 이를 구현한 오브젝트를 DAO에서 참조할 수 있도록 하는 것이바람직하다. 또한 템플릿 스타일의 과도한 코드를 제거하고 반복적인 JDBC 워크플로우를 제거하려면 콜백 방식으로 구현하는 게좋다. 그러면 DAO의 데이터액세스 로직과 JDBC 처리 워크플로우를 구분할 수 있다. <리스트 2>는 이렇게스프링을 이용한 POJO 스타일로 수정된 코드이다.

<리스트 2> 수정된 MyDAO

public class MyDAO {
              private DataSource dataSource;
              private SimpleJdbcTemplate jdbcTemplate;
              private QueryFactory queryFactory;
              // setter methods
              public void foo(UrlVo urlVo) {

jdbcTemplate.update(queryFactory.getQuery("FOO"), urlVo.getSeq());
}
}

MyDAO는 DataSource, QuereyFactory라는 협력객체를 인터페이스를 통해 액세스하므로 해당 오브젝트의 구현에 상관없이 동작할 수 있다. DataSource가 개발용에서 테스트용으로 또 실제 운영서버로 바뀐다고 하더라도 DAO는 그 부분에 신경 쓰지 않고 클래스의 목적인 데이터로직을 구현하는 데만 충실하게 만들어져 동작할 수 있게 되었다. 테스트를 위해 실제 DB가 아닌 Fake DB나 Embedded DB를 적용한다면 DB 설정을 돌려주는 적절한 DataSource를 MyDAO가 사용하도록 스프링을 통해 설정하면 그만이다.
또 JdbcTemplate이라는 JDBC 워크플로우를 제공하는 템플릿 기반의 Helper 클래스를 사용해서 반복적으로 등장하는 JDBC의 try/catch/finally 작업을 DAO에서 제외하고 순수하게 데이터 처리와 관련된 핵심 로직만 사용하도록 정리했다.
결과적으로 코드는 깔끔해지고 여러 클래스에 거쳐 나타나는 지저분한 중복은 제거되었다. 또한 DAO 오브젝트의 역할을 넘어선 것들은 의존하는 오브젝트로 분리하고 그 구현에 종속되지 않도록 인터페이스를 사용하게 했으므로 객체지향적인 설계에 충실한 오브젝트의 설계와 구현에 가깝도록 만들어졌다.
이번에는 MyService를 살펴보자. MyService의 가장 큰 문제는 무엇인가? 그것은 MyService는 비즈니스 로직을 구현한 클래스임에도 불구하고, 실제로 코드 안에는 그와 상관없는 데이터액세스와 관련된 코드들이 덕지덕지 붙어 있다. MyService에 나타난 커넥션과 트랜잭션 처리 코드들은 어찌 보면 어쩔 수 없는 선택으로 생각할 수도 있다. DAO를 분리해 놓은 상태로, MyService에서 여러 개의 DAO를 호출해 처리하는 결과를 하나의 커넥션과 트랜잭션으로 묶으려면 MyService 레벨에서 처리해야 하기 때문이다. 그렇지 않고 DAO 메소드 단위로 커넥션과 트랜잭션을 다루면 심각한 결과를 가져온다. 하지만 결과적으로 MyService는 DB 처리 코드와 로직 코드가 짬뽕되어 있는 지저분한 코드가 되었고, DB 관련 코드와 구현에 종속이 되었으므로 MyService는 객체지향적인 장점을 살려 재활용되거나 발전하는 데 극히 제한을 받게 된다. 만일 트랜잭션 처리가 JDBC 방식에서 JTA로 바뀌었다면? MyService처럼 구현한 모든 서비스/비즈니스 로직 코드를 일일이 수정해야 하는 중노동에 시달려야 한다. 하나의 클래스에 여러 가지 레이어의 기술이 짬뽕되고 책임이 중복되어 나타나며 한 가지 기능을 수정하기 위해 수많은 클래스를 수정해야 한다면 이것이 과연 POJO를 지지하는 사람들이 기대했던 바로 그 POJO 프로그래밍의 장점이라고 할 수 있을까?

<리스트 3> 수정된 MyService

class MyService {
     private MyDAO myDAO;
     private XxDAO xxDAO;
// setter methods
public void foo(UrVo urVo) {
    if (xxDAO.check(urVo))
       return myDAO.foo(con, urVo);
else
      return myDAO.boo(con, urVo);
 }

}


MyService를 순수한 비즈니스 로직에 충실한 POJO 기반으로 변경하려면 스프링과 같은 POJO 프레임워크의 도움이 절실히 필요하다. 겉으로 드러나는 DB 커넥션, 예외처리, 트랜잭션과 관련된 부분을 비즈니스 로직을 구현한 MyService에서 분리하기 위해서는 AOP 기술의 도움이 필요하기 때문이다. <리스트 3>은 스프링의 AOP로 선언적 트랜잭션 관리 기능을 적용해 변경한 MyService 코드이다.
<리스트 1>에서 보았던 MyService의 모든 지저분한 DB와 트랜잭션 처리 블록이 모두 제거되었다. 남은 것은 MyService의 비즈니스 로직에 충실한 코드들뿐이다. 필요에 따라 적절한 DAO를 호출해서 퍼시스턴스 관련된 기능을 DAO를 통해 처리하는 것만 있다. 이는 객체지향적인 설계 모델에 충실하게 만들어져 있다.
한 가지 남은 문제는 테스트 유용성인데, MyService의 메소드를 테스트하기 위해서는 MyDAO, XxDAO가 필요하다. DAO는 DB를 역시 필요로 하므로 MyService를 테스트하기 위해 그 메소드를 호출하면 2개의 DAO 코드와 DB까지 동작이 필요하기 때문이다. DB까지 연동하는 테스트는 그 데이터 준비도 만만치 않거니와 시간도 많이 걸린다. 따라서 테스트를 만들고 자주 수행하는 데 어려움을 준다.
이를 위해 MyDAO를 다시 생각해 볼 필요가 있다. MySer vice 입장에서 MyDAO는 오브젝트를 장기간 보존하고 그것을 다시 조회하도록 돕는 퍼시스턴스 기능의 필요에 따라 사용하는 대상이다. 그 구현이 어떻게 되는지는 중요하지 않을 뿐더러 퍼시스턴스 구현이 필요하면 바뀔 수도 있다고 가정하면 지금과 같은 MyDAO의 클래스를 직접 액세스하는 구조는 바람직하지 않다. 전략패턴(Strategy Pattern)의 개념을 적용해서 MyDAO를 인터페이스와 구현으로 분리하고 MyService는 MyDAO 인터페이스를 사용하도록 수정하면 이 문제를 모두 해결할 수 있다. 먼저 MyDAO 인터페이스를 정의한다.


interface My DAO {
void foo(UrVo);
}

그리고 이를 구현하는 클래스를 만든다.

class MyJdbcDAO implements MyDAO {

}

이제 MyService는 MyDAO라는 인터페이스에 대해 프로그래밍하는 구조로 바뀌었다. 이것의 장점은 MyDAO의 구현이 언제든지 교체 가능하다는 것이다. 구현한 알고리즘을 통째로 교환해 사용할 수 있게 하는 것이 전략패턴의 특징이다. DAO 구현이 필요에 따라 JDBC가 아닌 하이버네이트나 다른 종류의 퍼시스턴스여야 한다면 언제든지 MyDAO 인터페이스를 구현해 만들면 된다. MyService는 구현이 바뀌는 것에 전혀 영향 받지 않는다.
MyService의 로직이 복잡해 구현하면서 자주 테스트가 필요하다면 MyDAO 인터페이스를 구현한 간단한 테스트용 DAO 클래스를 만들어 MyService가 그것을 사용하도록 만든다. 이는 MyService와 MyDAO의 관계를 느슨하게 만들어 주었기 때문에 모두 가능한 것이다. 여기서 나온 MyService와 MyDAO는 아직 개선의 여지가 남아 있다. 하지만 일단 여기까지만 살펴보자.
여기서 사용한 기법들은 모두 객체지향의 기본 설계원리와 패턴에 등장하는 것들이지 어떤 최신 기법들이 아니다. 물론 AOP라는 새로운 기술을 사용하지만, 그것이 추구하는 것은 또 다른 프로그래밍 모델이 아닌 OOP에 충실한 자바 코드를 만드는 데 도움을 주는 것일 뿐이다.
앞에서 살펴본 것처럼 POJO 기반의 프레임워크를 가져다 사용하기만 했다고 해서 POJO 프로그래밍이 되고 그 유익을 누리는 것은 결코 아니다. POJO가 지향하는 자바의 객체지향적인 설계의 기본에 충실하도록 POJO 프레임워크의 도움을 받아 구현하는 것이 진정한 POJO 프로그래밍이다. 결과적으로 이렇게 만들어진 POJO 기반의 코드는 EJB나 그 이전의 자바개발 방법을 따라 만든 어떤 코드보다 더 간결하고 깔끔해진다. 이것이 객체지향 언어인 자바를 사용하면서 누려야 하는 진정한 혜택이 아닐까.

 

풍성한 도메인 모델

이제는 POJO 개발의 조금 다른 영역을 생각해 보자. POJO의 자바 오브젝트가 지닌 기본 특징은 하나의 오브젝트 안에 상태(State)와 행위(Behavior)를 모두 가지고 있는 것이다. 쉽게 말해 인스턴스 변수와 로직을 가진 메소드를 가지고 있다는 의미이다. 문제는 자바의 그런 특성이 EJB에 와서 이상한 오브젝트 형태로 왜곡된 것이다. 빈약한 오브젝트(anemic object)라고 불리는 것이 바로 그것으로, 로직이 없고 상태만 가진 오브젝트들이 다수 사용되었다. 그러면 그 로직에 해당하는 행위는 어디로 갔을까? 그것은 절차적인(procedural) 스타일로 작성된 서비스 레이어의 메소드로 들어갔다. 결과적으로 서비스 오브젝트는 과도한 로직이 트랜잭션 스크립트 형태로 반복되어 길게 등장하면서 갈수록 비대해졌고, 도메인 모델을 구현한 오브젝트는 빈약한 오브젝트로 오로지 상태 인스턴스 변수만 가진 전혀 객체지향 언어 답지 않은 결과만 남게 되었다.
이러한 스타일은 EJB뿐만 아니라 그 이후에 등장한 POJO 기반의 프로그래밍에서도 쉽게 찾아볼 수 있다. <리스트 4>와 <리스트 5>는 각각 은행의 계좌를 이체하는 서비스의 메소드와 계좌를 구현한 도메인 모델이다.

<리스트 4> 계좌이체 서비스 클래스

public class MoneyTransferServiceProceduralImpl implements MoneyTransferService {
     private AccountDAO accountDAO;
     private BankingTransactionDAO bankingTransactionDAO;

public BankingTransaction transfer(String fromAccountId, String toAccountId, double amount) {
      Account fromAccount = accountDAO.findAccount(fromAccountId);
      Account toAccount = accountDAO.findAccount(toAccountId);
              assert amount > 0;
             double newBalance = fromAccount.getBalance() - amount;
             switch (fromAccount.getOverdraftPolicy()) {
             case Account.NEVER:
               if (newBalance < 0)
                 throw new MoneyTransferException("In sufficient funds");
     break;
     case Account.ALLOWED:
       Calendar then = Calendar.getInstance();
       then.setTime(fromAccount.getDateOpened());
       Calendar now = Calendar.getInstance();
       double yearsOpened = now.get(Calendar.YEAR) - then.get(Calendar.YEAR);
         int monthsOpened = now.get(Calendar.MONTH) - then.get(Calendar.MONTH);
         if (monthsOpened < 0) {
         yearsOpened--;
         monthsOpened += 12;
      }
      yearsOpened = yearsOpened + (monthsOpened / 12.0);
      if (yearsOpened < fromAccount.getRequiredYearsOpen() ||        newBalance < fromAccount.getLimit())
throw new MoneyTransferException("Limit exceeded");
break;
default:
        throw new MoneyTransferException("Unknown overdraft type: " + fromAccount.getOverdraftPolicy());
}
     fromAccount.setBalance(newBalance);
     toAccount.setBalance(toAccount.getBalance() + amount);
     TransferTransaction txn = new TransferTransaction(fromAccount, toAccount, amount, new Date());
      bankingTransactionDAO.addTransaction(txn);
      return txn;
    }
}


 

<리스트 5> Account 도메인 클래스

public class Account {
      public static final int NEVER = 1;
      public static final int ALLOWED = 2;

      private int id;
      private double balance;
      private int overdraftPoicy;
      private String accountId;
      private Date dateOpened;
      private double requiredYearsOpen;
      private double limit;
      // getters/setters
}

 

MoneyTransferServiceProceduralImpl의 transfer 메소드는 길고 복잡하다. 자세히 살펴보면 사실 Account라는 오브젝트에 속해야 하는 로직들이 이체서비스의 서비스 로직에 빠져나와 있음을 알 수 있다. 반면에 Account 오브젝트에는 행위(메소드)는 없고 상태(필드)만 남아 있다. 만일 여기서 사용된 Account에 마땅히 들어가야 할 내용이 다른 곳에서 또 필요하다면? 그때는 복잡한 로직 코드가 여기저기 중복될 수밖에 없다.
그럼 객체지향 기술의 장점을 충분히 누릴 수 있도록 이 코드를 POJO답게 수정해 보자. 가장 간단한 방법은 Account 오브젝트가 가지고 있어야 하는 행위를 Account로 옮기는 것이다. <리스트 6>과 <리스트 7>은 각각의 코드를 수정한 것이다.

<리스트 6> 수정된 계좌이체 서비스 클래스

public class MoneyTransferServiceProceduralImpl implements MoneyTransferService {
    …
     public BankingTransaction transfer(String fromAccountId, String toAccountId, double amount) {
       Account fromAccount = accountDAO.findAccount(fromAccountId);
       Account toAccount = accountDAO.findAccount(toAccountId);
assert amount > 0;

        fromAccount.debit(amount);
        toAccount.credit(amount);

        TransferTransaction txn = new TransferTransaction(fromAccount, toAccount, amount, new Date());
         bankingTransactionDAO.addTransaction(txn);
         return txn;
}
}


 

<리스트 7> 수정된 Account 오브젝트

class Account {
      // fields
     // getters/setters
     …
public void debit(Account fromAccount, double amount) {
     double newBalance = getBalance() ? amount;
     switch (getOverdraftPolicy()) {
         ...
    }
     setBalance(newBalance);
}

public void credit(Account toAccount, double amount) {
    setBalance(getBalance() + amount);
  }
}

이체서비스 오브젝트 안의 한 메소드에 지저분하게 들어 있던 이체 로직은 그 로직이 있어야 Account 안에 절절한 메소드 형태로 옮겨졌다. 서비스 메소드에서는 그 오브젝트의 메소드를 호출해 서비스 로직을 수행하는 간단하고 명확한 코드로 역시 변경되었다. 이제 Account 오브젝트는 여러 로직과 계층에서 자신의 로직을 분산해 복사할 것 없이, 오브젝트 그대로 재활용되어 사용되는 객체지향의 혜택을 누릴 것이다. 또한 POJO이므로 Account와 MoneyTransferServiceProceduralImpl 모두 쉽게 테스트할 수 있다. 이렇게 객체지향 원리에 충실하게 도메인 모델을 만드는 것을 풍성한 도메인 모델(Rich Domain Model)이라고 한다.

 

진정한 POJO 프로그래밍 추구하기

이 글의 주제는 다이어트 POJO 프로그래밍이다. 필자처럼 체중이 많이 나가는 사람에게 다이어트는 매우 중요한 도전이자 과제이다. 다이어트가 왜 중요한가? 단지 체중을 줄여 날씬한 몸매를 만드는 것이 목적일까?
그렇지 않다. 다이어트가 중요한 이유는 그것이 건강을 추구하기 때문이다. 다이어트를 통해 궁극적으로 얻고자 하는 것은 건강이다. 마찬가지로 POJO를 잘 사용하면 군더더기 없는 최소한의 코드로 이전과 동일한 결과를 낼 수 있지만, 더 중요한 의미는 더 건강한 코드를 만드는 데 있다.
단지 어느 순간에 상태가 좋다고 해서 건강하다고 할 수는 없다. 건강이란 외부의 스트레스와 변화에도 흔들리지 않고 견고하게 버틸 수 있는 힘인데, 이렇게 건강한 코드를 만들기 위해서는 반드시 자동화된 테스트 코드를 개발해야 한다. 잘 만들어진 테스트 코드는 지속적인 변화에 유연하게 대응할 수 있도록 도와준다. 변화를 두려워하지 않게 만들고 코드의 구조를 개선하기 위한 리팩토링도 안심하고 진행할 수 있게 지원한다. 또한 POJO로 설계된 코드는 테스트 코드를 쉽게 만들 수 있도록 해준다. 이런 선순환이 지속되면 POJO 프로그래밍은 더 성숙된 결과로 나아가게 될 것이다. 그래서 필자는 POJO 프로그래밍의 꽃은 테스트 코드 작성이라고 생각한다.
2005년에 프랑스의 온라인 세무시스템은 큰 변화를 겪었다. 기존에 EJB와 J2EE 기반으로 구현된 기존 시스템을 스프링과 하이버네이트를 사용하는 POJO 방식으로 전환한 것이다. 단 3개월 만에 시스템의 구조를 변환하면서 동시에 50개의 새로운 유즈케이스와 100개의 새로운 화면, 150개의 기존 화면을 전환시켰다.
그 결과는 매우 성공적이었다. 손쉽게 작성할 수 있는 테스트와 간단한 패키징, 배포작업이 용이해졌고 지저분한 템플릿 코드를 제거하면서 생산성이 극대화되었다. 또한 기술적인 리스크가 줄고 일관성 유지가 쉬워졌으며 풍성한 도메인 모델을 적용하고 모든 레이어에 걸처 테스트를 작성하게 되어 큰 폭의 품질 향상이 이뤄졌다.
EJB나 무거운 기존 기술의 굴레에서 벗어나 객체지향 기술의 혜택을 가득 누릴 수 있는 즐거운 POJO 프로그래밍에 한번 도전해 보자. 혹시 지금 POJO 프레임워크를 사용해 개발하고 있다면 과연 자신이 만든 코드가 POJO의 원칙에 맞게 작성되었는지 살펴보자. 그리고 마지막에는 반드시 테스트를 작성하라. 그것이 POJO를 POJO답게 쓰게 하고 그 가치를 누리도록 도와줄 것이다.


출처 : http://www.hyojin.net/4

by skyforce | 2009/12/02 16:36 | [IT] 개발이야기 | 트랙백 | 덧글(0)

OOP 의 원칙들 (Object-Orientied Programming Principles)

<<Open-Closed Principle(OCP)를 검색하다가 발견>>


OO 언어가 절차(Procedual) 언어와 다른 이유는 모듈성(Modularity)을 전면으로 내세웠느냐 아니냐의 차이라고 본다.

보통은 이 모듈의 최소 단위로 클래스(Class) 를 이야기 한다. 따라서, 클래스가 얼마만큼 잘 만들어지느냐, 그리고그로 인해 클래스의 묶음체인 패키지(Package)혹은 컴포넌트(component)가 정제되어 있느냐가 OO 프로그램의질(quality)을 판가름 한다.

이 퀄러티를 평가하고 유지하는 데에 있어서 기준이 되는 원칙이 몇 개 있는데, 그걸 OO 원칙(OO Principle) 이라고 부른다.

그럼, OO 원칙에는 어떤 것들이 있는지 살펴보도록 하자.


OOP 4대 기본 원칙

(Object Oriented Programming Basic Principle)

추상화(Abstraction)

추상화는 주제 영역(Subject Domain Area)내에서 시스템에 참여 시키고자 하는 대상에 대한 관심점(apoint of concern)의 표현방법이다. 다만, 있는 그대로 풀어내는 것이 아니라, 모듈의 단위를 결정하기 위한'단어'를 정하는 것이다. 추상화 단계에서 속성(attribute)과 행위(Behavior)를 분리해 내는 방식은 추상화 혼란을일으키기 쉽기 때문에, 명백하다 생각되는 경우를 제외하고는 관계(relation)를 통해 속성과 행위를 찾아내는 방식을 선호한다.

캡슐화(Encapsulation)

추상화의 목적이 대상에 대한 관심 영역의 시야 한정(limitation of view)이라고 한다면, 캡슐화는 정보의영역의 한정(limitation of information area)이 목적이다. 정보(Information, 혹은 Data)를객체 안에 포함시키고, 그 정보에 대한 직접 접근은 허용하지 않는 대신, 필요에 따라 확인할 수 있는 접근점(인터페이스)을외부에 공개하는 방식이다. 가끔, 캡슐화와 정보은닉(Information Hiding)에 대해서 혼란을 겪을 수 있는데,캡슐화는 모듈화(modularity)의 의미가 더 강하고, 정보 은닉은 '대상에 대한 정보를 묻지(Query) 않으면 알 수없게 만든다'는 의미를 갖는다. 따라서, 잘 된 캡슐화는 정보 은닉의 내용을 포함한다.

상속(Inheritance)

상속은 추상화와 캡슐화가 이루어진 모듈에 대한 세분화된 체계(Datailed Category)이다. 따라서, 얼마만큼세분화(=상속)할 것인가는 설계자의 의지에 달려있다. 흔히 상세(Detail)와 복잡(Complextity)은 비례하기 때문에,현대 프로그래밍에서는 상속은 최소한으로 한정 할 것을 권장하고 있다.

다형성(Polymorphism)

다형성은 모듈이 갖고 있는 자아 정체성(Identity)과 그 것의 표현 방식을 의미한다. 말하자면, 학생은 모두 공부를해야 하지만, 공부하는 방식은 각자가 다 다르며, 그로 인해 각각의 학생은 구별된다. 다형성을 극대화 하기 위해 우리는 종종추상 클래스(Abstract class)나 인터페이스(Interface, Pure Virtual Function)를 이용한다.


OO 설계 원칙들 (OO Design Principles)

OO 프로그래밍의 기본 4대 원칙 이외에도 보다 나은 OO 프로그래밍을 위해서 이야기 되는 원칙들이 있다.

그 중 가장 흔히 접하는 용어중에 SRP (Single Responsibility Principle, 단일책임원칙)이라는게 있다.

SRP (Single Responsibility Principle, 단일책임원칙)

하나의 모듈, 그러니까 하나의 클래스 혹은 하나의 메소드는 하나의 일에 대해서만 책임을 질 수 있도록 잘 분리되어 있어야한다는 의미로, 객체 지향의 지향점 중 하나인 높은 응집도(High Cohesion)를 말한다. 이 법칙은, 흔히 말하는사고(logic)와 표현(Presentation)의 분리뿐 아니라, 관심도 철저하게 분리되어야 한다고 이야기 하고 있다. 따라서SRP가 잘 되어 있는 모듈은 중복(Duplication)이 적으며, 변경이 한 곳에서만 일어날 수 있게 만들어 준다. 혹시,데이터 모델링을 한 적이 있다면 정규화(Normalization)와 관련지어 생각할 수 있겠다. 피플웨어(Peopleware)로잘 알려져 있는 톰 디마코가 1979년에 쓴 'Structured Analysis and Systems Specification'에서 최초로 소개 되었다.


<구조적 분석/설계의 선구자 중 한명, Tom DeMarco>


디미터의 법칙(Demeter's Law, LoD)

최소 지식의 원칙(Principle of Least Knowledge) 이라고도 불린다. OO 프로그래밍 연구프로젝트(Demeter Project)에서 나온 법칙으로 흔히 '친한 친구들 하고만 이야기 하여라(Only talk toyour immediate friends)' 라는 이야기로 요약된다. 친구는 클래스 정도로 해석하면 되며,getA().getB().getC() 하는 식으로 '여러 객체에 물어물어 가는 식으로 정보를 찾아내는 스타일은 자제하는 것이 좋다'라는 법칙이다. 재미있는 것은 다른 법칙과 달리 디미터의 법칙은 장/단점이 생기는 법칙으로, 디미터의 법칙을 잘 따를 경우메소드(=함수)가 많이 생기게 되면서 복잡도가 증가 할 수 있다. 하지만, 전체적으로는 잘 따르는 것이 좋은 법칙이다. 현재디미터의 법칙이라 불리는 법칙은 디미터 프로젝트의 OO 프로그래밍 스타일 가이드 중 함수와 메소드(Law of Demeterfor Functions/Methods) 부분의 가이드를 말한다.


<웁슬라88 (OOPSLA 88')에서 발표된 LoD>

참고: http://www.ccs.neu.edu/home/lieber/LoD.html


<농업과 계절의 여신, 디미터>


Open/Closed Principle (OCP, 개방 폐쇄의 원칙)

"변경에는 닫혀있고, 확장에는 열려 있다" 는 법칙으로, 디자인 패턴들의 기본 원칙이 되는 원칙이다. 다형성과 인터페이스를 통해 변하는 부분과 변하지 않는 부분을 분리해 낸다.


<전략 패턴(Strategy Pattern)의 한 예>

알려진 바에 의하면 OCP는 두 종류가 있는데, 현재 우리가 사용하는 방식은, 업계에서 엉클 밥(Uncle Bob)이라 불리고 있는 Robert C. Martin이 1996년에 말한 Polymorphic Open/Closed Principle을 지칭한다.

참조 : http://www.objectmentor.com/resources/articles/ocp.pdf


<엉클 밥 아저씨>

ps. 데이터 베이스에 관심있는 사람은 오라클 자격증 OCP(Oracle Certificated Professional)와 혼동하지 않기를.:)


<< OCP 추가설명 - start>>


개방-폐쇄 원칙
휴대전화를 살 때마다 느끼는 것이지만 똑같은 기능을 하는 충전기가 (같은 회사의 제품임에도 불구하고) 저마다 다른 모양으로제작되는지 불만이었다. 의도는 뻔하게도 하나라도 더 팔아서 이윤을 높이기 위함이다. 기업에는 이익이겠지만 소비자에게는 똑같은기능의 부품을 또 사야 하는 스트레스를 유발시키고, 국가적으로도 엄청난 자원 낭비가 될 만도 하다.

얼마 후 정통부에서 표준 규격으로 24핀 잭을 발표했다. 그 정책 덕분에 이제 휴대전화만 사고 충전기는 재사용할 수 있게 됐다.따라서 휴대전화의 여러 종류에는 ‘개방하지만’ 충전기의 쓸데없는 생산은 ‘닫아두는’ 효과를 얻은 것이다. 바로 이번 호에서소개할 개방-폐쇄의 원칙을 잘 반영한 결과라고 생각한다.

<그림 2> 1×1 호출 관계

다시 우리의 필드로 돌아와서 <그림 2>는 클라이언트가 의 서비스를 실행하는 모습이다. 일반적인 경우지만 의서비스 타입이 여러 개 있을 경우 1×n의 클라이언트 대 서비스의 관계가 성립된다. 따라서 각각의 다른 서비스를 호출하는클라이언트들은 호출 코드를 각 서비스 타입에 따라 다르게 작성해야 한다. 이 때 클라이언트가 파일만을 읽는 것이 아니라 스트링버퍼로 바이트 배열로 읽는다면 실제로 그 관계는 n×m의 관계가 된다. 이렇게 읽는 대상의 타입이 확장, 변경될수록 복잡도는<그림 3>과 같이 두 배 이상 증가한다.

<그림 3> n×m 호출 관계

물론 약간의 경력자들은 이런 객체 관계를 무의식적으로 피한다. 실제 자바 설계에도 반영됐지만 보통 <그림 4>와 같은상속을 통한 다형성을 이용한다. 목적은 InputStream의 확장은 열어두고 클라이언트의 변경은 닫아두기 위함이다. 따라서클라이언트는 InputStream 자식 클래스의 실제 타입에 상관없이 InputStream을 통해 충분히 읽고 싶은 것을 읽을수 있다. 즉, 이상적인 1×n의 관계가 됐다. 확장에 대한 비용은 단지 생성 시점에 실제로 사용할 자식 클래스의 타입을선택해주면 되는 정도다.

<그림 4> 상속을 통한 다형성을 이용한 호출 관계(1×n)

이 개방-폐쇄 원칙을 잘 정의한 버틀란트 메이어(Bertrand Meyer)는 소프트웨어 구성 요소(컴포넌트, 클래스, 모듈,함수)는 확장에 대해서는 개방돼야 하지만 변경에 대해서는 폐쇄되어야 한다고 말한다. 변경을 위한 비용은 가능한 줄이고 확장을위한 비용은 가능한 극대화해야 한다는 의미다.

방법은 우선 변하는(확장되는) 것과 변하지 않는 것을 엄격히 구분해야 한다. 변하는 것은 가능한 변하기 쉽게, 변하지 않는것은(폐쇄돼야 하는 것은) 변하는 것에 영향을 받지 않게 설계하는 것이다. 다음으로 이 두 모듈이 만나는 지점에 인터페이스를정의해야 한다. 인터페이스는 변하는 것과 변하지 않는 모듈의 교차점으로 서로를 보호하는 방죽 역할을 한다.

에있어서 인터페이스는 확장의 내용이 정의되고 이 규약에 따라 확장이 구체화하는 역할을 한다. 따라서 인터페이스는 서비스 내용을추상화하는 형태로 제공되므로 인터페이스 설계에 주의가 필요하다. 또한 인터페이스는 클라이언트에 있어서 의 확장·변경에 따른 클라이언트 변경을 무색하게 하는 방패가 된다. 에서는 인터페이스 규약에 의해서만 확장·변경하기 때문이다. 따라서 안정된 계약에 의한 설계(Design by Contract)를 보장한다.

이를 통해 얻을 수 있는 효과, 즉 목적은 앞의 예처럼 객체간의 관계를 단순화해 복잡도를 줄이고, 확장·변경에 따른 충격을줄이는 데 있다. 또한 클라이언트는 InputStream의 타입을 알아야 할 시점(InputStream의 자식 클래스 생성시점)과 각 타입의 무관한 사용 시점(호출 시점)을 명확히 분리해 사용할 수 있다. 따라서 다른 InputStream이 확장될때 클라이언트에 있어서 InputStream의 자식 클래스를 생성하는 코드만 변경해 주면 된다. 물론 클라이언트의 확장에 따른코드 충격은 다른 방법으로 단순화, 자동화할 수 있다(환경 변수로 타입을 정의하거나 문자열로 동적 객체 바인딩 메커니즘을이용하는 등).

사례들
사실 이 원칙은 실세계에서도 흔히 찾아볼 수 있을 정도로 너무도 당연한 원칙이어서 소프트웨어에서 사례를 찾는데 오히려 안 보이는 곳이 없을 정도로 많이 적용된 원칙이다.

사례 1 : 컴파일러의 계층  

컴파일러 개발자들은, 특히 GCC 같이 여러 종류의 시스템에 포팅되는 소프트웨어를 만드는 사람은 고생이 많을 것이다. 시스템마다바이너리 포맷이 다르고 프로세서에 따라 명령어가 차이가 있다. 게다가 병렬 처리나 실시간, 임베디드 시스템의 경우는 제공해야하는 라이브러리도 가중된다. 여기서 가변적인 요소는 운영체제와 프로세스의 종류이다. 따라서 컴파일러 개발자는 지금 주제의관점에서 (앞의 두 요소에 의해) 변하는 모듈과 변하지 않는 모듈을 구분하고 이 두 모듈간의 인터페이스를 정의하는 미션이 중요할것이다.

컴파일러 처리 과정을 보면 이 두 가지의 Open 모듈과 Closed 모듈이 명백히 분리되어 있다. 스캐닝, 파싱이나 구문,문법, 의미 분석은 OS나 CPU에 영향을 받지 않는 프로그램 언어의 영역이다. 따라서 변경되지 않는 모듈로 보호해야 한다.하지만 각 플랫폼에 따른 기계어로 바이너리 코드를 만드는 모듈은 포팅되는 시스템마다 확장되어야 하는 모듈이다. 즉 앞에 열거한작업은 시스템에 의존성이 없기 때문에 시스템이 변경된다 하더라도 변경해줄 필요가 없다.

하지만 기계어 코드를 만드는 과정에서 최적화 제네레이팅하는 작업은 시스템에 의존적이기 때문에 시스템별로 확장을 요하는 모듈이다. AIX, ,솔라리스 등의 버전이 필요하게 된다. 따라서 이 모듈은 확장에 적합한 구조로 개방해야 할 것이다. 여기서 중간표현(intermediate representation) 데이터는 이 두 모듈간의 인터페이스 역할을 한다. 함수 형태의인터페이스도 있지만 프로토콜과 같이 메시지(데이터 포맷) 형태의 인터페이스도 가능하다.



<그림 5> 컴파일러의 OCP 계층

GCC 처럼 여러 시스템을 지원하는 컴파일러의 경우와 같이 POSIX(Portable Operating SystemInterface)는 유닉스 기반 운영체제의 시스템 인터페이스 표준이다. 자바의 경우 한번 작성된 코드로 JVM이 제공되는 모든플랫폼에서 실행을 보장한다(고수준의 플랫폼 추상화). 비슷하게 POSIX 표준 또한 여러 종류의 유닉스 시스템에서 공통으로제공하는 시스템 인터페이스를 정의하고 있다(저수준의 플랫폼 추상화). 따라서 POSIX를 준수한 시스템 인터페이스를 사용하는코드는 다른 운영체제의 시스템 함수를 사용하는 데 문제가 되지 않는다.

앞에서 예를 든 정통부의 휴대전화 충전기 24핀 표준 규격이나 자바 표준, POSIX 표준, IEEE에 이르기까지 어디에나'표준'이 일종의 해결사, 중재자 역할을 한다. 이 '표준'의 역할은 지금의 맥락에서 의미심장하게도 OCP의 '인터페이스'의기능을 한다. 표준에 의해 사용자는 서비스의 기능(규약)을 신뢰할 수 있으며(closed), 서비스 제공자는 자신의 목적에 맞게확장·특화하여 서비스의 차별화, 상품성을 높인다(open).

OCP 주의점 1
다시 <그림 5>로 돌아가서, 공통 모듈(shared module)의 존재는 (그림에서와 같이) 시스템을 지저분하게하는 경향이 있다. <그림 4>와 같이 상속 구조를 갖는 자식 클래스들이 있을 때 공통된 루틴이나 변수를 리팩토링의‘Pull Up Method/Pull Up Field’하고 싶은 유혹에 빠진다. 리팩토링은 설계를 깔끔하게 하는 좋은 방법이지만문제는 대상의 크기에 있다. 위의 공통 모듈이 작을 경우 공통 모듈 재사용성을 얻기 위해 너무 잦은 (다른 영역의) 모듈을접근해야 하고 모듈 구성도 보는 바와 같이 지저분해진다.

OCP에서 주의할 점은 확장되는 것과 변경되지 않는 모듈을 분리하는 과정에서 크기 조절에 실패하면 오히려 관계가 더 복잡해져서설계를 망치는 경우가 있다는 것이다. 설계자의 좋은 자질 중 하나는 이런 크기 조절과 같은 갈등 상황을 잘 포착하여 (아깝지만)비장한 결단을 내릴 줄 아는 능력에 있다.

사례 2 : 미들웨어의 출현  

C/S에서 미들웨어로의 전환은 역시 변하지 않는 서비스를 독립시키려는 이유에서이다(물론 다른 의미도 많겠지만). 트랜잭션이나 분산 시스템, 메시징 서비스 같은 미들웨어적인 기능은 비대해지는 와 클라이언트 모두에게 부담이 됐다. 또한 이 모듈의 목적도 어느 정도 정리되어 있어 변하지 않는 모듈로도 손색이 없다. 따라서 자연스럽게 closed의 영역으로 분리됐다.

하지만 미들웨어도 완결된 시스템이 아니기 때문에 분리되는 과정에서 미들웨어 인터페이스를 만들게 된다. 즉 미들웨어 자체의 버전업그레이드나 내부 모듈의 확장 여지를 남기기 위해 (변하지 않아서 분리됐음에도 불구하고) 자신의 인터페이스를 클라이언트와 에 제공한다.

여기서 레이어 시스템의 양방향 OCP 전략을 볼 수 있는데, 한 레이어의 인터페이스는 관계하는 양쪽을 위해 두 개의 인터페이스로정의하게 된다. 가령 OSI 레이어에서 각 레이어는 위, 아래를 위한 두 개의 인터페이스를 갖는다. 이로써 그 레이어 내부의확장·변경으로부터 외부에 전달되는 충격을 무력화한다. 마치 이더넷 카드를 다른 제품으로 바꾼다 하더라도 그 이더넷 카드는 하위망 계층과 상위 드라이버 계층에 동일한 인터페이스를 준수하기 때문에 동작하는 데 아무 문제가 생기지 않는다.




OCP 주의점 2
하지만 재미있는 현상은 JTA와 같은 어댑터(adapter)의 역할이다. 가령 트랜잭션 모니터의 경우 의MTS나 JTS, 턱시도(Tuxedo)들은 인터페이스의 차이가 있어 비슷한 기능을 함에도 불구하고 상호 운용을 위해 서로의인터페이스를 변환시켜 주는 어댑터를 필요로 한다. 적절한 비유가 될지 모르지만, InputStream의 예에서도 공유 메모리스트림을 확장해야 한다고 했을 때 문제가 발생한다. 공유 메모리 접근은 비동기적이기 때문에 기존의 동기적인 read() 메쏘드를그대로 적용하는 데 문제가 생긴다. 이전까지 동기적인 스트림만 상대했던 설계자에게 있어서 공유 메모리는 경악할만한 요구사항이다. 이 경우에도 비동기적 접근을 동기적으로 표현하기 위한 어댑터가 필요할 것이다.

확장을 보장하는 open 모듈 영역에서 예측하지 못한 확장 타입을 만났을 때 인터페이스 변경하려는 안과 어댑터를 사용하려는 안사이에서 갈등하게 된다. 위의 두 예에서처럼 변경의 충격이 적은 후자를 택하는 경우가 대부분이다. 한 번 정해진 인터페이스는시간이 갈수록 사용하는 모듈이 많아지기 때문에 바꾸는 데 엄청난 출혈을 각오해야 한다. 자바의 deprecated API가대표적인 경우다.

즉, 인터페이스는 가능하면 변경해서는 안 된다. 따라서 인터페이스를 정의할 때 여러 경우의 수에 대한 고려와 예측이 필요하다.물론 과도한 예측은 불필요한 작업을 만들고 보통, 이 불필요한 작업의 양은 크기 마련이다. 따라서 설계자는 적절한 수준의 예측능력이 필요한데, 설계자에게 필요한 또 하나의 자질은 예지력이다.

사례 3 : 커맨드 패턴  

텍스트 에디터에서 Copy란 명령은 여러 방법으로 실행할 수 있다. 아이콘, 메뉴에서 선택, 오른쪽 마우스, 단축키 등을 통해사용할 수 있다. 하지만 명령을 요청하는 방법이 다를 뿐이지 처리하는 핸들러는 하나다. 이렇게 요청자와 처리자의 관계가복잡해지는 객체 관계에서 그 처리를 단순화하기 위해 커맨드 패턴을 사용한다. 즉, 명령을 실행하는 방식이 어떤 방법이든지Copy 기능을 구현한(execute() 메쏘드로) CopyCommand라는 객체를 생성하여 처리자에 전달하면 처리자는CopyCommand가 어떤 작업을 하는지 알 필요 없이 execute()를 실행하여 객체 관계를 단순화할 수 있다.

Command 객체는 실제 작업과 정보를 캡슐화하여 처리자에 전달된다. 따라서 복잡도는 낮아진다. 이렇게 처리하는 방식은스트럿츠에서도 적용되는데, 스트럿츠 Action 클래스는 커맨드 패턴의 좋은 사례가 된다. 커맨드 패턴은 이렇게 호출을 요청하는요청자와 그 요청을 처리하는 처리자 간에 Command라는 실제 처리 로직을 캡슐한 객체를 통해 의존성을 분리하는 패턴이다.

이제 우리는 이 구조에서도 OCP의 형태를 직관할 수 있다. 이 패턴이 유용한 점은 이 처리 캡슐 단위의 조합으로 또 하나의처리 단위를 만들 수 있다는 것이다. 가령 '파일 비교'란 명령을 수행하기 위해 '파일 열기' → diff → '파일 닫기'명령을 두 파일을 통해 수행함으로써 하나의 처리가 완성된다.



<그림 6> 커맨드 패턴

OCP 주의점 3
이 패턴이 성공하기 위한 포인트는 요청자와 처리자 사이의 계약인 커맨드의 역할이다. 처리자는 execute()란 인터페이스만알면 어떤 처리도 수행할 수 있다. 따라서 서로 의미적 관계가 없는 Command들도 execute()란 메쏘드로 무엇이든확장할 수 있다. OCP 구조에서 가 확장할 수 있는 운신의 폭이 넓어진 반면 클라이언트는 가어떤 처리를 하는지 무지해진다. 물론 커맨드 패턴에서의 execute() 메쏘드는 적절하지만 InputStream 예제에서의read()를 doWork() 같은 메쏘드로 대치한다면 좋은 구조가 되지 못 할 것이다. 왜냐하면 클라이언트는 자신이 어떤작업을 하는지 모르기 때문이다.

즉, 인터페이스 설계에서 적당한 추상화 레벨을 선택하는 것이 중요하다. 우리는 추상화라는 개념에 '구체적이지 않은' 정도의의미로 약간 느슨한 개념을 갖고 있다. 그래디 부치(Grady Booch)에 의하면 ‘추상화란 다른 모든 종류의 객체로부터식별될 수 있는 객체의 본질적인 특징’이라고 정의하고 있다. 즉, 이 '행위'에 대한 본질적인 정의를 통해 인터페이스를 식별해야한다. 이것이 OCP의 세 번째 주의점이다.

설계 원칙의 역설
디자인 패턴은 소프트웨어 설계의 좋은 템플릿이다. 우리는 디자인 패턴을 이용하여 설계 모델을 좋은 구조로 유도한다. 이 구조는소프트웨어 품질을 높이게 하고 기능을 강화시키기도 한다. 소프트웨어 설계 모델의 메타적인 원리가 디자인 패턴의 단위라고 한다면디자인 패턴에 등장하는 좋은 구조들에 대한 메타적인 원리가 이번 연재를 통해 소개할 원칙들 정도 된다.

이 원리들은 물론 표준화 작업에서부터 아키텍처 설계에 이르기까지 다양하게 적용할 수 있지만 그 크기의 대비를 보면 패턴보다 훨씬작고 여러 곳에 적용되는 원칙이다. 그리고 우리는 이 원칙들에 자연스럽게 익숙한지도 모른다. 하지만 이 원칙의 정체에 대해서는(필자가 앞에서 추상화를 언급한 것처럼) 모호하게 생각한다. 이 연재를 통해 좀더 이 원칙들의 의미와 내용을 심도 있게 다룰계획이다.

초로의 나이이임도 불구하고 태극권의 일인자였던 어느 노인이 이런 말을 했다고 한다. “나는 한평생을 걸쳐 무술을 연습했지만 이제서기(자세)를 제대로 할 수 있을 것 같다.” 입문자 때 배우는 서기 자세는 아주 쉬운 것 같지만 역설적이게도 아주 어렵다고한다. 우리에게 있어서 이 설계 원칙이 이 정도의 의미가 아닐까 생각된다. 따라서 한 번 더 고민할 만한 화두일 것이다.@
<< OCP 추가설명 - end>>

Liskov Substitution Principle (LSP, 리스코프의 치환원칙)

정확히는 Liskov의 공식화된 OO원칙(Fomulated principle)이 맞는 말이다. 가장 간단한 원칙이면서도 많은 걸 내포하고 있는 원칙으로, '자식(Sub-type)은 부모(Super-type)로 치환될 수 있다' 는 원칙이다.

즉, 이런 상속 관계에서 Customer A = new Police(); 가 가능해야 한다는 이야기이다.

리스코프의 원래 공식은

"Let q(x) be a property provable about objects x of type T. Then q(y) should be true for objects y of type S where S is a subtype of T."

"q(x)가 타입 T의 객체 x를 증명할 수 있는 속성이 된다고 가정한다. 이때, T의 하위 클래스 S의 객체 y 에 대해서도 q(y)는 '참'이어야 한다"


Dependency Inversion Principle(DIP, 의존성 역전 원칙)

"상위 레벨의 모듈은 하위 레벨들에 의존해선 안되며, 모든 것들은 추상에 의존해야 한다"는 원칙.

OCP와 마찬가지로 OO 설계의 중요한 개념 중 하나가 된다. 쉽게는 인터페이스에 의한 코딩으로 해석해도 무방하다. 종종IOC(Inversion Of Control)와 혼용해서 쓰는 경향이 있는데, IOC는 Control의 Flow에 좀 더집중되어 있기에, DIP가 발전되어서 IOC 와 DI(Dependency Injection)의 개념이 생겨났다고 보는게 맞다.참고로 IOC는 Call Back의 개념을 갖는 헐리우드 법칙(Hollywood Principle) - "Don't callus, we'll call you" (내가 알아서 연락할테니까, 전화하지 말고 기다려)과도 일맥 상통한다.

여유가 되면 IOC와 DI 에 대해서는 마틴 파울러(Martin Fowler)의 유명한 글인 Inversion of Control Containers and the Dependency Injection pattern을 읽어보면 많은 도움이 된다.



이 외에도 ISP (Interface Segregation Priciple, 인터페이스 분리 원칙) 등이 있지만, 중요도 측면에서 생략하고자 한다.

여기 소개된 원칙들은 개발의 기본이 되는 하나의 경향(Tendency)과 흐름(Stream)이라고 생각하고, 늘 고민하며 적용할 수 있도록 노력하자.


원문 : http://doortts.tistory.com/entry/OOP-%EC%9D%98-%EC%9B%90%EC%B9%99%EB%93%A4-Object-Orientied-Programming-Principles

by skyforce | 2009/12/02 14:54 | [IT] 개발이야기 | 트랙백 | 덧글(0)

Dependency Injection 정리

<<헐리우드 법칙에 관련된 내용을 찾다가 발견한 자료>>

Spring 프레임워크나 EJB3를 쓰다보면 Dependency Injection(이하 DI)에 대한 이야기가 많이 나오고 있다. 뿐만 아니라 근래에 나오는 프레임워크(Struts2, Hibernate, Grails, ...)들도 기본으로 D.I를 지원하는 추세이다.
D.I의 정확한 의미를 알아보겠다.

무엇이 문제인가?

Wiring
객체의 힘 - 추상화, 다형성
객체지향프로그램(OOP)의 위대함으로 추상화에 있다. 객체를 사용하는 클라이언트(Caller)를 실제 사용하는 객체가 뭔지 몰라도 객체의 인터페이스만 잡고 일을 시킬 수 있다.
여기에서 클라이언트가 잡고 있는 객체를 클라이언트 모르게 바꾸어도 그 코드는 잘 동작하고 동일한 인터페이스라도 실제객체(concrete class)가 어떤 것인지에 따라 그 동작 방법을 바꿀 수 있다. 이게 OO에서 잘 알려진다형성(polymorphism)이다.

Wiring
객체는 보통 홀로 동작하지않는다. 쓸만한 객체가 작성되었다면 최소한 그것을 사용하는 클라이언트는 있어야 할 것이다. 이와 같이 객체들은 서로 협력하여목적을 달성하기 위해 다른 객체의 참조를 가지고 이를 통해 객체간의 체인이 형성된다. 이를 wiring이라 한다.
사용자 삽입 이미지
추상화의 힘을 사용하기 위해 보통 인터페이스나 추상(abstract 또는 virtual) 클래스를 이용하여 객체간 참조한다. 이제 이 추상화된 참조에 실제 사용할 클래스들을 할당하여 연결하여야 하는 문제가 남아있다.

의도하지 않은 커플링(coupling)
이런 추상화의 힘을 이용하기 위해 객체를 사용하는 클라이언트(caller)를 객체의 인터페이스만을 잡고 있지만 사용할 객체를 생성하기 위해 new 연산자를 사용하게 된다.
이런! new 연산자에 의해 원하지 않게 구체적인 클래스가 의존관계가 형성되게 된다.
사용자 삽입 이미지

위의 그림의 예를 들면 CallerClass는 다음과 같은 코드를 가질 수 있다.
public CallerClass {
    Interface if;
    void doSomething() {
        if = new ConcreteA(); // or if = new ConcreteB()
        ....
       if.xxx();
    }
}
이렇게 의도하지 않은 의존관계를 해결하기 위해 주로 생성과 관련된 패턴을 사용하여 객체 생성을 클라이언트(CallerClass)로부터 분리하기도 한다. 더 좋은 방법은 없을까?

순진한 코더의 예제
다음에 설명할 DI의 개념 이해를 위해 굉장히 간단한 예제로 이야기 할 것이다. 예제는 다음 그림과 같이 임의의 저장소에서 영화목록을 읽고 특정 감독의 영화목록을 추출하는 프로그램이다.
저장소는 CSV(Comma Separated Volume) 파일이 될 수도 있고, DB나 다른 형태가 될 수도 있다. 임의의저장소에서 찾는 동작을 MovieFinder 인터페이스로 정의하고 이것을 구현한다. MovieLister는 이것을 사용한다.

MovieFinder
public interface MovieFinder {
    List findAll();
}

MovieLister
class MovieLister...
    private MovieFinder finder;
    public MovieLister() {
        finder = new CSVMovieFinder(“movies1.txt”);
    }
    public Movie[] moviesDirectedBy(String arg) {
        List allMovies = finder.findAll();
        for (Iterator it = allMovies.iterator(); it.hasNext();) {
            Movie movie = (Movie) it.next();
            if (!movie.getDirector().equals(arg)) it.remove();
        }
        return (Movie[]) allMovies.toArray(new Movie[allMovies.size()]);
   }

MovieFinder를 적절히 추상화했지만 위에서 보듯 MovieLister는 의도하지 않게 CVSMovieFinder와 강한 커플링을 형성하고 있다.
사용자 삽입 이미지
이러한 문제를 어떻게 풀 것인가?

IoC 그리고 Dependency Injection

제어의 역전 (Inversion of Control)
이 개념은 어떤 일을 하는 주체를 바꾸는 것을 의미하는데 통상적으로 일을 처리하는 주도권을 상대에게 넘겨준다는 의미이다.

헐리우드 법칙(Hollywood Principle)
제어의 역전을 이해하기 쉬운 예로 유명한 헐리우드 법칙을 들어보겠다.
예전 미국 영화전성기(지금도 마찬가지인가?)에 영화의 메카인 헐리우드에 많은 스타 지망생들이 몰려 들었다. 많은 스타 지망생들이헐리우드에 에이전시를 찾아가 오디션 신청하여 에이전시는 이를 매우 귀찮게 여기기 시작했다. 그래서 에이전시는 스타 지망생들에게이력서만 놓고 그냥 가라고 하면서 이렇게 말했다.
우리를 더 이상 부르지 마시오. 우리가 필요하면 당신을 부르겠소.
(Don not call us, we will call you)

소프트웨어에서의 헐리우드 법칙

소프트웨어 세계에서도 헐리우드 법칙이 오래전부터 적용되고 있었다. 전형적인 예로 Command line 프로그램과 GUI 프로그램을 들 수 있다.
최초의 Command line 기반 프로그램은 아래 그림과 같이 모든 제어를 내 프로그램이 담당했다. 입력이 필요하면 입력을 요청한 후 대기하고 입력이 들어오면 뭔가 해서 또 스스로 출력하기도 한다.
반면 GUI 프로그램이 보편화되면서 많은 GUI 프레임워크들이 출현하였다. 우리가 이 GUI 프로그램을 할 때 모든 입력요청과출력 그리고 이벤트 처리들과 같은 주요한 동작은 GUI 프레임워크가 주도하여 실행한다. 우리는 단지 특정 이벤트에 대해 어떻게반응할 것인지에 대한 콜백(callback, event handler, ...) 프로그램만작성하여 넣기만 한다. 이렇듯 더 이상 우리가 어떤 것을 주도하여 호출하는 것이 아니라 주도권은 빼앗기고 호출당하기를 기다리는콜백 프로그램을 만드는 일이 主가 되었다.
사용자 삽입 이미지

이를 Inversion of Control이라 하고 프레임워크의 일반적인 속성이다.
D.I는 객체 Wiring하는 문제를 이 IoC 개념을 이용하여 풀어보자는 시도에서 출발한다. (IoC와 D.I가 동의어로 사용되는 경우를 보았는데 그건 아니다.)

그럼 이제 제어의 역전 개념을 이용하여 전에 보았던 영화목록 찾기 프로그램의 의존관계 문제를 풀어보자.


의존성 주입 (Dependency Injection)
아이디어는 간단하다. 순진한 코더의 프로그램은 MovieLister가 능동적으로 자신이 사용할 구체적인 클래스를 생성했다는데에서문제가 발생했다. 이것을 IoC 아이디어를 빌려와서 사용할 객체의 생성을 다른 객체에게 맡겨보자. 그리고 자신에게 그 객체를넘겨달라고 하자.
사용자 삽입 이미지
그림에서 Assembler가 CSVMovieFinder를 생성하는 객체이다. Assembler는 CSVMovieFinder를생성하고 MovieLister에게 전달한다. 이제 더 이상 MovieLister에는 의도하지 않은 커플링이 발생하지 않고Assembler가 다른 MovieFinder(예로 DBMovieFinder)를 생성해서 주입(inject)하더라도 아무런영향을 받지 않는다.

그럼 MovieLister는 Assembler에게 어떻게 사용할 객체를 넘겨받을까. MovieLister는 사용할 MovieFinder를 받을 콜백 메소드만 제공하면 Assembler는 그 메소드로 MovieFinder를 넘긴다.
MovieLister
class MovieLister {
    private MovieFinder finder;
    ...
    public void setFinder(MovieFinder finder) {
        this.finder = finder;
    }
    ...
}



실전에서

이러한 DI를 제공하는 몇 가지 컨테이너가 있다. 이제는 Spring 프레임워크가 대세인 것 같은데 여기서 이것을 포함하여 두 가지 예만 보겠다.

PicoContainer (http://www.picocontainer.org/)
PicoContainer는 DI를 제공하는 가벼운 컨테이너이다. 이것은 생성자의 아규먼트를 통해 의존성 주입을 이용하여 객체가 wiring을 한다.
MovieLister
class MovieLister...
public MovieLister(MovieFinder finder) {
  this.finder = finder;
}
CSVMovieFinder
class CSVMovieFinder...
    public CSVMovieFinder(String filename) {
        this.filename = filename;
    }
위와 같이 의존성 주입이 필요한 객체를 생성자의 아규먼트로 받을 준비(콜백)를 한 후 다음과 같이 컨테이너를 초기화한다. 이 과정에서 자동으로 타입을 기반으로 맞는 타입을 찾아 wiring을 한다.
private MutablePicoContainer configureContainer() {
        MutablePicoContainer pico = new DefaultPicoContainer();
        Parameter[] finderParams =  {new ConstantParameter("movies1.txt")};
        pico.registerComponentImplementation(
              MovieFinder.class,CSVMovieFinder.class, finderParams);
        pico.registerComponentImplementation(MovieLister.class);
        return pico;
    }
그 후 컨테이너에서 객체 인스턴스를 얻어오면 wiring된 객체를 사용할 수 있다.
MutablePicoContainer pico = configureContainer();
MovieLister lister = (MovieLister) pico.getComponentInstance(MovieLister.class);
Movie[] movies = lister.moviesDirectedBy("Sergio Leone");


Spring 프레임워크 (http://www.springframework.org/)
무거운 EJB에 환멸을 느낀 개발자를 위해 제공하는 비교적 가벼운 컨테이너이다. 현재는 DI뿐만 아니라 애플리케이션 개발을 위한 다양한 장치를 제공하여 기반 프레임워크로 굉장히 많이 사용하고 있다.
Spring 프레임워크에서도 생성자를 통한 의존성 주입이 가능하지만 대부분 setter 메소드를 통한 의존성 주입을 사용한다. 따라서 먼저 사용할 객체를 받아들일 수 있는 콜백 메소드를 준비한다.
MovieLister
class MovieLister...
  private MovieFinder finder;
  public void setFinder(MovieFinder finder) {
    this.finder = finder;
  }

CSVMovieFinder
class CSVMovieFinder...
    public void setFilename(String filename) {
        this.filename = filename;
    }

Spring 프레임워크는 주로 객체간의 wiring 정보를 담고 있는 XML 파일을 설정파일로 사용한다.
<beans>
        <bean id="MovieLister" class="spring.MovieLister">
            <property name="finder">
                <ref local="MovieFinder"/>
            </property>
        </bean>
        <bean id="MovieFinder" class="spring.ColonMovieFinder">
            <property name="filename">
                <value>movies1.txt</value>
            </property>
        </bean>
    </beans>

여러가지 방법이 있겠지만 다음과 같이 XML 파일을 설정파일로 읽어 컨테이너를 초기화한다. 그 후 마찬가지로 컨테이너에서 wiring된 객체를 얻어 사용할 수 있다.
ApplicationContext ctx = new FileSystemXmlApplicationContext("spring.xml");
MovieLister lister = (MovieLister) ctx.getBean("MovieLister");
Movie[] movies = lister.moviesDirectedBy("Sergio Leone");

Service Locator
맨 처음 이야기했던 의도하지 않은 커플링을 해결하기 위해 객체를 찾아주는 Service Locator 패턴을 사용하기도 했다.
사용자 삽입 이미지
이 방법도 나름 훌륭하여 많이 사용하고 있다.
이것을 이용할 때도 MovieLister는 MovieFinder의 구현클래스(concrete class)에 의존성을 갖지 않지만 ServiceLocator를 의존하고 있다.

Service Locator vs Dependency Injection
공통점

두 방법 모두 초기 어리숙한 코더가 만든 프로그램에 비해 커플링을 없애(decoupling)주는 해결책을 제공한다.
둘 다 우리가 관심있는 객체가 구현 클래스를 직접 참조하지 않는다.

차이점
Service Locator(이하 S.L.)는 애플리케이션(객체-여기에서는 MovieLister)이 Locator를 통해 능동적으로 객체 획득을 요청한다. 그 결과 Locator에 의존적일 수 밖에 없다.
반면 DI는 애플리케이션이 구현클래스를 요청하지 않는다. 그저 누군가 그것을 해주기를 바랄 뿐이다.
즉 구현 클래스 획득 방법에서 서로 반대의 성격을 가지고 있다.

단점
IoC는 비교적 이해하기 어렵고 문제가 발생했을 때 추적하기 어려울 수도 있다.
DI가 이렇게 거의 완벽한 decoupling 방법을 제공하기 해도 IoC의 아이디어에 기반을 두기에 이러한 단점을 내포할 수도있다. 예로 의존관계에 문제가 발생했을 때 소스코드에 명확히 나오지 않으므로 추적하기 어려울 수도 있다.

DI가 유용한 경우
  • 객체간의 wiring을 한 군데에서 담당하기에 객체간의 의존관계를 한 눈에 파악할 수 있다. Spring 프레임워크의경우 XML 설정파일안에 객체간의 의존관계가 모두 나와 있다. 반면 S.L.의 경우 소스코드를 모두 추적해야만 이것이 가능할것이다.
  • POJO(Plan Old Java Object)로 구현이 더 쉽고 재사용이 용이하다. S.L.은 S.L이 제공하는 고유의 API(예:JNDI)를 사용하기 때문에 순수한 POJO가 안될 수도 있다.
  • 단위 테스트를 위해 테스트 객체(Mock Object)로 교환할 경우 S.L.은 테스트를 위해 테스트용 객체를찾도록 S.L.을 수정해야 한다. 반면 DI는 단지 setMovieFinder를 통해 테스트 용 MockMovieFinder를셋팅하기만 하면 된다.
원문 : http://greatkim91.tistory.com/entry/Dependency-Injection-1

by skyforce | 2009/12/02 14:10 | [IT] 개발이야기 | 트랙백 | 덧글(0)

처음 만나는 그림

처음 만나는 그림처음 만나는 그림 - 10점
선동기 지음/아트북스

언제부터인가 그림을 봐야겠다라는 생각이 들었다.

이런 구체적인 생각이 든건 아마도 박경철씨가 소개한 '서양미술사'라는 책 때문이 아닌가 싶다.
하지만 '서양미술사'는 그림에 대해 전혀 문외한인 나에게 너무나도 두껍고 왠지 버거운 느낌을 주는 책이었다.
흥미는 있었지만..선뜻 손이 쉽게 가지 않는 책이었다.

그래서 거부감없이 편하고 접근하기 쉬운 책을 우선 봐야겠다라고 생각했는데..
이 책이 눈에 가장 먼저 들어왔다.

책의 구성및 내용은 정말 내가 찾는 그런 책이었다.

이 책에는 꽤 많은 1800년대 중반에서 1900년대 초반까지의 서양작가들이 소개가 되고 있고 그들의 그림들또한 몇점씩 소개되고 있다.

어떻게 그림을 보는것이 제대로 보는것인지에 대한 지식을 주기 보다는 한점의 그림을 보고 그에 대한 작가의 감성적인 해설이 들어 있었다.
물론 그 그림에 대한 해석 내용은 온전히 지은이의 아주 사적인 감정을 표현한듯하다.

하지만 그림을 보고 옆에 써 있는 해설을 보면 왠지 그 그림에 동화되는 느낌이 든다.

그림에 대한 화풍이라던지 그림이 가지고 있는 객관적인 평가라던지..
그런 시각이 아닌 정말로 그림이 말하려고 하는 이야기를 들을수 있도록 가이드를 해주는 느낌이었다.

여기서의 지은이는..아마도 그림을 통역해주는 통역사가 아닌가 싶다.

이 책을 읽음으로써 나는 그림을 볼때 그림의 배치 및 구도 보다..

그림에 표현된 사람의 인상, 몸짓, 풍경의 그리움 및 화사함들에 관점을 가지고 그림을 대할수 있게 되었다.

다시한번 그림을 정독해야겠다.
http://skyforce.egloos.com2009-11-30T01:22:230.31010

by skyforce | 2009/11/30 10:22 | [MY] 책이야기 | 트랙백 | 덧글(0)

두바이의 몰락

오늘 코스피가 3%정도 빠졌다.

요즘 계속 횡보하던 코스피가 이렇게 빠진경우는 아주 드물었다.

그 이유는.."두바이월드의 채무불이행 선언" 때문이다.

향후 제2의 금융위기가 오지 않겠냐는..불안한 심리를 감추지 않고 메스컴에서는..떠들고 있다.

그런데 이런 상황을 아주 정확하게 박경철씨는 1년 한참전에 미리 예견하고 있었다.

정말 놀라지 않을수 없는 그 만의 통찰이 아닌가 싶다.

컬럼 전문 내용 : http://blog.naver.com/donodonsu/100053717397

내용을 간단히 살펴보면..

최근 주목받고 있는 두바이의 변신도 바로 이런 비극을 예방하기 위한 지도자의 판단이만들어낸 결과물이다. 천연자원에 의존한 나라들은 제조업이 약화되고, 광업에 지나치게 집중된 투자는 생산성의 후퇴를 가져오기때문이다. 하지만 이 점을 간파한 두바이의 신화도 머지않아 큰 실패가 예고되어 있다. 아무리 두바이가 금융과 관광의 중심지로자리 잡으려 해도, 열사의 나라 중동은 관광으로 승부하기에는 기후가 너무나 열악하고, 사막에 만든 인공 구조물을 구경하기 위해관광객이 일부러 두바이를 찾을 리는 없으며, 중동의 정세 불안은 두바이를 금융허브로 만드는 데 가장 치명적인 약점이 될 것이기때문이다.

지금 보이는 두바이의 성장은 넘치는 오일 달러가 만들어낸 이벤트에 지나지 않는다. 실제 두바이에서 일하는근로자의 70%가 외국인 근로자인 상황에서, 두바이는 비싼 기름을 퍼낸 돈으로 외국계 건설사와 노동자의 주머니만 불려주고 있기때문이다. 이 점은 러시아, 브라질과 같은 천연자원이 풍부한 나라에서 공통적으로 나타나는 현상 중 하나다. 그 점에서 지금우리나라 기업이 이들 나라에 과도하게 뛰어드는 것은 대단히 우려스러운 일이다. 자원부국은 글로벌 경기침체에 빠지면 내수의 취약성탓에 위기가 크게 증폭되는 특징이 있다. 두바이 투자로 각광을 받던 모 중견 건설사의 모호한 행보는 이미 그 시점이 눈앞에다가왔음을 보여주는 신호탄일 수도 있다.

 

그러고 보면 조만간 경제학 용어 중에 ‘두바이의 비극’이라는 새로운 조어가 등장하게 될지 며느리도 모를 일이다.


이 컬럼은

"2008 08/19   뉴스메이커 788호"에 기고된 내용이다.


이런것이 바로 그가 말하던 통찰력이 아닌가 싶다.


단순히 현재의 현상만 바라보지 않고, 과거의 지혜를 빌리고, 현재의 상황을 면밀히 검토하여, 미래를 통찰하는 능력..


언젠가 이 글을 그냥 무심코 그럴수도 있겠다 정도의 감흥으로 지나쳐 버린 나의 무지를 다시한번 되뇌이게 하다.




by skyforce | 2009/11/27 14:16 | [MY] 일상이야기 | 트랙백 | 덧글(0)

◀ 이전 페이지 다음 페이지 ▶