IBM®
메인 컨텐츠로 가기
    Korea [국가변경]    이용약관
 
 
   
        제품    서비스 & 솔루션    고객지원 & 다운로드    회원 서비스    
메인 컨텐츠로 가기

한국 developerWorks  >  자바 | 오픈 소스  >

JRuby와 스윙(Swing)으로 크로스 플랫폼 개발

Monkeybars JRuby 스윙 라이브러리를 사용해 데스크톱 애플리케이션을 빠르게 개발하자

developerWorks
문서 옵션

JavaScript가 필요한 문서 옵션은 디스플레이되지 않습니다.

샘플 코드

영어원문

영어원문


제안 및 의견
피드백

난이도 : 중급

James Britt, 사장, Happy Camper Studios

옮긴이 : 오국환 dwkorea@kr.ibm.com

원문 게재일 : 2008 년 10 월 07 일
번역 게재일 : 2008 년 12 월 16 일

루비로 웹과 콘솔 애플리케이션을 제작할 수 있지만, 또한 다수의 플랫폼에서 실행 가능한 복잡한 GUI 데스크톱 애플리케이션도 작성할 수 있습니다. JRuby 덕택에 루비 GUI 툴킷에서 자바(Java™) 플랫폼이 제공하는 UI 도구도 사용할 수 있습니다. JRuby는 루비의 C 구현 대비 안정된 대체 구현입니다. 본 문서에서는 Monkeybars를 소개하고, 이와 관련된 예제 애플리케이션도 더불어 제공합니다. Monkeybars는 JRuby와 스윙을 써서 애플리케이션을 제작하기 위한 라이브러리입니다.

루비는 현재까지 웹 애플리케이션 작성을 위한 최고의 프로그래밍 언어로 알려져 있다. 이는 주로 Ruby on Rails 프레임워크 덕분이다. 그러나 이 언어는 충분히 강력하므로, GUI 데스크톱 애플리케이션 작성 이상의 것도 할 수 있다. 이 글에서는 데스크톱 목적으로 루비를 사용하는 법을 다룬다. 독자는 GUI 데스크톱 애플리케이션을 생성하는 상세 예제를 통하여 Monkeybars를 실습해 볼 수 있다. Monkeybars란 스윙과 JRuby에 기반을 둔 오픈 소스 라이브러리다.

데스크톱용 루비

표준 루비 배포판은 Tk 바인딩을 위한 코드를 포함한다. Tk는 오픈 소스이고 다중 플랫폼을 지원하는, GUI 데스크톱 애플리케이션 생성 용도의 위젯 모음이다. 이는 매우 간편하다. 그러나 루비를 소스에서 설치하려면, Tk 의존 관계를 명확히 하여 컴파일 세팅에서 Tk를 포함하도록 하여야 한다. 그 뛰어난 “클릭 한 번의” 설치 프로그램 패키지를 써서 루비 온 윈도(Ruby on Windows®)를 설치하더라도, 여전히 여러 추가 단계를 거쳐야 Tk를 동작시킬 수 있다. Tk가 더 이상 자동 설치를 지원하지 않기 때문이다.

루비용 Tk 셋업이 다소 거추장스러운 데다, Tk 애플리케이션은 Tk 애플리케이션처럼 보일 때가 있다. 대상 플랫폼에 따라서는 어색하게 보일지도 모른다. 또한 복잡한 인터페이스를 생성하려는 시도는 끔찍하다. 결과적으로 Tk는 단순한 GUI 요구사항에 가장 적합하다.

가용 툴킷

Tk가 취약하다 보니, 루비용 GUI 툴킷 대체 옵션이 여럿 개발되었다(관련 링크는 참고자료를 보라). 한 번 써볼 만한 몇 가지 후보를 열거하면 다음과 같다.

  • FxRuby: 루비용 폭스(Fox) 바인딩이다. 폭스는 C++로 작성한 GUI 툴킷이다. FxRuby는 RubyGems로 설치할 수 있다. 윈도용으로는 바이너리 젬(gem)을 제공한다. 다른 플랫폼용 젬의 경우 네이티브 코드로 컴파일해 써야 한다.

  • WxRuby: 다중 플랫폼 wxWidgets C++ GUI 툴킷 바인딩이다. 이로써 네이티브 모양새를 닮은 데스크톱 애플리케이션을 작성할 수 있다. 젬으로 설치할 수 있다.

  • QtRuby: 루비용 Qt 툴킷(KDE 데스크톱 시스템에서 사용하는 툴킷) 바인딩이다. 윈도 설치용 젬을 제공한다. 그러나 다른 플랫폼에서는 소스 코드만 제공한다.

  • GTK-Ruby: GTK는 GNOME에서 사용하는 UI 툴킷이다. 네이티브 코드로 컴파일해 써야 한다.

  • Shoes: 최근에 소개된 루비 GUI 위젯이다. 본 목록에서 언급한 다른 툴킷과 달리, 루비 전용으로 디자인되었다. 플랫폼에 특화된 설치 프로그램을 제공한다.

  • 스윙: 스윙? 물론이다. 스윙은 모든 자바 런타임 환경에서 함께 설치되는 GUI 라이브러리다. JRuby를 사용하는 한 스윙을 쓸 수 있다.

이상의 목록 중 하나를 제외한 나머지는 모두 C나 C++로 작성된 GUI 또는 위젯 라이브러리다. 이러한 라이브러리는 루비, 파이썬, 펄과 같이 다른 언어에서도 호출할 수 있는 바인딩을 제공한다. 대개의 경우에 설치나 배포와 같은 여러 고려 사항이 있지만 말이다.

GUI 라이브러리 선택 시 고려 사항

어떤 GUI 툴셋을 사용할지는 전적으로 해당 프로젝트 요구 사항에 달렸다. 합리적인 선택을 위한 고려 사항은 다음과 같다.

  • 풍부한 위젯 컴포넌트 모음
  • 안정된 구현
  • (대개 매킨토시, Win32, KDE, GNOME 등) 다수 플랫폼 지원 여부
  • 호스트 플랫폼의 고유 모양새 지원
  • 활발히 유지 보수되는지 여부
  • 커스텀 위젯 작성의 편의성
  • 사용 제약 없는 라이선스
  • 적절한 가격
  • 빠른 개발을 위한 프레임워크와 라이브러리 제공
  • 성숙한 IDE와 폼 레이아웃 도구
  • 테스트 도구와 프레임워크
  • 패키지 제작과 배포 편의성

