JNDIDBCP/DataSource의 사용법

JNDI등록을 통한 DBCP(Database Connection Pool)의 사용법을 간단히 알아보자

 

응용프로그램에서 데이터 베이스와 연결하는 커넥션을 생성하고 제거하는 작업은 많은자원을 소모한다. 만약 웹프로그램에서 다수의 사용자들이 접속할때마다 이커넥션을 생성하고 제거한다면 당신의 서버는 그부하를 견더낼수있을까? 이러한 문제를 해결해보고자 나온게 바로 DBCP이다.

 

먼저 이 DBCP사용을 위한 전체적인 구성을 한번 살펴보자  

사용자는 다음과 같은 요청을 통해 커넥션을 획득하고 데이터베이스에 접근하게 된다.

     사용자가 요청을 보낸다.

     사용자요청은 컨트롤영역을 거쳐 모델영역을 호출하게된다.

     모델영역으로 넘어간 요청은 JNDI에 등록된 데이터베이스정보(DBCP정보)객체

(Type:DataSource)를 검색한다.

     JNDI를 통해 찾은 정보객체(Type:DataSource)로부터 커넥션을 획득한다.

     데이터베이스 조작 작업이 끝난 후 획득한 커넥션을 다시 반환시킨다.

 

이제 JNDI등록을 통한 DBCP사용방법에 대해 자세히 알아보자 그전에 DBCP사용을 위한 환경설정이 되어있는지 살펴봐야 한다. 가장 중요한 것은 웹 컨테이너의 설치여부이다.

참고로 이글은 톰캣5.5x를 기준으로 설명한다. 또한 여기서 사용되는 DBCP는 자카르타DBCP API  사용하여 설명한다. 먼저 자카르타 DBCP를 사용하기 위해서는 별도의 jar파일이 필요하다.

다음의 jar파일을 다운받아서 webapps/WEB-INF/lib에 붙여 넣자 

 Commons-dbcp-1.2.1.jar

Commons-collections-3.1.jar

Commons-pool-1.2.jar

 자 이것으로 DBCP사용을 위한 API설치는 끝이다 이제 사용에 관한 정보설정에 들어가보자

(사실 위의 파일을 설치하지 않아도 톰캣5.X버전 이후부터는common/libnaming-factory-dbcp.jar이라는 하나의 라이브러리로 제공하고 있다고 한다. 한번 사용해보는 것도 권장한다.)

 

DBCP 사용에 관한 정보설정

가장먼저 JNDI서비스를 이용하기 위해 네임등록을 해야한다. Tomcat/conf/server.xml파일을 에디터로 열어보자

<!-- Global JNDI resources -->

  <GlobalNamingResources>

           …. 

</GlobalNamingResources>

태그가 보일것이다. 이태그의 사이에 우리가 사용할 DBCP에 대한 정보와 네임을 등록시킨다.

<Resource name="jdbc/OracleCp" //네임스페이스에 등록될 객체의 이름

                        auth="Container" //자원관리자

                        type="javax.sql.DataSource" //(반환 객체타입)

                        driverClassName="oracle.jdbc.driver.OracleDriver"//DB드라이버 클래스

                        loginTimeout="10"

                        maxWait="10"

                        username="scott"

                        password="tiger"

                        testOnBorrow="true"

                        url="jdbc:oracle:thin:@localhost:1521:ora9i" //데이터베이스의 위치정보

     />

여기서 중요한 것은 nametype 그리고 driverClassNameurl이라고 할수있다. Name은 실제로 JNDI서비스를 이용해 객체를 찾을 때 사용하는 이름이며 Type은 객체를 찾아 반환받는 타입이다.

driverClassNameurlDBMS와 그위치에 따라 바뀌는 것이므로 알아두자

이제는 DBCP를 웹애플리케이션에서 사용하기위해 배포서술자(web.xml)에 위와 똑같이 DBCP에대한 정보를 저장해야한다. 또한 톰캣5.X이상에서는 자원의 사용을 위해 별도의 Context.xml파일을 생성해 자원정보를 기술해야한다.(여기서 Context란 웹애플리케이션의 최상위 디렉토리명을 뜻한다. ) 만약 톰캣4.x를 사용한다면 별도의 Context.xml파일은 필요치않지만 대신 Tomcat/conf/

server.xml 파일에 컨텍스트정보와 함께 자원정보를 등록해야한다. 자원정보의 등록은 <context></context>태그를 통해 가능하며 <host></host>태그사이에 등록하도록한다.

Web.xml

<resource-ref>

  <description>Oracle Datasource</description>

  <res-ref-name>jdbc/OracleCP</res-ref-name>

  <res-type>javax.sql.DataSource</res-type>

  <res-auth>Container</res-auth>

  </resource-ref>

Tomcat5.x  위치:Tomcat/conf/Catalina/localhost/context.xml

<Context docBase="${catalina.home}/webapps/contextRootDirectory"

         privileged="true" antiResourceLocking="false" antiJARLocking="false">

      <Resource name="jdbc/OracleCP"

                        auth="Container"

                        type="javax.sql.DataSource"

                        driverClassName="oracle.jdbc.driver.OracleDriver"

                        loginTimeout="10"

                        maxWait="10"

                        username="scott"

                        password="tiger"

                        testOnBorrow="true"

                        url="jdbc:oracle:thin:@localhost:1521:ora9i"

     />

 

</Context>

Tomcat4.x  위치:Tomcat/conf/server.xml

<HOST>

<Context docBase="${catalina.home}/webapps/contextRootDirectory"

         privileged="true" antiResourceLocking="false" antiJARLocking="false">

      <Resource name="jdbc/OracleCP"

                        auth="Container"

                        type="javax.sql.DataSource"

                        driverClassName="oracle.jdbc.driver.OracleDriver"

                        loginTimeout="10"

                        maxWait="10"

                        username="scott"

                        password="tiger"

                        testOnBorrow="true"

                        url="jdbc:oracle:thin:@localhost:1521:ora9i"

     />

 

</Context>

</HOST>

이제 DBCP사용을 위한 설치 및 모든 설정이 끝이 났다.