그저 잠시 잠깐 메시지 상자를 띄우거나 간단한 사용자 입력을 받는 정도의 요구 사항이면, 앞서 열거한 어떤 툴킷이든 충분하다. 단순한 요구 사항이라면, 플랫폼 지원 여부나 필요한 위젯 지원 여부, 적절한 가격에 초점을 맞추는 편이 낫다. 애플리케이션을 배포할 목적이라면, 툴킷 라이선스도 확인해야 한다. 사용자가 필요한 환경을 이미 갖추고 있는지, 필요한 라이브러리나 위젯을 독립 실행(stand-alone) 애플리케이션 또는 설치 패키지 형태로 번들 제공이 편리한지도 확인해야 한다.

복잡한 애플리케이션을 작성할 목적이라면, 요구 사항은 더욱 까다로워진다. 몇 가지 단순한 폼 이상의 애플리케이션이라면 폼 디자이너 도구가 필요할지 모른다. 또한 풍부한 위젯 모음을 원할지 모른다. 예를 들어, 날짜 선택기나 파일 브라우저 컴포넌트를 직접 작성하기보다는 이미 작성된 컴포넌트를 재사용하는 편이 나을 것이기 때문이다.

C 기반의 다양한 루비 GUI 툴킷은 나름대로 괜찮은 품질을 제공한다. 그러나 이 중 어느 것도 명백한 승자로 등극하지는 못 했다. 대개 루비에서 다중 플랫폼 데스크톱 개발 용도로 선뜻 선택할 수 있는 선두 주자는 없다. 정도의 차이는 있겠지만 모두 설치, 문서, 디자인 도구, 패키징, 배포 등에서 이슈를 안고 있다. 기능을 하나씩 비교해 보면, 이러한 C 기반 툴킷이 모두 C가 아닌 나머지 한 옵션 대비 절대적인 강점이 없다.




위로


자바 기술 200% 활용하기

JRuby는 자바 플랫폼용 루비 구현이다(참고자료). 이로써 JVM에서 루비 코드를 실행할 수 있다. JRuby에서 동작하는 루비 코드는 스윙을 포함하여 자바 라이브러리를 로딩하고 사용할 수 있다.

“JRuby + 스윙”에 대한 몇 가지 이견

긍정적인 측면에서 스윙 컴포넌트는 풍부하다. 부정적인 측면에서, 이러한 컴포넌트는 그저 스윙 객체일 뿐이다. 스윙 객체를 루비 코드에서 로딩할 수는 있지만, 연관된 API를 알아야 하고 문법적으로 다소 꼬아서 사용해야 한다. 스윙 문서와 예제는 다양하다. 그러나 이러한 컴포넌트에 루비 포장을 씌우지 않으면, 달리 방법이 없이 본래 스윙 코드를 루비 코드와 지저분하게 섞어 써야 한다.

공통적인 반대 의견 중 둘째는 스윙이 Qt 등과 비교하여 보기에 매끄럽거나 네이티브 UI를 닮지 않았다는 것이다. 그러나 지금은 예전같이 그렇지는 않으며, 스윙도 꽤 보기 좋다.

C 기반 도구 대비 자바 플랫폼을 평가하면, 몇 가지 선택을 해야 하고 무엇이 가장 중요한지를 결정해야 한다. 예를 들어, Qt가 상용 또는 오픈 소스 애플리케이션에서 다양한 비용과 제약이 있는 반면, 스윙은 완전 공짜로 쓸 수 있다. 그 대신, 프로그램에서 보기에 Qt 컴포넌트의 모양새가 더 나아 보일 수는 있다.

JRuby가 좋은 선택이 되는 자바의 몇 가지 측면은 다음과 같다.

  • 안정되고 잘 테스트된 플랫폼이다.
  • 강력한 커뮤니티와 벤더 지원이 있다.
  • 문서가 잘 갖춰져 있고 풍부하다.
  • 훌륭한 IDE와 UI 레이아웃 도구가 있다.
  • (비용과 라이선스 측면에서) 공짜로 쓸 수 있다.
  • 자바 런타임은 대체로 사용자 컴퓨터에 이미 설치되어 있다.

(J)Ruby에서 애플리케이션을 작성하고 UI는 스윙을 쓰고자 한다면, 사용자가 자바 런타임 최신판을 갖고 있는지 확인하고 JRuby JAR 파일을 포함하도록 애플리케이션을 패키징하기만 하면 된다. JRuby 애플리케이션 패키징을 위한 루비 도구가 있으므로, 이 작업은 특별히 이슈가 되지 않는다.

“(J)Ruby + 스윙”에 대한 개발자 옵션

루비에서 스윙을 사용하는 여러 옵션이 있다.

  • 스윙 객체에 수작업으로 본래 자바 API 스타일 그대로 한 줄씩 메서드를 호출: 간단한 경우라면, 마치 다른 루비 객체를 다루듯이 스윙 객체를 참조할 수 있다.
    panel = Java::javax::swing::JFrame.new("JRuby panel")
    panel.show

  • "빌더"나 도메인 특화 언어(domain specific language, DSL) 스타일의 라이브러리: 패널이나 폼을 구축하고 컴포넌트를 수작업으로 추가하는 일 정도를 꼼수로 빠르게 처리할 수 있다. 이를 위해 스윙과의 상호 작용을 더 루비스럽게 해 주는 몇몇 라이브러리가 있다. 예를 들면, 다음과 같은 것이 있다.
    • Cheri::Swing은 루비 블록 구문을 써서 스윙 코드를 생성한다.

    • 또 다른 라이브러리인 Profligacy는 본래 스윙 메서드를 호출하는 곳에 루비 포장을 덧씌워, 웬만하면 자바 스타일의 코드 없이 스윙 코드를 작성할 수 있도록 돕는다. 여전히 스윙 컴포넌트를 적절히 사용하려면 스윙 API와 친숙해야 함은 물론이다.
    이러한 두 접근법은 패널, 폼, 수공예 코드를 사용한 레이아웃을 전제로 한다. 스윙 코드를 직접 사용하는 것과 비교하여 개선된 것은 분명하지만, 복잡한 사용자 인터페이스를 다루기에는 여전히 버거워 고생을 감수해야 한다.

  • “자바 클래스가 어디에서 왔든 상관하지 않겠다”는 접근법: 세 번째 접근법은 그저 루비로 스윙 코드를 쉽게 작성하려는 시도를 접고 스윙 객체를 생성하는 컴파일된 자바 코드가 이미 있다는 가정에서 시작한다.

마지막에 언급한 접근법이 바로 Monkeybars 라이브러리에서 취하는 방법이다(참고자료). 몇 개의 매우 우수하면서도, 마음껏 쓸 수 있는 비주얼 스윙 UI 레이아웃 에디터가 있다. 앞서 언급한 (폭스나 GTK 등) GUI 툴킷을 사용하면서, 이따금 대화 상자를 띄우려고 UI 에디터를 쓰지는 않는다. 그러나 이 이상의 작업을 하면서 이 같은 도구를 무시할 수는 없을 것이다. 이러한 도구를 외면하면서까지 섬세한 데스크톱 애플리케이션의 UI 코드를 손으로 작성할 당위성은 어디에도 없다.

Monkeybars

Monkeybars 프로젝트는 어느 제품 개발 과정에서 Happy Camper Studios의 David Koontz에 의해 나왔고, 테스트가 쉽고 유지 보수가 가능하며 복잡한 루비 데스크톱 애플리케이션을 생성하려는 경험을 바탕으로 계속 진화했다. 이 프로젝트는 활발히 관리되고 있으며 누구든 완전히 자유롭게 사용할 수 있다.

Monkeybars는 기존 자바 스윙 클래스(스윙 UI를 정의하는 말 그대로 컴파일된 자바 클래스)를 루비 코드와 연결하는 오픈 소스 루비 라이브러리다. 이는 모델(Model), 뷰(View), 컨트롤러(Controller)의 MVC 디자인 패턴을 따른다. MVC는 뷰 로직과 UI 컴포넌트를 애플리케이션 로직으로부터 분리하는 데 목적이 있다.

이는 자바 언어와 스윙 라이브러리를 사용하므로, Monkeybars는 성숙하고 안정된 기술을 바탕으로 개발되었다. JRuby를 위한 다른 현재의 스윙 라이브러리와는 달리 크고, 복잡하면서, 다중 패널을 가진 애플리케이션을 작성하기에도 적합하다. 나중에 보겠지만, Monkeybars 애플리케이션을 생성하면 몇 가지 오버헤드를 수반한다. 따라서 이는 간단한 폼 제작을 위한 최적의 해법은 아니다. 그러나 어느 정도의 복잡도를 요구하는 JRuby 데스크톱 애플리케이션을 작성한다면 합리적인 선택이 될 수 있다.

  • (사용자가 최신 JVM을 설치했느냐에 따라) 다중 플랫폼에 안정된 배포 가능
  • 임의의 복잡도를 지닌 UI 위젯을 선택할 수 있는 폭이 넓다.
  • 복잡한 UI 폼과 패널을 생성하고 서로간에 상호 작용할 수 있다.
SWT 대신 스윙을 사용한 이유는 무엇인가?

Monkeybars는 다음 이유로 SWT(Standard Widget Toolkit) 대신 스윙을 사용하였다.

  • 스윙은 자바 개발 환경의 일부다. 자바 개발 환경을 갖춘 사용자라면 스윙도 갖고 있다.
  • 스윙은 컴포넌트의 동작에 대해 세밀하게 제어할 수 있다.
  • 스윙 컴포넌트는 컴포넌트가 어떻게 보일지에 대해 유연성을 더 많이 제공한다.

Profligacy 같이 Monkeybars는 스윙 API를 감추지 않는다. 그럼에도, Monkeybars는 컴파일된 UI 클래스를 다루므로, 실제 레이아웃을 생성하기 위한 어느 도구나 애플리케이션을 최대한 활용할 수 있다. 애플리케이션의 복잡도에 따라, 루비 코드에서 무엇을 할지 알아내려고 어느 정도는 스윙 컴포넌트 API 문서와 코드 예제를 참조하는 것은 불가피하다(그러나 루비와 자바 라이브러리 간의 깔끔한 통합 덕분에, 편리하게 재사용할 수 있도록 루비 API 내에 이러한 스윙 코드를 쉽게 감쌀 수 있다). Monkeybars를 써서 만든 프로그램은 임의의 복잡도를 가질 수 있지만, 몇 가지 기본 패턴을 따라 코드를 관리 가능한 수준으로 유지할 수 있다.

Monkeybars가 MVC를 구현한 방식

MVC 패턴은 여러 변종과 함께 오랜 역사를 갖고 있다. Monkeybars에서의 기본 전제는 각 스윙 프레임에 적용된 MVC 패턴이다(여기서 스윙 프레임이란 말하자면, 관련된 컴포넌트나 위젯을 잡고 있는 UI 객체다. 몇몇 경우에 이는 모델 패널일 수도 있다). 모델, 뷰, 컨트롤러에 해당하는 세 가지 루비 파일이 있다. 모델 클래스는 본질적인 비즈니스 로직을 갖고 애플리케이션에서 그 로직에 해당하는 데이터를 관리한다. 이러한 내용은 모델과 상호 작용하기 위한 수단으로 존재하는 뷰나 컨트롤러 코드와는 무관해야 한다. 뷰와 컨트롤러를 모델과 독립적으로 유지하는 것이 개발하고 테스트하기에 쉽다.

뷰는 컴파일된 스윙 코드를 포함하는 특정 자바 클래스를 참조하기 위한 또 다른 루비 파일이다. 뷰는 스윙 컴포넌트와 모델 데이터 간의 상호 작용을 관장한다. 뷰는 모델에 직접 접근할 수 있다. 그러나 데이터를 컨트롤러에 전달하는 수단으로 모델의 사본과도 동작한다. 모델은 이러한 두 가지 목적을 달성하려는 것이므로, 이 점이 모델 클래스를 디자인할 때 유념해야 할 사항이다. 모델의 원본 인스턴스는 장기간 상태를 유지하고 애플리케이션 로직을 제공한다. 뷰가 사용하는 사본은 본질적으로 데이터 접근용 객체이고 폐기 처분할 수 있다. 모델은 뷰가 사용하는 데이터 객체를 레퍼런스로 참조하도록 하여 상대적으로 사본 생성에 부담이 없어야 한다.

뷰는 컨트롤러와 직접적으로 접촉하지 않는다. 대신, 신호를 줄 수 있는 시스템을 제공하여 컨트롤러와 뷰 간에 상호 작용을 추상화한다. 이와 같이 뷰와 컨트롤러 간에 꼬임을 방지하면 더 쉽게 테스트할 수 있다.