그럼 이것을 어떻게 사용하느냐? 쉽다. 그냥 아래의 코드를 외우자

private javax.sql.DataSource cp;

 

try{

javax.naming.Context ctx = new InitialContext();//네이밍조작을 위한 초기 컨텍스트 반환

cp = (DataSource) ctx.lookup("java:/comp/env/jdbc/OracleCP");//등록된 이름의 자원 반환

}catch(Exception e){

  e.printStackTrace();

                     }

Connection con=cp.getConnection();//커넥션 얻어옴

 위의 컨텍스트는 네이밍 서비스를 이용하기 위해 바인딩 정보를 가지고 있는 컨텍스트 객체이다.

이 컨텍스트 객체를 통해 이름으로 등록된 DBCP객체를 가져올수있다. 그러면 위의 설정에서 명시한대로 DataSource타입으로 객체가 반환된다.

여기서 주의해서 볼 것은 커넥션풀을 이용해 커넥션을 가져오는데 DriverManager클래스를 이용한게 아니라 DataSource클래스를 통해 가져왔다는것이다. DataSource를 이용하는 이유는? API를 보자 잘나와있다.(API는 이럴 때 사용하라고 있는거다.)

마지막으로"java:/comp/env/jdbc/OracleCP"의 부분이다. 여기서 우리가 설정시 지정해준 이름은

jdbc/OracleCP이다 그럼 java:/comp/env/? 요건 JNDI사용시 등록되는 자원들이 놓이게되는 가상공간(Name Space)이라고 하더라 그냥 접두어같이 외워서 쓰자 


출처 -  http://blog.naver.com/ceresrits?Redirect=Log&logNo=40062891265 


==================================================================================

제가 아는 한도 내에서 설명을 드리겠습니다. 틀릴 수 있으니 참조 정도로만 하시고 여러 책과 인터넷 상의 데이터를 모아 공부하시면 좋을 듯 합니다.

우선 DataSource는 데이터베이스에 접근하기 위한 하나의 표준 인터페이스로 등장했고 특히 DBCP(Database Connection Pooling)를 이용하기 위한 표준 방식이라 보시면 될 듯 합니다.

그래서 JNDI(Java Naming and Directory Interface)를 통해 해당 객체에 접근할 수 있어 DataSource 객체를 가져옵니다.

그런 다음 java.sql.Connection 객체를 얻어와서 일반적으로 JDBC 코딩 하듯이 사용하면 됩니다.

DataSource가 나오기 전에는 수많은 DBCP 구현체가 나와서 소스 간 가독성 등등이 부족했었는데 이 표준 방식을 사용하면 매우 편하게 이해할 수 있었겠죠.

보통 WAS는 자체 구현한 DataSource 구현체가 있으므로 해당 JDBC 드라이버만 적절하게 등록해주면 사용할 수 있습니다.

Tomcat과 같은 JSP/Servlet Container는 같은 자카르타 프로젝트 중 하나인 Commons 중 DBCP 패키지가 있으니 이를 이용하면 됩니다.

설정 방법은 지식인에서 뒤져보시면 많이 나오니까 그걸 참조하면 될 겁니다.

그럼 해당 소스 부분만 설명해 드리죠.


import javax.naming.*;
import javax.sql.*;
Import java.sql.*;

... 중략

Context ctx = new InitialContext(); // (1)
DataSource ds =
(DataSource) ctx.lookup("java:/comp/env/jdbc/mydbtest"); // (2)
Connection conn = ds.getConnection();

... 중략 

conn.close();


위는 자바 소스 상에서의 사용법입니다.

(1)과 (2)는 JNDI에서 등록된 객체나 서비스를 이름으로 가져오는 부분의 코드입니다.

이쪽은 따로 공부해주시면 좋은데 잘 모르겠거든, 그냥 설정에서 DataSource 설정에 부여한 이름을 사용한다 정도로만 생각해 주시면 되겠군요.

그 뒤는 동일합니다. 그리 복잡할 내용이 아니니 생략합니다.

핵심은 DataSource 설정과 JNDI를 통한 객체의 획득입니다.

그러니 기본적으로 DBCP 기능이 있다고 봐도 되지만, 구현에 따라 무늬만 DBCP인 듯 보이는 경우도 있으니 관련 패키지의 문서를 주의 깊게 읽어보시고 사용 결정을 하시면 될 듯 싶네요.

그럼 조금이라도 도움이 되셨길... 


출처 - 네이버 지식인
Posted by linuxism
,

다이나믹웹어플 소스레벨에서 데이터소스을 등록하는법

 

톰켓의 경우 원래 저 경로로 데이터소스을 잡는걸 추천한다고 한다.

플젝을 톰켓에 올릴때보면 wtp의 런타임 서버의 설정의

해당 context에 자동으로 저 파일의 내역이 기술된것을 확인 할 수 있다

주의할것은 웹서버 레벨에서 데이터소스을 생성하는것이 아니기에

작성 문법에 주의할것 톰켓사이트의 예제을 사용하면 가장 이상적이다

작성문법  때문에 모든 설정을 다 잡고도 해맸다

예로들 어 오라클의 로컬서버의 경우 /META-INF/context.xml 의 내용은

<?xml version="1.0" encoding="UTF-8"?>

<Context>

 <Resource name="jdbc/oracle_db" auth="Container"

              type="javax.sql.DataSource" driverClassName="oracle.jdbc.OracleDriver"

              url="jdbc:oracle:thin:@localhost:1521:ORCL"

              username="scott" password="tiger" maxActive="20" maxIdle="10"

              maxWait="-1"/> 

 </Context>

 이런식이 될듯하다


출처 -  http://blog.naver.com/tomatogun?Redirect=Log&logNo=100121972600

'IDE & Build > Eclipse' 카테고리의 다른 글

이클립스 Access restriction 해결 방법  (0) 2012.02.25
이클립스 JSP 한글깨짐  (0) 2012.01.26
이클립스 설치  (0) 2012.01.19
Eclipse 단축키  (0) 2011.01.03
Eclipse에서 Servlet 및 JSP 개발 환경 설정  (0) 2010.11.18
Posted by linuxism
,