이렇게 기술함으로써 여러 상세한 부분을 섬세하게 단순화하고 일부 생략하기도 한다. 이러한 구조 하에서 뷰와 컨트롤러가 친밀하게 서로 상호 작용한다. 하부구조에서는 이러한 동작을 조율할 수 있는 수단이 필요하다. Monkeybars의 목적은 개발자의 손을 묶는 것이 아니라 테스트 가능하고 관리 가능한 코드를 작성하도록 돕는 데 있다. 개발자는 적절하다고 생각되면 의도한 API일지라도 자유롭게 건너 뛸 수 있다.

컨트롤러 클래스는 (버튼 클릭과 텍스트 입력 변화 등) 스윙 이벤트의 핸들러를 정의하고 모델의 상태를 제어하는 곳이다. 컨트롤러는 모델의 원본 인스턴스의 레퍼런스를 유지한다. 컨트롤러는 뷰와 직접 통신하지 않는다.

컨트롤러가 뷰에서 데이터를 얻고자 할 때, 뷰는 현재 UI 내용물과 관계하는 모델의 사본을 넘겨 준다. 컨트롤러는 이 데이터를 갖고 모델의 원본 인스턴스를 갱신하거나 이러한 값에 따라 몇 가지 동작을 수행할지 결정할 수 있다. 컨트롤러는 뷰에서 스스로 값을 갱신하고 갱신된 값을 돌려 주도록 명령할 수도 있다. 예제 애플리케이션을 통해 이러한 내용이 어떻게 동작하는지 살펴 보겠다.




위로


Monkeybars를 사용한 JRuby 스윙 애플리케이션 예제

Monkeybars를 써서 생성한 간단한 프로그램을 살펴 보고, 스윙과 루비를 써서 데스크톱 애플리케이션을 작성하는 감을 잡자(전체 예제 소스 코드의 링크는 다운로드를 보라).

시작하기

시작에 앞서, 몇 가지 갖출 것이 있다.

  1. jruby-complete.jar의 사본을 받자(다운로드 링크는 참고자료를 보라).

  2. Monkeybars 라이브러리를 설치하라. 여느 젬과 같이 이 라이브러리를 설치할 수 있다.
    sudo gem install monkeybars
    

    gitorious.org의 저장소에서 현재 소스 코드를 얻을 수도 있다(참고자료).

  3. 다음과 같이 rawr 젬을 설치한다.
    sudo gem install rawr

    엄밀히 말해, rawr는 Monkeybars 애플리케이션 작성의 필수품은 아니다. 그러나 이것은 JRuby 애플리케이션을 실행 가능한 단일 JAR 파일로 변환하는 여러 유용한 rake 태스크를 제공한다. 본 문서의 예제에서 이를 사용한다.

애플리케이션 기본

예제 애플리케이션은 “카드”의 수를 정의한 텍스트 파일을 읽는 “플래시 카드” 프로그램이다. 이 애플리케이션은 종료할 때까지 루프를 돌며, 주기적으로 짧은 순간 동안 자신을 보였다가 감췄다가 한다. 기본적으로, 이는 학습을 위한 도구다. 이 예제에서 카드란 독일어 어휘와 구문 모음이다. 이 프로그램은 또한 카드 정의 파일의 위치와 (보임/숨김 속도, 창 크기 등) 몇 가지 세팅을 정의한 설정 파일을 읽기도 한다.

이 예제의 목적은 다음과 같다.

  • Monkeybars 코드 생성기의 용도를 보여 준다. 이 생성기는 공통 파일 생성을 자동화한다.
  • Monkeybars 애플리케이션의 기본 구조를 보여 준다.
  • 각 부분별로 Monkeybars MVC 튜플을 생성하는 것을 보여 준다.
  • Monkeybars가 애플리케이션 데이터를 UI 컴포넌트에 매핑하는 방식을 보여 준다.
  • 애플리케이션을 실행 가능한 단일 JAR 파일로 패키징하는 법을 보여 준다.

Monkeybars 애플리케이션 생성 스크립트 사용하기

일단 설치되면, Monkeybars는 일련의 초기 애플리케이션 파일을 생성할 용도로 커맨드라인 스크립트를 제공한다. 새로운 Monkeybars 프로젝트를 시작하려면, 젬과 함께 설치된 monkeybars 스크립트를 실행한다. 프로젝트 이름은 monkey_see라고 하자.

$ monkeybars monkey_see

이 스크립트는 주어진 경로(또는 애플리케이션 이름만 주어진 경우 현재 디렉터리)에 새 디렉터리를 생성하고 새 애플리케이션에서 사용할 핵심 파일과 여러 디렉터리를 추가한다.

rawr을 사용하여 코드를 자바 환경으로 부트스트랩하기

rawr은 Monkeybars에서 파생된 또 다른 루비 라이브러리다. 이것은 잡다한 패키징 작업을 수행한다. 또한, 이것은 Monkeybars 애플리케이션에서 사용할 기본 자바 클래스를 생성하기 위한 커맨드라인 스크립트를 제공한다. 이렇게 생성되는 기본 자바 클래스 덕분에 (애플리케이션을 JRuby를 통하여 루비 프로그램처럼 실행하지 않고) Monkeybars 애플리케이션을 자바 애플리케이션처럼 실행하게끔 한다.

Monkeybars 애플리케이션에서 이것을 쓰려면, 프로젝트 디렉터리로 가서 rawr 스크립트를 실행한다.

$ cd money_see; rawr install

Monkeybars rake 태스크를 사용하여 파일 생성하기

Monkeybars가 기능을 모델, 뷰, 컨트롤러로 각각 분할하는 방식을 이미 살펴 보았다. 관례적으로 이러한 파일은 한 디렉터리에 위치한다. 이러한 작업을 도우려고, Monkeybars는 이러한 파일을 생성하기 위한 목적으로 rake 태스크를 제공한다. 세 파일 중 하나 또는 (더 일반적인 경우로) 전체 파일 세트를 생성할 수 있다.

$ rake generate ALL=src/flash

이 명령은 “src/” 아래 “flash”라는 이름의 서브디렉터리를 생성하고 flash_controller.rb, flash_view.rb, flash_model.rb라는 파일 세 개를 그 서브디렉터리에 생성한다. 처음 두 파일은 기본 Monkeybars 클래스에서 상속한 껍데기 클래스를 갖는다. 모델 코드의 경우는 이와 같지 않다. Monkeybars는 애플리케이션 로직과 데이터를 어떻게 관리할지에 대한 어떠한 가정도 하지 않는다. 이는 전적으로 개발자의 몫이다.




위로


UI 생성하기

애플리케이션 인터페이스를 구성하려면 플래시 카드 데이터를 보여 주는 스윙 클래스가 필요하다. 이 클래스를 어떻게 생성할지는 개발자의 몫이다. Monkeybars는 특정 UI 도구나 스윙 코드 생성기와 완전 독립적이다. 편의상, 스윙 파일을 관련 튜플(src/flash/FlashFrame.java)과 같은 디렉터리에 둔다. 여기서 개발자는 클래스 패키지 이름을 알아 둘 필요가 있다. 그래야, 뷰 클래스에 이것들을 전달할 수 있다(여기서는 flash 패키지 이름을 사용하고 클래스 이름은 FlashFrame이다).

이 애플리케이션의 화면 레이아웃은 그림 1과 같다.


그림 1. 애플리케이션의 화면 레이아웃
애플리케이션의 화면 레이아웃

몇 가지 요점: 플래시 카드 내용물을 보여줄 목적으로 JTextPane을 써서 HTML로 표시할 텍스트 형식을 지정하도록 한다. 텍스트 페인과 버튼에는 인식할 수 있는 이름을 부여해야 한다. 이렇게 이름을 붙여 두면 뷰를 다루어 UI 컴포넌트에 대해 뭔가 알고자 할 때 편리하다. 프로그램 코드는 루비에 있으므로, 루비의 메서드 이름 짓기 관례를 따르도록 하라. 텍스트 페인에는 card_pane이라고 이름을 붙이고 두 메뉴 아이템에는 edit_menu_itemquit_menu_item이라는 이름을 붙인다. 필요한 단축 키도 물론 등록하도록 한다.

프레임 자체의 이름은 그리 중요하지 않다. 뷰 클래스는 이름으로 컴포넌트를 직접 참조할 수 있다.

모델 정의

모델은 애플리케이션 로직과 UI 뒤에 숨은 데이터를 관리한다. Monkeybars 프로그램은 일반적으로 각 자바 폼 별로 모델을 가진다. 예제 애플리케이션은 플래시 카드 데이터를 다루기 위해 단일 모델을 사용한다. 모델 코드는 알려진 위치에서 데이터를 로딩할 수 있어야 하고, 데이터에 접근할 수 있도록 퍼블릭 메서드를 제공할 수 있어야 한다.

단순함을 위해, 애플리케이션이 실행되는 곳에서부터 데이터를 서브디렉터리 내의 텍스트 파일에 저장해 보자. 수작업 HTML보다는 텍스타일(textile) 마크업으로 작성하고 RedCloth 루비 라이브러리로 마크업을 변형할 수 있다. 각 카드 입력은 구분자 문자열로 구분한다.

외부 라이브러리 사용하기

텍스타일은 단순한 일반 텍스트 관례를 따라 HTML을 정의할 목적으로 사용되는 텍스트 마크업 형식이다. 예를 들어, <em>italicized</em>로 쓰려면, 대신 _italicized_라고 쓴다. RedCloth는 젬으로 설치할 수 있는 루비 라이브러리로, 텍스타일 형식의 텍스트를 HTML로 변환한다.

Rubygems는 외부 라이브러리를 쉽게 설치하고 사용할 수 있도록 한다. 그러나 개발자는 코드를 JAR에 패키징하고 잠재적으로 이것을 배포할 것이므로, 모든 코드가 애플리케이션에 포함되었는지 확인해야 한다. 이를 위해, RedCloth 젬을 풀고 redcloth.rb 파일을 프로젝트의 “ruby/lib/” 디렉터리에 복사해야 한다.

$ cd /tmp; gem unpack RedCloth

이와 같이 하면, (젬 버전이 다르지 않는 한) “/tmp/RedCloth-3.0.4/”가 생긴다. “/tmp/RedCloth-3.0.4/lib/redcloth.rb”를 monkey_see 프로젝트의 “lib/ruby/” 디렉터리로 복사한다.

일반적으로 어떠한 루비 라이브러리든 애플리케이션의 핵심 요소가 아닌 것은 (관례를 따라) “lib/ruby/” 아래에 위치한다. 젬을 사용하면, 실제 라이브러리 파일을 풀어 해당 프로젝트에 그 파일을 추가해야 한다. 본 글의 나중에, 프로그램에서 이러한 파일을 어떤 식으로 찾게끔 하는지 언급하겠다.

주요 모델 메서드

load_cards 메서드는 텍스트 파일을 디스크에서 읽어서 각 카드를 나누고 @card 인스턴스 변수에 결과 값을 할당한다.

select_card 메서드는 임의로 카드를 선택하여 @current_card 인스턴스 변수에 이를 할당한다. attr_accessor를 사용하여 이 변수를 읽고 쓰기 위한 메서드를 정의한다.

이제 이를 정리하여 어떤 카드가 UI에서 보일지를 그 자리에서 편집할 수 있도록 한다. 편집이 끝나면 update_current_card 메서드가 @current_card 내용물을 취하여 @cards 배열에 다시 삽입한다. save 메서드는 @cards 배열을 디스크로 다시 쓴다.

current_card 메서드의 값이 바로 화면에 그리고자 하는 것이다. 이렇게 하려면 뷰 클래스가 필요하다.

뷰 클래스 정의하기

Monkeybars 뷰 클래스는 자바 스윙 클래스의 소유자다. flash_view.rb 파일을 열어 보면, 이것이 set_java_class라는 클래스 메서드를 호출한다는 것을 알 수 있다. 이 메서드는 뷰를 위해 정의한 스윙 클래스에 세팅해야 한다. 본 예제에서는 flash.FlashFrame이 이에 해당한다.

일반적으로, Monkeybars 뷰 클래스는 다음 세 가지 작업을 수행할 필요가 있다.

  • 스윙 컴포넌트와 데이터를 주고 받는다.
  • (크기나 위치 같은) 뷰 중심의 잡다한 동작을 관리한다.
  • 컨트롤러가 보내는 신호에 반응한다.

데이터 매핑하기