7.1 인터페이스란?

인터페이스는 일종의 추상클래스이다. 인터페이스는 추상클래스처럼 추상메서드를 갖지만 추상클래스보다 추상화 정도가 높아서 추상클래스와 달리 몸통을 갖춘 일반 메서드 또는 멤버변수를 구성원으로 가질 수 없다. 
오직 추상메서드와 상수만을 멤버로 가질 수 있으며, 그 외의 다른 어떠한 요소도 허용하지 않는다. 
추상클래스를 부분적으로만 완성된 '미완성 설계도'라고 한다면, 인터페이스는 구현된 것은 아무 것도 없고 밑그림만 그려져 있는 '기본 설계도'라 할 수 있다. 

추상클래스처럼 인터페이스도 완성되지 않은 불완전한 것이기 때문에 그 자체만으로 사용되기 보다는 다른 클래스를 작성하는데 도움 줄 목적으로 작성된다. 



7.2 인터페이스의 작성

인터페이스를 작성하는 것은 클래스를 작성하는 것과 같다. 다만 키워드로 class 대신 interface를 사용한다는 것만 다르다. 그리고 interface에도 클래스와 같이 접근제어자로 public 또는 default를 사용할 수 있다. 


interface 인터페이스이름 { 
      public static final 타입 상수이름 = 값; 
      public abstract 메서드이름(매개변수목록); 



일반적인 클래스의 멤버들과 달리 인터페이스의 멤버들은 다음과 같은 제약사항을 가지고 있다. 


- 모든 멤버변수는 public static final 이어야 하며, 이를 생략할 수 있다. 
- 모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 있다. 

[참고]인터페이스에 정의된 모든 멤버에 예외없이 적용되는 사항이기 때문에 제어자를 생략할 수 있는 것이며, 편의상 생략하는 경우가 많다. 생략된 제어자는 컴파일시에 컴파일러가 자동적으로 추가해준다.



interface PlayingCard { 
      public static final int SPADE = 4; 
      final int DIAMOND = 3;             // public static final int DIAMOND=3; 
      static int HEART = 2;             // public static final int HEART=2; 
      int CLOVER = 1;                  // public static final int CLOVER=1; 