Monkeybars는 모델 메서드를 스윙 컨트롤에 엮는 map 메서드를 지원한다. 가장 간단한 사용 예는 다음과 같이 그저 UI 컴포넌트 메서드와 모델 메서드를 연결하는 것이다.

map :view => :card_pane.text, :model => :current_card

이러한 매핑은 직접적인 양방향 관계에 기반을 둔 기본 동작을 사용한다. 즉, card_pane 컴포넌트의 text 메서드의 결과는 모델의 current_card= 메서드로 전달된다. 뷰가 모델로부터 갱신되면, 프로세스는 반대가 된다. model.current_card 결과는 card_pane.text로 전파된다(노트: JRuby는 루비/자바 이름 규칙을 다룬다. 따라서 실제 스윙 메서드인 setTextset_text=로 호출할 수 있다).

종종 이런 형태의 단순한 매핑이 무난히 동작하기도 한다. 그러나 때때로 데이터 타입이나 포맷의 차이 또는 일부 애플리케이션 로직의 요구 사항으로 인해 직접적인 데이터 교환을 원치 않을 수도 있다. Monkeybars에서는 데이터 교환시 중개자를 사용할 수 있다. :using 매개변수(즉, 배열을 가리키는 해시 키)를 사용하여 매핑을 전달한다. 이 매개변수는 데이터를 모델에서 뷰로 또는 뷰에서 모델로 옮길 때 사용하는 대체 수단을 가리킨다(:using을 사용하는 또 다른 이유는 스윙 컴포넌트의 값이나 상태가 일반적인 getPropertysetProperty 패턴을 따르지 않는 컴포넌트 메서드나 자식 객체를 다룰 필요가 있는 상황을 처리하는 것이다).

이번 코드에서 card_pane text 속성에 값을 할당하기 전에 모델에서 읽어낸 텍스타일 형식의 문자열을 HTML로 먼저 변환하고자 한다. 이를 처리하기 위해 to_html 메서드를 생성하자. 마찬가지로 뷰에서 직접 모델의 current_card 값을 갱신하지 않도록 하자. 뷰에서도 카드를 편집하기 위한 특별 코드가 필요하다. 따라서 뷰에서 모델로 매핑하는(view-to-model) 메서드 이름이 있어야 할 자리에 nil을 대신 사용하자.

결과는 다음과 같은 매핑이 된다.

 map :view  => :content_pane.text, 
     :model => :current_card, :using => [:to_html, nil ]

스윙 프레임이 특별한 방식으로 자신을 표현할 필요가 있을 수도 있다. 기본적으로, 스윙 프레임은 스크린에서 상단 좌측에 나타난다. 애플리케이션에서 이 프레임을 상단 우측 모서리에 보이기를 원할 수 있다. 또는 갑자기 나타났다가 사라지는 것이 아니라 부드러운 슬라이딩 효과를 주고 싶을 수도 있다.

스윙 객체 관리하기

뷰 클래스는 해당 스윙 클래스가 참조하는, @main_view_component라는 이름의 특별한 인스턴스 변수를 갖는다. 뷰 코드는 이 객체를 통해 스윙 컴포넌트와 상호 작용한다. 플래시 카드의 텍스트 페인의 내용물을 변경하려면, 본 예에서는 다음과 같이 쓰도록 한다.

 @main_view_component.card_pane.text = "Some new text"

그러나 뷰 클래스는 본질적으로 이러한 유형의 코드를 위해 존재한다. 따라서 Monkeybars가 @main_view_component 변수를 대신 처리하고, 개발자는 이 변수를 명시적으로 사용하지 않고 컴포넌트를 직접 참조할 수 있다.

 card_pane.text = "Some new text"

기본 Monkeybars::View 클래스는 method_missing을 사용하여 이러한 코드를 가로채고, 이것이 컴포넌트 참조인지 확인하여 이 요청을 @main_view_component에서 대리하도록 한다.

스윙 프레임에 대한 명시적인 참조가 없이도, 다음과 같이 메서드를 호출할 수도 있다.

@main_view_component.width = 500

부드러운 슬라이딩 효과를 얻기 위해, 뷰 클래스는 스윙 프레임의 높이와 위치를 조작하는 메서드를 갖는다. 이 메서드는 점차 프레임을 확대하거나 축소하여 각 렌더링 주기마다 프레임이 스크린 상단에서 아래로 슬라이딩하며 내려 오게 하거나 다시 올라가게 한다.

컨트롤러에서 요청 처리하기

MVC 튜플에서 키 부분을 분리하도록 Monkeybars를 디자인했다. 뷰는 자바 스윙 객체에 대한 직접적인 참조를 가지므로, 이 부분은 테스트하기 가장 까다로운 부분이다. Monkeybars는 모델과 컨트롤러가 직접 뷰와 상호 작용하는 일은 최소화하려고 했다. 그러나 컨트롤러에는 UI 이벤트를 다룰 책임이 있으므로, 필연적으로 컨트롤러가 뷰와 반응하도록 제어할 필요가 있다. 그렇지만, 컨트롤러는 뷰 클래스와 직접 통신하지 않으며, 대신 시그널을 사용한다.

잠깐 이 부분을 컨트롤러 측면에서 보자. 뷰에서는 define_signal 메서드를 써서 시그널 처리자를 정의할 필요가 있다. 이 메서드는 시그널 이름과 그 시그널을 처리하는 뷰 메서드를 정의하는 해시를 취한다.

define_signal :name => :initialize,  
              :handler => :do_roll_up

시그널 처리 메서드는 반드시 (컨트롤러에서 전달되는) 모델과 전달 객체, 이 두 개의 인자를 가진다. 전달 객체는 컨트롤러와 뷰 간에 데이터를 주고 받기 위해 사용하는 임시 해시다. 뷰는 UI의 초기 위치, 슬라이딩으로 들어오고 나가는 순서, 카드 편집의 시작과 종료를 정의한 시그널을 가진다. 이러한 각 시그널 핸들러는 매우 짧다. 다음은 do_roll_up method 메서드의 예다.

  def do_roll_up model, transfer
    hide
    move_to_top_right
    roll_up
  end

편집 순서는 메뉴 이벤트를 통해 신호를 준다. Edit 메뉴 아이템이 편집을 켜고 끈다. 뷰에서 편집 순서는 card_pane.editable = true로 세팅하고 HTML로 그린 내용물을 원본 텍스타일 카드 텍스트와 치환하는 것을 의미한다. 또는, 컴포넌트의 콘텐츠 타입을 변경하여 일반 텍스트를 제대로 그릴 필요도 있다.

편집이 끝나면, 반대 상황이 종료된다. 페인에 HTML이 주어지면, editablefalse로 세팅된다. 뷰는 스윙 컴포넌트의 상태를 관리하는 데만 관심이 있다. 컨트롤러는 모델에서 텍스트 갱신과 저장을 처리하도록 지시하는 일을 한다.

컨트롤러 클래스 정의하기

중첩된 컨트롤러

Monkeybars 컨트롤러는 주로 싱글톤(singleton) 클래스로 사용된다. 일반적으로 다중 인스턴스가 필요하지 않다. 주의할 예외라면 프레임이나 페인이 같은 클래스의 인스턴스인 여러 서브컴포넌트를 가져 중첩된 컨트롤러를 사용하는 때다. 이 주제는 본 문서의 범위를 벗어나므로 깊이 다루지는 않겠다. 기본적으로 프레임, 페널, 컴포넌트의 복잡한 구성을 개별 MVC 튜플 세트로 나눌 수 있다. 주소록이 좋은 예가 될 수 있다. 최상위 스윙 프레임에서 여러 주소 객체를 그리는데, 이 때 각 주소 객체는 address_entry라는 MVC 튜플 세트의 인스턴스가 되는 경우다.

스윙 객체는 몇 가지 메뉴 아이템을 갖는다. 그러나 뷰 클래스에 아직 아무런 코드를 넣지 않았다. 이러한 코드는 컨트롤러에 속한다. 컨트롤러는 버튼 클릭, 메뉴 선택, 텍스트 변경 등 모든 UI 이벤트를 처리한다. Monkeybars는 이런 작업을 조율하여 기본적으로 스윙 코드로부터 오는 모든 이벤트를 조용히 삼키도록 한다. 관심 있는 이벤트에 대해서만 처리자를 정의하면 된다. 본 예제 애플리케이션에서 관심 있는 이벤트는 메뉴 클릭이다.

이벤트 처리자는 형식은 다음과 같다.

    def your_component_name_action_performed
      # code
    end

(코드에서 필요하다면 실제 스윙 이벤트를 매개변수로 받는 처리자를 정의할 수도 있다.)

Quit 메뉴 아이템을 처리하려면, 그저 프로그램을 종료하면 된다.

  def quit_menu_item_action_performed
    java.lang.System.exit(0)
  end   

Edit 메뉴의 동작은 좀 더 작업이 필요하다.

  def edit_menu_item_action_performed
    if @editing
      @editing = false
      signal :end_edit
      update_card 
    else
      @editing = true
      signal :begin_edit
      update_model view_model, :current_card
    end
  end   

이 코드는 뷰를 제어하는 시그널을 사용하여 편집 모드를 켜고 끄는 작업을 수행한다. 요점은 컨트롤러의 모델 인스턴스와 view_model 메서드가 제공하는 뷰의 모델 사본을 써서 카드 텍스트를 옮기는 방식(시그널에 의해 뷰에 암묵적으로 전달)이다.

컨트롤러에서 사용자 인터페이스의 상태를 알고자 하면, view_state 메서드를 써서 뷰의 모델 사본과 현재 전송 객체를 참조할 수 있다. view_state에서 모델 사본을 얻는 것은 매우 일반적인 일이므로, Monkeybars는 view_model 메서드를 지원한다.

컨트롤러는 또한 초기 화면을 그리기 시작하라는 메서드와 보임/감춤 디스플레이 순서를 다루는 메서드를 갖는다. 이 두 메서드 모두 시그널을 써서 실제로 표현하는 코드를 뷰에 적용하도록 한다.




위로


애플리케이션 전체 조율하기

하나 또는 그 이상의 MVC 튜플과 더불어, Monkeybars 애플리케이션은 코드를 준비하고 실행하는 두 가지 주요 도우미 파일을 사용한다.

이 두 파일 모두 “src/” 디렉터리에 있다. manifest.rb 파일은 라이브러리 로딩 경로를 세팅하고 프로그램이 파일 시스템에서 직접 실행되거나 JAR 파일에서 실행되는지 여부에 따라 어떤 파일을 포함시킬 것인지를 정의한다.

앞서 redcloth.rb를 “lib/ruby/”에 추가했다. 애플리케이션에서 이 파일의 위치를 확인하려면, 이 디렉터리를 로딩 경로에 추가해야 한다. “lib/java” 디렉터리도 마찬가지다. 따라서 manifest.rb에 다음 두 줄이 반드시 포함되도록 하자.

 add_to_load_path "../lib/java"
 add_to_load_path "../lib/ruby"

또한 main.rb가 “src/”에 있다. 이는 애플리케이션에서 루비 진입점(entry point)이다. 이 파일에는 다른 것도 있을 수 있지만, 주로 글로벌 오류 처리자를 정의하고 메인 애플리케이션 로직을 수행하기 앞서 수행해야 하는 플랫폼 특화된 코드를 넣어 둔다.

예제 프로그램에서는 다음의 단순한 루프를 사용했다.

begin
  flash_card = FlashController.instance
  flash_card.init_view :flash_interval_seconds => 8,
                       :show_for_seconds => 20,
                       :window_height => 200,
                       :data_src => 'data/cards.rc'

  while true do
    flash_card.present 
  end

rescue => e
  show_error_dialog_and_exit(e)
end




위로


코드 실행하기

생성된 JAR 파일에 관한 몇 가지 함정

rawr:jar로 생성한 JAR 파일에는 프로그램 실행에 필요한 모든 정보가 들어 있지 않다. 특히, (jruby-complete.jar와 monkeybars-0.6.4.jar 같이) 프로그램에 필요한 라이브러리를 담은 다른 JAR의 경우 monkey_see.jar와 함께 번들로 포함되지 않는다.

rawr:jar를 실행할 때, 이것은 “package/deploy/” 아래에 몇 가지 파일을 생성한다. 이것들은 애플리케이션 실행에 필요한 파일이며, 함께 배포해야 한다. build_configuration.yaml 파일은 어떤 파일이 JAR에 있어야 하는지 어떤 파일과 디렉터리가 배포 디렉터리에 복사되어야 하는지 결정하도록 한다. 예를 들어, data/cards.rc 파일은 monkey_see.jar에 번들로 묶을 수 있다. 그러나 애플리케이션은 편집을 허용하므로, 이 파일을 외부 파일로 두어 변경된 사항을 다시 기록할 수 있어야 한다.