      public abstract String getCardNumber(); 
      String getCardKind();       // public abstract String getCardKind();로 간주된다. 






7.3 인터페이스의 상속

interface는 클래스가 아닌 인터페이스로부터만 상속받을 수 있으며, 클래스와는 달리 다중상속, 즉 여러 개의 인터페이스로부터 상속을 받는 것이 가능하다. 
[참고]클래스와는 달리 Object클래스와 같은 모든 인터페이스의 최고조상은 없다.



interface Movable { 
      /** 지정된 위치(x, y)로 이동하는 기능의 메서드 */ 
      void move(int x, int y);       


interface Attackable { 
      /** 지정된 대상(u)을 공격하는 기능의 메서드 */ 
      void attack(Unit u); 


interface Fightable extends Movable, Attackable { } 


클래스에서의 상속과 마찬가지로 자손인터페이스(Fightable)은 조상인터페이스(Movable, Attackable)에 정의된 멤버를 모두 상속받는다. 
그래서 Fightable자체에는 정의된 멤버가 하나도 없지만 조상인터페이스로 부터 상속받은 두 개의 추상메서드, move(int x, int y)와 attack(Unit u)를 멤버로 갖게 된다. 




7.4 인터페이스의 구현

추상클래스에서와 같이 인터페이스의 추상메서드를 몸통을 구현하는 자손클래스를 작성해야한다. 이때는 인터페이스를 '구현(implement)한다'고 하며 키워드 implements를 사용한다. 


class 클래스이름 implements 인터페이스이름 { 
      // 인터페이스에 정의된 추상메서드를 구현해야한다. 


class Fighter implements Fightable { 
      public void move() { /* 내용 생략*/ } 
      public void attack() { /* 내용 생략*/ } 


[참고]이 때 'Fighter클래스는 Fightable인터페이스를 구현한다.'라고 한다.

만일 구현하는 인터페이스의 메서드 중 일부만 구현한다면, 추상클래스로 선언되어야 한다. 


abstract class Fighter implements Fightable { 
     public void move() { /* 내용 생략*/ } 



그리고 다음과 같이 상속과 구현을 동시에 할 수도 있다. 


class Fighter extends Unit implements Fightable { 
      public void move(int x, int y) { /* 내용 생략 */
      public void attack(Unit u) { /* 내용 생략 */


[참고]인터페이스의 이름에는 주로 Fightable과 같이 '~ 를 할 수 있는'의 의미인 'able'로 끝나는 것들이 많은데, 그 이유는 어떠한 기능 또는 행위를 하는데 필요한 메서드를 제공한다는 의미를 강조하기 위해서이다. 또한 그 인터페이스를 구현한 클래스는 '~를 할 수 있는' 능력을 갖추었다는 의미이기도 하다. 
이름이 'able'로 끝나는 것은 인터페이스라고 쉽게 추측할 수 있지만, 모든 인터페이스의 이름이 반드시 'able'로 끝나야 하는 것은 아니다.


[예제7-23] FighterTest.java

class FighterTest 

      public static void main(String[] args) 
      { 
            Fighter f = new Fighter(); 

            if (f instanceof Unit)       {             
                  System.out.println("f는 Unit클래스의 자손입니다."); 
            } 
            if (f instanceof Fightable) {       
                  System.out.println("f는 Fightable인터페이스를 구현했습니다."); 
            } 
            if (f instanceof Movable) {             
                  System.out.println("f는 Movable인터페이스를 구현했습니다."); 
            } 
            if (f instanceof Attackable) {       
                  System.out.println("f는 Attackable인터페이스를 구현했습니다."); 
            } 
            if (f instanceof Object) {             
                  System.out.println("f는 Object클래스의 자손입니다."); 
            } 
      } 


class Fighter extends Unit implements Fightable { 
      public void move(int x, int y) { /* 내용 생략 */
      public void attack(Unit u) { /* 내용 생략 */


class Unit { 
int currentHP;             // 유닛의 체력 
int x;                   // 유닛의 위치(x좌표) 
int y;                   // 유닛의 위치(y좌표) 

interface Fightable extends Movable, Attackable { } 
interface Movable {       void move(int x, int y);       } 
interface Attackable {       void attack(Unit u); } 

[실행결과]
f는 Unit클래스의 자손입니다. 
f는 Fightable인터페이스를 구현했습니다. 
f는 Movable인터페이스를 구현했습니다. 
f는 Attackable인터페이스를 구현했습니다. 
f는 Object클래스의 자손입니다. 

예제에 사용된 클래스와 인터페이스간의 관계를 그려보면 다음과 같다. 

 

실제로 Fighter클래스는 Unit클래스로부터 상속받고 Fightable인터페이스만을 구현했지만, Unit클래스는 Object클래스의 자손이고, Fightable인터페이스는 Movable과 Attackable인터페이스의 자손이므로 Fighter클래스는 이 모든 클래스와 인터페이스의 자손이 되는 셈이다. 
인터페이스는 상속 대신 구현이라는 용어를 사용하지만, 인터페이스로부터 상속받은 추상메서드를 구현하는 것이기 때문에 인터페이스도 조금은 다른 의미의 조상이라고 할 수 있다. 

여기서 주의 깊게 봐두어야 할 것은 Movable인터페이스에 정의된 void move(int x, int y)를 Fighter클래스에서 구현할 때 접근제어자를 public으로 했다는 것이다. 


interface Movable { 
      void move(int x, int y);       


class Fighter extends Unit implements Fightable { 
      public void move(int x, int y) { /* 실제구현내용 생략 */
      public void attack(Unit u) { /* 실제구현내용 생략 */



오버라이딩을 할 때는 조상의 메서드보다 넓은 범위의 접근제어자를 지정해야한다는 것을 기억할 것이다. Movable인터페이스에 void move(int x, int y)와 같이 정의되어 있지만 사실public abstract가 생략된 것이기 때문에 실제로 public abstract void move(int x, int y)이다. 
따라서 이를 구현하는 Fighter클래스에서는 void move(int x, int y)의 접근제어자를 반드시 public으로 해야 하는 것이다.
 




7.5 인터페이스를 이용한 다중상속

두 조상으로부터 상속받는 멤버 중에서 멤버변수의 이름이 같거나 메서드의 선언부가 일치하고 구현 내용이 다르다면 이 두 조상으로부터 상속받는 자손클래스는 어느 조상의 것을 상속받게 되는 것인지 알 수 없다. 어느 한 쪽으로부터의 상속을 포기하던가, 이름이 충돌하지 않도록 조상클래스를 변경하는 수 밖에는 없다. 

자바에서는 이러한 충돌문제를 해결하기 위해서 단일 상속만을 허용하고, 인터페이스를 이용해서 단일 상속의 단점을 보완하도록 하였다. 

인터페이스는 상수만을 정의할 수 있으므로 조상클래스의 멤버변수와 충돌하는 경우는 극히 드물고 추상메서드는 구현내용이 전혀 없으므로 조상클래스의 메서드와 선언부가 일치하는 경우에는 당연히 조상클래스 쪽의 메서드를 상속받으면 되므로 문제되지 않는다. 
이렇게 해서 상속받는 멤버의 충돌은 피할 수 있지만, 다중상속의 장점을 잃게 된다는 것이 아쉽다. 

만일 두 개의 클래스로부터 상속을 받아야 할 상황이라면, 두 조상클래스 중에서 비중이 높은 쪽을 선택하고 다른 한쪽은 클래스 내부에 멤버로 포함시키는 방식으로 처리하거나 어느 한쪽을 필요한 부분을 뽑아서 인터페이스로 만든 다음 구현하도록 한다. 

다음과 같이 Tv클래스와 VCR클래스가 있을 때, TVCR클래스를 작성하기 위해 두 클래스로부터 상속을 받을 수만 있으면 좋겠지만 다중상속을 허용하지 않으므로, 한 쪽만 선택하여 상속받고 나머지 한 쪽은 클래스 내에 포함시켜서 내부적으로 인스턴스를 생성해서 사용하도록 한다. 


public class Tv { 
      protected boolean power; 
      protected int channel; 
      protected int volume; 
      public void power() { power = ! power;} 
      public void channelUp() { channel++;} 
      public void channelDown() { channel--;} 
      public void volumeUp() { volume++;} 
      public void volumeDown() { volume--;} 


public class VCR { 
      protected int counter;             // VCR의 카운터 
      public void play() { 
            // Tape을 재생한다. 
      } 
      public void stop() { 
            // 재생을 멈춘다. 
      } 
     public void reset() {             
           counter =0; 
     } 
     public int getCounter() { 
           return counter; 
     } 
     public void      setCounter(int c) { 
           counter = c; 
     } 



VCR클래스에 정의된 메서드와 일치하는 추상메서드를 갖는 인터페이스를 작성한다. 


public interface IVCR { 
      public void play(); 
      public void stop(); 
      public void reset(); 
     public int getCounter(); 
     public void setCounter(int c); 



이제 IVCR 인터페이스를 구현하고 Tv클래스로부터 상속받는 TVCR클래스를 작성한다. 이때 VCR클래스 타입의 참조변수를 멤버변수로 선언하여 IVCR인터페이스의 추상메서드를 구현하는데 사용한다. 


public class TVCR extends Tv implements IVCR { 
      VCR vcr = new VCR();       
      public void play() { 
            vcr.play();   // 코드를 작성하는 대신 VCR인스턴스의 메서드를 호출하면 된다. 
      } 
      public void stop() { 
            vcr.stop(); 
      } 
      public void reset() { 
           vcr.reset(); 
     } 
     public int getCounter() { 
          return vcr.getCounter(); 
     } 
     public void setCounter(int c) { 
          vcr.setCounter(c); 
     } 


IVCR인터페이스를 구현하기 위해서는 새로 메서드를 작성해야하는 부담이 있지만 이처럼 VCR클래스의 인스턴스를 사용하면 손쉽게 다중상속을 구현할 수 있다. 
또한 VCR클래스의 내용이 변경되어도 변경된 내용이 TVCR클래스에도 자동적으로 반영되는 효과도 얻을 수 있다. 

사실 인터페이스를 새로 작성하지 않고도 VCR클래스를 TVCR클래스에 포함시키는 것만으로도 충분하지만, 인터페이스를 이용하면 다형적 특성을 이용할 수 있다는 장점이 있다. 




7.6 인터페이스를 이용한 다형성

다형성에 대해 학습할 때 자손클래스의 인스턴스를 조상타입의 참조변수로 참조하는 것이 가능하다는 것을 배웠다. 
인터페이스 역시 이를 구현한 클래스의 조상이라 할 수 있으므로 해당 인터페이스 타입의 참조변수로 이를 구현한 클래스의 인스턴스를 참조할 수 있으며, 인터페이스 타입으로의 형변환도 가능하다. 
인터페이스 Fightable을 클래스 Fighter가 구현했을 때, 다음과 같이 Fighter인스턴스를 Fightable타입의 참조변수로 참조하는 것이 가능하다. 


Fightable f = (Fightable)new Fighter(); 
또는 
Fightable f = new Fighter(); 

[참고]물론 Fightable타입의 참조변수로는 인터페이스 Fightable에 정의된 멤버들만 호출이 가능하다.

따라서 인터페이스는 다음과 같이 메서드의 매개변수의 타입으로 사용될 수 있다. 


void attack(Fightable f) { 
     //... 



인터페이스 타입의 매개변수가 갖는 의미는 메서드 호출 시 해당 인터페이스를 구현한 클래스의 인스턴스를 매개변수로 제공해야한다는 것이다.
그래서 attack메서드를 호출할 때는 매개변수로 Fightable인터페이스를 구현한 클래스의 인스턴스를 넘겨주어야 한다. 


class Fighter extends Unit implements Fightable { 
      public void move(int x, int y) { /* 내용 생략 */
      public void attack(Fightable f) { /* 내용 생략 */


위와 같이 Fightable인터페이스를 구현한 Fighter클래스가 있을 때, attack메서드의 매개변수로 Fighter인스턴스를 넘겨 줄 수 있다. 즉, attack(new Fighter())와 같이 할 수 있다는 것이다. 

그리고 다음과 같이 메서드의 리턴타입으로 인터페이스의 타입을 지정하는 것 역시 가능하다. 


Fightable method() { 
     // ... 
     return new Fighter(); 



리턴타입이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미한다. 
위의 코드에서는 method()의 리턴타입이 Fightable인터페이스기 때문에 메서드의 return문에서Fightable인터페이스를 구현한 Fighter클래스의 인스턴스를 반환한다.
 

[예제7-24] ParserTest.java

class ParserTest { 
      public static void main(String args[]) { 
            Parseable parser = ParserManager.getParser("XML"); 
            parser.parse("document.xml"); 
            parser = ParserManager.getParser("HTML"); 
            parser.parse("document2.html"); 
      } 


interface Parseable { 
      public abstract void parse(String fileName);       // 구문 분석작업을 수행한다. 


class ParserManager { 
     // 리턴타입이 Parseable인터페이스이다. 
      public static Parseable getParser(String type) {       
            if(type.equals("XML")) { 
               return new XMLParser(); 
            } else { 
               Parseable p = new HTMLParser(); 
               return p; 
               // return new HTMLParser();       위의 두 줄을 간단히 할 수 있다. 
            } 
      } 


class XMLParser implements Parseable { 
      public void parse(String fileName) { 
          /* 구문 분석작업을 수행하는 코드를 적는다. */ 
            System.out.println(fileName + " - XML parsing completed."); 
      } 


class HTMLParser implements Parseable { 
      public void parse(String fileName) { 
          /* 구문 분석작업을 수행하는 코드를 적는다. */ 
            System.out.println(fileName + " - HTML parsing completed."); 
      } 

[실행결과]
document.xml - XML parsing completed. 
document2.html - HTML parsing completed. 

Parseable인터페이스는 구문분석(parsing)을 수행하는 기능을 구현할 목적으로 추상메서드 parse(String fileName)를 정의했다. 그리고 XMLParser클래스와 HTMLParser클래스는 Parseable인터페이스를 구현하였다. 
ParserManager클래스의 getParser메서드는 매개변수로 넘겨받는 type의 값에 따라 XMLParser인스턴스 또는 HTMLParser인스턴스를 반환한다. 

 

getParser메서드의 수행결과로 참조변수 parser는 XMLParser인스턴스의 주소값을 갖게 된다. 마치 Parseable parser = new XMLParser();이 수행된 것과 같다. 


parser.parse("document.xml"); 


참조변수 parser를 통해 parse()를 호출하면, parser가 참조하고 있는 XMLParser인스턴스의 parse메서드가 호출된다. 

만일 나중에 새로운 종류의 XML구문분석기 NewXMLPaser클래스가 나와도 ParserTest클래스는 변경할 필요없이 ParserManager클래스의 getParser메서드에서 return new XMLParser(); 대신 returnnew NewXMLParser();로 변경하기만 하면 된다. 

이러한 장점은 특히 분산환경 프로그래밍에서 그 위력을 발휘한다. 사용자 컴퓨터에 설치된 프로그램을 변경하지 않고, 서버측의 변경만으로도 사용자가 새로 개정된 프로그램을 사용하는 것이 가능하다. 




7.7 인터페이스의 장점

인터페이스를 사용하는 이유와 그 장점을 정리해 보면 다음과 같다. 


- 개발시간을 단축시킬 수 있다. 
- 표준화가 가능하다. 
- 서로 관계없는 클래스들에게 관계를 맺어 줄 수 있다. 
- 독립적인 프로그래밍이 가능하다. 


1. 개발시간을 단축시킬 수 있다.
일단 인터페이스가 작성되면, 이를 사용해서 프로그램을 작성하는 것이 가능하다. 메서드를 호출하는 쪽에서는 메서드의 내용에 관계없이 선언부만 알면 되기 때문이다. 
그리고 동시에 다른 한 쪽에서는 인터페이스를 구현하는 클래스를 작성하도록 하여, 인터페이스를 구현하는 클래스가 작성될 때까지 기다리지 않고도 양쪽에서 동시에 개발을 진행할 수 있다. 

2. 표준화가 가능하다.
프로젝트에 사용되는 기본 틀을 인터페이스로 작성한 다음 개발자들에게 인터페이스를 구현하여 프로그램을 작성하도록 함으로써 보다 일관되고 정형화된 프로그램의 개발이 가능하다. 

3. 서로 관계없는 클래스들에게 관계를 맺어 줄 수 있다.
서로 상속관계에 있지도 않고, 같은 조상클래스를 가지고 있지 않은 서로 아무런 관계도 없는 클래스들에게 하나의 인터페이스를 공통적으로 구현하도록 함으로써 관계를 맺어 줄 수 있다. 

4. 독립적인 프로그래밍이 가능하다.
인터페이스를 이용하면 클래스의 선언과 구현을 분리 시킬 수 있기 때문에 실제구현에 독립적인 프로그램을 작성하는 것이 가능하다. 클래스와 클래스간의 직접적인 관계를 인터페이스를 이용해서 간접적인 관계로 변경하면, 한 클래스의 변경이 관련된 다른 클래스에 영향을 미치지 않는 독립적인 프로그래밍이 가능하다. 
예를 들어 한 데이터베이스 회사가 제공하는 특정 데이터베이스를 사용하는데 필요한 클래스를 사용해서 프로그램을 작성했다면 이 프로그램은 다른 종류의 데이터베이스를 사용하기 위해서는 전체 프로그램 중에서 데이터베이스 관련된 부분은 모두 변경해야할 것이다. 

그러나 데이터베이스 관련 인터페이스를 정의하고 이를 이용해서 프로그램을 작성하면, 데이터베이스의 종류가 변경되더라도 프로그램을 변경하지 않도록 할 수 있다. 

단, 데이터베이스 회사에서 제공하는 클래스도 인터페이스를 구현하도록 요구해야한다. 데이터베이스를 이용한 응용프로그램을 작성하는 쪽에서는 인터페이스를 이용해서 프로그램을 작성하고, 데이터베이스 회사에서는 인터페이스를 구현한 클래스를 작성해서 제공해야한다.

실제로 자바에는 다수의 데이터베이스 관련 인터페이스를 제공하고 있으며, 프로그래머는 이 인터페이스를 이용해서 프로그래밍을 하면 특정 데이터베이스에 종속되지 않는 프로그램을 작성할 수 있다. 

게임 스타크래프드에 나오는 유닛을 클래스로 표현하고 이들간의 관계를 상속계층도로 표현해 보았다.

 

게임에 나오는 모든 유닛들의 최고 조상은 Unit클래스이고 유닛의 종류는 지상유닛(GoundUnit)과 공중유닛(AirUnit)으로 나뉘어진다.
그리고 지상유닛에는 Marine, SCV, Tank가 있고, 공중유닛으로는 DropShip이 있다. SCV에게 Tank와 DropShip과 같은 기계화 유닛을 수리할 수 있는 기능을 제공하기 위해 repair메서드를 정의한다면 다음과 같을 것이다. 


void repair(Tank t) { 
      // Tank를 수리한다. 


void repair(DropShip d) { 
      // DropShip을 수리한다. 
}


이런 식으로 수리가 가능한 유닛의 개수 만큼 다른 버젼의 오버로딩된 메서드를 정의해야할 것이다.

이것을 피하기 위해 매개변수의 타입을 이 들의 공통 조상으로 하면 좋겠지만 DropShip은 공통조상이 다르기 때문에 공통조상의 타입으로 메서드를 정의한다고 해도 최소한 2개의 메서드가 필요할 것이다.



void repair(GroundUnit gu) { 
      // 매개변수로 넘겨진 지상유닛(GroundUnit)을 수리한다. 


void repair(AirUnit au) { 
      // 매개변수로 넘겨진 공중유닛(AirUnit)을 수리한다. 
}


그리고 GroundUnit의 자손 중에는 Marine과 같이 기계화 유닛이 아닌 클래스도 포함될 수 있기 때문에 repair메서드의 매개변수 타입으로 GroundUnit은 부적합하다.
현재의 상속관계에서는 이들의 공통점은 없다. 이 때 인터페이스를 이용하면 기존의 상속체계를 유지하면서 이들 기계화 유닛에 공통점을 부여할 수 있다.
다음과 같이 Repairable이라는 인터페이스를 정의하고 수리가 가능한 기계화 유닛에게 이 인터페이스를 구현하도록 하면 된다.


interface Repairable {} 

class SCV extends GroundUnit implements Repairable { 
      //... 


class Tank extends GroundUnit implements Repairable 
      //... 


class DropShip extends AirUnit implements Repairable { 
      //... 
}


이제 이 세 개의 클래스에는 같은 인터페이스를 구현했다는 공통점이 생겼다. 인터페이스 Repairable에 정의된 것은 아무것도 없고, 단지 인스턴스의 타입체크에 사용된다.
Repairable인터페이스를 중심으로 상속계층도를 그려보면 다음과 같다.



그리고, repair메서드의 매개변수의 타입을 Repairable로 선언하면, 이 메서드의 매개변수로 Repairable인터페이스를 구현한 클래스의 인스턴스만 받아들여질 것이다.


void repair(Repairable r) { 
      // 매개변수로 넘겨받은 유닛을 수리한다. 
}


앞으로 새로운 클래스가 추가될 때, SCV의 repair메서드에 의해서 수리가 가능하도록 하려면 Repairable인터페이스를 구현하도록 하면 될 것이다. 

[예제7-25] RepairableTest.java

class RepairableTest 

      public static void main(String[] args) 
      { 
            Tank t = new Tank(); 
            DropShip d = new DropShip(); 

            Marine m = new Marine(); 
            SCV       s = new SCV(); 

            s.repair(t);       // SCV가 Tank를 수리하도록 한다. 
            s.repair(d); 
//             s.repair(m);       에러발생 : repair(Repairable) in SCV cannot be applied to (Marine) 
      } 


interface Repairable {} 
class GroundUnit extends Unit { 
      GroundUnit(int hp) { 
            super(hp); 
      } 


class AirUnit extends Unit { 
      AirUnit(int hp) { 
            super(hp); 
      } 


class Unit { 
      int hitPoint; 
      final int MAX_HP; 
      Unit(int hp) { 
            MAX_HP = hp; 
      } 
      //... 


class Tank extends GroundUnit implements Repairable { 
      Tank() { 
            super(150);             // Tank의 HP는 150이다. 
            hitPoint = MAX_HP; 
      } 

      public String toString() { 
            return "Tank"
      } 
      //... 


class DropShip extends AirUnit implements Repairable { 
      DropShip() { 
            super(125);             // Dropship의 HP는 125이다. 
            hitPoint = MAX_HP; 
      } 

      public String toString() { 
            return "DropShip"
      } 
      //... 


class Marine extends GroundUnit { 
      Marine() { 
            super(40); 
            hitPoint = MAX_HP; 
      } 
      //... 



class SCV extends GroundUnit implements Repairable{ 
      SCV() { 
            super(60); 
            hitPoint = MAX_HP; 
      } 

      void repair(Repairable r) { 
            if (r instanceof Unit) { 
                  Unit u = (Unit)r; 
                  while(u.hitPoint!=u.MAX_HP) { 
                        /* Unit의 HP를 증가시킨다. */ 
                        u.hitPoint++; 
                  } 
                  System.out.println( u.toString() + "의 수리가 끝났습니다."); 
            } 
      }       
      //... 
}
[실행결과]
Tank의 수리가 끝났습니다.
DropShip의 수리가 끝났습니다.


repair메서드의 매개변수 r은 Repairable타입이기 때문에 인터페이스 Repairable에 정의된 멤버만 사용할 수 있다.
그러나 Repairable에는 정의된 멤버가 없으므로 이 타입의 참조변수로는 할 수 있는 일은 아무 것도 없다.
그래서 instanceof연산자로 타입을 체크한 뒤 캐스팅하여 Unit클래스에 정의된 hitPoint와 MAX_HP를 사용할 수 있도록 하였다.
그 다음엔 유닛의 현재체력(hitPoint)이 유닛이 가질 수 있는 최고체력(MAX_HP)이 될 때까지 체력을 증가시키는 작업을 수행한다.

 Marine은 Repairable인터페이스를 구현하지 않았으므로 SCV클래스의 repair메서드의 매개변수로 Marine을 사용하면 컴파일 시에 에러가 발생한다.

이와 유사한 예를 한가지 더 들어보자. 게임 스타크래프트에 나오는 건물들을 클래스로 표현하고 이들의 관계를 상속계층도로 표현하였다.



건물을 표현하는 클래스 Academy, Bunker, Barrack, Factory가 있고 이들의 조상인 Building클래스가 있다고 하자. 이 때 Barrack클래스와 Factory클래스에 다음과 같은 내용의, 건물을 이동시킬수 있는, 새로운 메서드를 추가하고자 한다면 어떻게 해야할까? 


void liftOff() { /* 내용생략 */
void move(int x, int y) { /* 내용생략 */
void stop() { /* 내용생략 */
void land() { /* 내용생략 */}


Barrack클래스와 Factory클래스 모두 위의 코드를 적어주면 되긴 하지만, 코드가 중복된다는 단점이 있다. 그렇다고 해서 조상클래스인 Building클래스에 코드를 추가해주면, Building클래스의 다른 자손인 Academy와 Bunker클래스도 추가된 코드를 상속받으므로 안된다.
이런 경우에도 인터페이스를 이용해서 해결할 수가 있다. 우선 새로 추가하고자하는 메서드를 정의하는 인터페이스를 정의하고 이를 구현하는 클래스를 작성한다. 

interface Liftable { 
     /** 건물을 들어 올린다. */ 
     void liftOff(); 
     /** 건물을 이동한다. */ 
     void move(int x, int y); 
     /** 건물을 정지시킨다. */ 
     void stop(); 
     /** 건물을 착륙시킨다. */ 
     void land(); 


class LiftableImpl implements Liftable { 
     void liftOff() { /* 내용 생략 */
     void move(int x, int y) { /* 내용 생략 */ } 
     void stop() { /* 내용 생략 */ } 
     void land() { /* 내용 생략 */ } 



마지막으로 새로 작성된 인터페이스와 이를 구현한 클래스를 Barrack과 Factory클래스에 적용하면 된다. 


class Barrack extends Buildings implments Liftable { 
     LiftableImpl l = new LiftableImpl(); 
     void liftOff() { l.liftOff();} 
     void move(int x, int y) { l.move(x, y);} 
     void stop() { l.stop();} 
     void land() { l.land();} 
     void trainMarine() { /* 내용 생략 */ } 
     void trainMedic() { /* 내용 생략 */ } 
          // ... 


class Factory extends Buildings implments Liftable { 
     LiftableImpl l = new LiftableImpl(); 
     void liftOff() { l.liftOff();} 
     void move(int x, int y) { l.move(x, y);}      

     void stop() { l.stop();} 
     void land() { l.land();} 
     void trainMarine() { /* 내용 생략 */ } 
     void trainMedic() { /* 내용 생략 */ } 
     // ... 


Barrack클래스가 Liftable인터페이스를 구현하도록 하고, 인터페이스를 구현한 LiftableImpl클래스를 Barrack클래스에 포함시켜서 내부적으로 호출해서 사용하도록 한다.
이렇게 함으로써 같은 내용의 코드를 Barrack클래스와 Factory클래스에서 각각 작성하지 않고 LiftableImpl클래스 한 곳에서 관리할 수 있다. 그리고 작성된 Liftable인터페이스와 이를 구현한 LiftableImpl클래스는 후에 다시 재사용될 수 있을 것이다.

 





7.8 인터페이스의 이해


지금까지 인터페이스의 특징과 구현하는 방법, 장점 등 인터페이스에 대한 일반적인 사항들에 대해서 모두 살펴보았다. 하지만 '인터페이스란 도대체 무엇인가?'라는 의문은 여전히 남아있을 것이다. 이번 절에서는 인터페이스의 규칙이나 활용이 아닌, 본질적인 측면에 대해 살펴보자.

먼저 인터페이스를 이해하기 위해서는 다음의 두가지 사항을 반드시 염두에 두고 있어야 한다. 


- 클래스를 사용하는 쪽(User)과 클래스를 제공하는 쪽(Provider)이 있다.
- 메서드를 사용(호출)하는 쪽(User)에서는 사용하려는 메서드(Provider)의 
선언부만 알면 된다.(내용은 몰라도 된다.)


다음과 같이 클래스 A와 클래스 B가 있다고 하자. 


class A { 
      public static void main(String args[]) { 
            B b = new B(); 
            b.methodB(); 
      } 


class B { 
      public void methodB() { 
            System.out.println("methodB()"); 
      } 



클래스 A(User)는 클래스 B(Provider)의 인스턴스를 생성하고 메서드를 호출한다. 이것을 간단히 'A-B'라고 표현하자. 이 두 클래스는 서로 직접적인 관계에 있다.


이 경우 클래스 A를 작성하기 위해서는 클래스 B가 이미 작성되어 있어야 한다. 그리고 클래스 B의 methodB()의 선언부가 변경되면, 이를 사용하는 클래스 A도 변경되어야 한다.
이와 같이 직접적인 관계의 두 클래스는 한 쪽(Provider)가 변경되면 다른 한 쪽(User)도 변경되어야 한다는 단점이 있다.

그러나 클래스 A가 클래스 B를 직접 호출하지 않고 인터페이스를 매개체로 해서 클래스 A가 인터페이스를 통해서 클래스 B의 메서드에 접근하도록 하면, 클래스 B에 변경사항이 생기거나 클래스 B와 같은 기능의 다른 클래스로 대체 되어도 클래스 A는 전혀 영향을 받지 않도록 하는 것이 가능하다.

두 클래스간의 관계를 간접적으로 변경하기 위해서는 먼저 인터페이스를 이용해서 클래스 B(Provider)의 선언과 구현을 분리해야한다.

먼저 다음과 같이 클래스 B에 정의된 메서드를 추상메서드로 정의하는 인터페이스 I를 정의한다.


interface I { 
     public abstract void methodB(); 



그 다음에는 클래스 B가 인터페이스 I를 구현하도록 한다. 


class B implements I 
     public void methodB() { 
           System.out.println("methodB in B class"); 
      } 



이제 클래스 A는 클래스 B 대신 인터페이스 I를 사용해서 작성할 수 있다.


class A { 
      public void methodA(I i) { 
            i.methodB(); 
      } 
}

[참고]methodA가 호출될 때 인터페이스 I를 구현한 클래스의 인스턴스(클래스 B의 인스턴스)를 제공받아야 한다.

클래스 A를 작성하는데 있어서 클래스 B가 사용되지 않았다는 점에 주목하자. 이제 클래스 A와 클래스 B는 'A-B'의 직접적인 관계에서 'A-I-B'의 간접적인 관계로 바뀐 것이다.



결국 클래스 A는 여전히 클래스 B의 메서드를 호출하지만, 클래스 A는 인터페이스 I하고만 직접적인 관계에 있기 때문에 클래스 B의 변경에 영향을 받지 않도록 하는 것이 가능하다.
클래스 A는 인터페이스를 통해 실제로 사용하는 클래스의 이름을 몰라도 되고 심지어는 실제로 구현된 클래스가 존재하지 않아도 문제되지 않는다.

클래스 A는 직접적인 관계에 있는 인터페이스 I의 영향만을 받는다. 

 

인터페이스 I는 실제구현 내용(클래스 B)을 감싸고 있는 껍데기이며, 클래스 A는 껍데기 안에 어떤 알맹이(클래스)가 들어 있는지 몰라도 된다.

[예제7-26] InterfaceTest.java

class A { 
      void autoPlay(I i) { 
            i.play(); 
     } 


interface I { 
      public abstract void play(); 


class B implements I { 
     public void play() { 
            System.out.println("play in B class"); 
      } 


class C implements I { 
     public void play() { 
            System.out.println("play in C class"); 
      } 


class InterfaceTest { 
      public static void main(String[] args) { 
            A a = new A(); 
            a.autoPlay(new B()); 
            a.autoPlay(new C()); 
      } 


[실행결과]
play in B class
play in C class
[참고]클래스 A를 작성하는데 클래스 B가 관련되지 않았다는 사실에 주목한다.

클래스 A가 인터페이스 I를 사용해서 작성되긴 하였지만, 이처럼 매개변수를 통해서 인터페이스 I를 구현한 클래스의 인스턴스를 동적으로 제공받아야 한다.
클래스 Thread의 생성자인 Thread(Runnable target)와 AWT컴포넌트의 addActionListener(ActionListener l)가 이런 방식으로 되어 있다.
[참고]Runnable과 ActionListener는 인터페이스이다.

이처럼 매개변수를 통해 동적으로 제공받을 수 도 있지만 다음과 같이 다른 제 3의 클래스를 통해서 제공받을 수도 있다. JDBC의 DriverManager클래스가 이런 방식으로 되어 있다.

[예제7-27] InterfaceTest2.java

class InterfaceTest2 { 
      public static void main(String[] args) { 
            A a = new A(); 
            a.methodA(); 
      } 


class A { 
      void methodA() { 
           I i = InstanceManager.getInstance(); 
            i.methodB(); 
      } 


interface I { 
      public abstract void methodB(); 


class B implements I { 
      public void methodB() { 
           System.out.println("methodB in B class"); 
      } 


class InstanceManager { 
      public static I getInstance() { 
            return new B(); 
      } 

[실행결과]
methodB in B class


출처 -  http://cafe.naver.com/javachobostudy

'Development > JavaEssential' 카테고리의 다른 글

클래스와 객체  (0) 2012.03.18
패키지(package)  (0) 2012.02.07
추상클래스(Abstract class)  (0) 2012.01.24
클래스변수 & 인스턴스 변수  (0) 2011.12.28
java - 두 가지 데이터 타입  (0) 2011.09.24
Posted by linuxism
,