제자리에 코드와 적절한 데이터 파일이 있을 때만 프로그램을 실행할 수 있다. rawr rake 태스크를 써서 실행 가능한 JAR 파일을 생성하라. 프로젝트를 시작할 때 rawr install을 실행했는데, 이는 “src/org/rubyforge/rawr/” 아래 Main.java 파일을 생성한다. JAR에서 프로그램을 실행하려면 Main 자바 클래스가 필요한데 rawr이 이 파일을 생성한다. 이 클래스에는 main.rb 파일을 찾아 해석하는 기본 코드를 담고 있다(파일을 찾지 못하면, 인라인으로 파일을 생성하여 대신 사용한다).

raker rawr:jar 태스크는 이 코드를 컴파일하여 JAR로 파일을 패키징한다. build_configuration.yaml 파일은 이러한 작업을 조율한다. JAR를 생성하기 앞서, 이 파일을 편집하여 애플리케이션의 상세 정보가 반영되도록 하라.

프로그램을 실행하려면, 먼저 다음과 같이 JAR 파일을 만든다.

$ rake rawr:jar

그리고 다음과 같이 실행한다.

$ java -jar package/deploy/monkey_see.jar

플래시 카드 스크린이 상단 좌측에서부터 내려와서 잠시 머물다가 말려 올라가는 모습을 볼 수 있을 것이다.

창이 보이는 상태에서, 메뉴 아이템으로 현재 보이는 카드를 편집할 수 있다. 종료하려면, Quit 메뉴 아이템을 쓰면 된다(단축키를 정의했다면, Alt+Q를 누르면 된다).




위로


결론

전통적인 C 구현의 루비를 대신하여 안정적이면서도 발전적인 대안으로 JRuby를 개발한 것은 루비 GUI 툴킷이 C 기반 옵션을 넘어서 자바 플랫폼에서 가용한 UI 도구도 쓸 수 있음을 의미한다. 스윙은 자바 런타임 설치에 포함된 표준 영역이므로, (J)Ruby는 스윙 컴포넌트로 인해 성숙하고 완비된 그래픽 툴킷을 갖게 되었다. 자바 플랫폼을 사용한다는 것은 애플리케이션을 여러 플랫폼에서 개발하고, 패키징하고, 사용자에게 배포할 수 있음을 뜻한다. Monkeybars 라이브러리를 쓰면, 루비 개발자는 자바 세계의 향상된 개발 편의를 누리면서 복잡한 데스크톱 애플리케이션을 테스트, 유지 관리 가능한 수준으로 개발할 수 있다.

이 문서의 예제는 JRuby 스윙 GUI 개발에서 가능한 부분을 소개하기 위한 주 목적으로 작지만 섬세하게 취사 선택한 것이다. Monkeybars 사이트에는 더 많은 정보와 더 큰 덩치의 예제가 있다(참고자료).





위로


다운로드 하십시오

설명이름크기다운로드 방식
이 글의 예제 코드j-monkeybars.zip25KBHTTP
다운로드 방식에 대한 정보


참고자료

교육
  • Monkeybars: Monkeybars 라이브러리에 관한 API 문서, 튜토리얼, 예제

  • JRuby: JRuby 사이트를 방문해 보라.

  • "Ruby off the Rails"(Andrew Glover, developerWorks, 2005년 12월): Ruby on Rails 외에 자바 개발자가 루비로 할 수 있는 일에 대한 개요

  • FxRuby, WxRuby, QtRuby, GTK-Ruby, Shoes: 루비용 GUI 툴킷들에 대해 좀 더 알아보라.

  • ProfligacyCheri::Swing: 이 라이브러리는 루비와 스윙의 상호 작용을 좀 더 루비스럽게 해 주지만 복잡한 UI는 처리하지 못 한다.

  • 스윙: 스윙 패키지 문서를 살펴 보라.

  • 이 주제나 다른 기술적인 주제를 다루는 책을 기술 서점에서 찾아 보라.

  • developerWorks 자바 존: 자바 프로그래밍의 여러 관점에 대한 수백 가지의 자료


제품 및 기술 얻기
  • JRuby: jruby-complete.jar를 다운로드하라.

  • Monkeybars: 현재 Monkeybars 소스 코드를 다운로드하라.

  • IBM 제품 평가판을 다운로드해 DB2®, Lotus®, Rational®, Tivoli®, WebSphere®에서 제공하는 애플리케이션 개발 도구와 미들웨어 제품에 익숙해져 보라. .

토론


필자소개

James Britt

James Britt은 애리조나주 피닉스시에 위치한 루비 애플리케이션 개발사인 Happy Camper Studios의 사장이다. Ruby 개발자 그룹의 활발한 대변인으로 James는 미 국과 유럽에서 열린 Ruby 컨퍼런스에서 발표했으며 Dr. DobbsLinux Journal과 같 은 출판물에 수 차례 기술 문서를 기고하기도 하였다. Hal Fulton의 책인 The Ruby Way(2판)에서 웹 개발을 다룬 장은 거의 그가 썼다. James는 또한 Ruby 언어의 주요 문서 사 이트인 Ruby-doc.org 사이트를 제작하고 관리한다.




기사에 대한 평가


보다 나은 서비스를 제공하기 위함이오니 잠시 짬을 내어 이 양식을 제출하여 주십시오.



 


 


 


이 문서 북마킹 하기

mar.gar.in mar.gar.in naver naver eolin eolin del.icio.us del.icio.us





위로


DB2, IBM, Lotus, Rational, Tivoli, and WebSphere are trademarks of IBM Corporation in the United States, other countries, or both. Java and all Java-based trademarks are trademarks of Sun Microsystems, Inc. in the United States, other countries, or both. Microsoft, Windows, Windows NT, and the Windows logo are trademarks of Microsoft Corporation in the United States, other countries, or both. 기타 회사, 제품, 및 서비스명은 다른 상표나 서비스 마크일 수 있습니다.

developerWorks 콘텐트를 다른 사이트에 전재하기:
developerWorks 콘텐트에 대한 저작권은 IBM에 있습니다. IBM의 서면 허가나 원본 저자의 허락이 없이는 전재를 금합니다. 저희 콘텐트를 전재하시려면 IBM developerWorks 담당자 에게 문의하십시오.
    IBM 소개 개인정보 보호정책 문의