그래픽을 구현하는 클래스로 Graphics 클래스와 이를 상속하는 Graphics2D 클래스가 있다. Graphics2D 클래스는 JDK1.2 이후에 추가된 것으로 2D(평면) 그래픽 환경을 강력히 지원하는 고수준의 API이다. 이번 장에서는 Java2D 그래픽을 이해하고 애니메이션이나 게임과 같은 응용프로그램에 활용하는 방법을 생각해 보자.

 

 

컴포넌트에 그리기

컴포넌트의 paint 메소드는 자신을 그릴 때 호출되는 메소드이다. 이 메소드는 컴포넌트가 다시 그려질 필요가 있을 때 자동으로 호출된다. 예를 들어 컴포넌트의 일부가 어떤 것에 의해 가려진 후, 다시 보여지면 가려졌던 부분을 다시 그릴 필요가 있을 것이다. 그때 이 메소드가 호출된다. 이 메소드의 헤더는 다음과 같다.

 

public void paint(Graphics g)

 

paint 메소드의 인수로 Graphics 객체(일반적으로 Graphic context라고 함)가 넘어온다. 컴포넌트의 일부를 다시 그릴 필요가 있으면 Graphics 객체를 이용하여 컴포넌트를 그린다. 프로그래머는 paint 메소드를 오버라이드해서 컴포넌트에 원하는 그림이나 도형을 그릴 수 있다.

 

Paint1.java

 

import java.awt.*;

public class Paint1 extends Frame{

  public Paint1(String title){               // 생성자

    super(title);

  }

  public void paint(Graphics g){

    g.drawLine(10,30,50,50);              // 선을 그린다.

    g.drawRect(60,30,50,50);             // 사각형을 그린다.

    g.drawString("Hello!",120,50);         // 문자열을 그린다.

  }

  public static void main(String[] args){

    Paint1 f=new Paint1("paint");

    f.setSize(200,100);

    f.setVisible(true);                     // 프레임이 보여질 때 paint가 호출된다.

  }

}


 

[그림 18-1] paint 메소드

 

paint 메소드는 필요에 의해 자동으로 호출된다. 하지만 화면이 가려지지 않더라도 화면을 갱신해야할 경우가 생길 것이다. 그 때는 repaint 메소드들 호출하면 된다. repaint 메소드가 호출되면 빠르게 update 메소드가 호출되고 update 메소드는 컴포넌트를 원래의 모습대로 그린 후에 paint를 호출한다.

 

repaint() -> update() -> paint()

 

paint 메소드를 오버라이드하지 않고도 컴포넌트에 그릴 수 있는 방법이 있다. Component 클래스의 getGraphics 메소드를 이용하는 것이다. 이 메소드는 컴포넌트의 Graphics 객체의 레퍼런스를 반환한다. 주의할 것은 컴포넌트가 보여질 수 없는 상태에서 getGraphics를 호출하면 null을 반환한다는 점이다.

 

public Graphics getGraphics()      // 컴포넌트의 Graphics 객체를 반환한다.

 

다음 예제를 실행하면 Paint1과 같은 모양의 프레임이 보인다. 하지만 paint 메소드를 오브라이드하지 않았기 때문에 프레임의 일부가 가려지면 가려진 화면은 지워진다.

 

 

Paint2.java

 

import java.awt.*;

public class Paint2{

  public static void main(String[] args){

    Frame f=new Frame("paint");

    f.setSize(200,100);

    f.setVisible(true);                  // 프레임을 보이게 한다.

    Graphics g=f.getGraphics();    // 프레임의 Graphics 객체를 얻어온다.

    g.drawLine(10,30,50,50);

    g.drawRect(60,30,50,50);

    g.drawString("Hello!",120,50);

  }

}


 

 

 

도형 그리기

Graphics 클래스는 여러 가지의 도형을 그릴 수 있는 메소드를 제공한다. Graphics 클래스는 대부분의 메소드가 추상이지만 편의상 abstract 키워드는 생략할 것이다.

 

Graphics 클래스의 유용한 메소드 1

 

public void drawLine(int x1, int y1, int x2, int y2)

좌표 (x1, y1)에서 (x2, y2)까지 직선을 긋는다.

public void drawRect(int x, int y, int width, int height)

좌표 (x, y)로부터 너비가 width이고 높이가 height인 사각형을 그린다.

public void drawRoundRect(int x, int y, int width, int height,

                                                    int arcWidth, int arcHeight)

(x, y)로부터 너비가 width이고 높이가 height인 모서리가 둥근 사각형을 그린다.

arcWidth 와 arcHeight는 모서리의 크기를 정한다.

public void drawOval(int x, int y, int width, int height)

(x, y)로부터 너비가 width이고 높이가 height인 사각형안에 들어가는 타원을 그린다.

width와 height가 같으면 원이 된다.

public void drawArc(int x, int y, int width, int height,

                                                  int startAngle, int arcAngle)

(x, y)로부터 너비가 width이고 높이가 height인 사각형안에 들어가는 원호를 그린다.

startAngle은 원호의 시작 각(도)이고, arcAngle은 원호로 형성되는 부채꼴의 각이다.

public void drawPolygon(int xPoints[], int yPoints[], int nPoints)

다각형을 그린다. 배열 xPoints와 yPoints는 꼭지점의 x와 y의 집합이고, nPoints는 다각형의 꼭지점 개수로 배열의 크기와 일치한다.

public void drawPolygon(Polygon p)

다각형 p를 그린다. Polygon은 다각형을 구현하는 클래스이다.

public void drawPolyline(int xPoints[], int yPoints[], int nPoints)

연결된 직선들을 그린다. 배열 xPoints와 yPoints는 연결점의 x와 y의 집합이고, nPoints는 연결점의 개수이다.

public void clearRect(int x, int y, int width, int height);

주어진 범위에 있는 이미지를 지운다.

 

다음 예제는 프레임에 각종 다각형을 그리는 것이다.

 

Draw1.java

 

import java.awt.*;

public class Draw1 extends Frame{

  public Draw1(String title){                    // 생성자

    super(title);

  }

  public void paint(Graphics g){

    g.drawLine(10,30,50,50);                  // 직선을 그린다.

    g.drawRect(60,30,50,50);                  // 사각형을 그린다.

    g.drawRoundRect(120,30,50,50,20,20);    // 둥근 사각형을 그린다.

    g.drawOval(10,100,70,50);                 // 타원을 그린다.

    g.drawArc(100,100,50,50,90,180);          // 원호를 그린다.

    int[] x=new int[]{200,240,200,220,240};

    int[] y=new int[]{80,80,120,60,120};

    g.drawPolygon(x,y,5);                     // 다각형을 그린다.

  }

  public static void main(String[] args){

    Frame f=new Draw1("도형 그리기");

    f.setSize(300,200);

    f.setVisible(true);

  }

}


 

[그림 18-2] 도형 그리기

 

안이 색으로 채워진 도형을 그릴 때는 다음과 같은 메소드를 사용한다.

 

 

Graphics 클래스의 유용한 메소드 2

 

public void fillRect(int x, int y, int width, int height)

채워진 사각형을 그린다.

public void fillOval(int x, int y, int width, int height)

채워진 타원을 그린다.

 

위의 메소드외에도 fillRoundRect, fillPolygon 등이 있다.

 

Draw2.java

 

import java.awt.*;

public class Draw2 extends Frame{

  public Draw2(String title){

    super(title);

  }

  public void paint(Graphics g){

    g.fillRect(60,30,50,50);

    g.fillRoundRect(120,30,50,50,20,20);

    g.fillOval(10,100,70,50);

  }

  public static void main(String[] args){

    Frame f=new Draw2("도형 그리기");

    f.setSize(300,200);

    f.setVisible(true);

  }

}


 

[그림 18-3] 채워진 도형 그리기

 

 

 

마우스로 곡선 그리기

마우스의 버튼을 누른 채로 움직이면 마우스 포인터를 따라 곡선이 그려지도록 해보자. 움직이기 전의 마우스 좌표(ox, oy)에서 움직인 후의 마우스 좌표(x, y)까지 직선을 긋는 방법을 이용한다.

 

Draw3.java

 

import java.awt.*;

import java.awt.event.*;

public class Draw3 extends Frame{

  Graphics g;                     // 프레임의 Graphics 객체를 위한 변수

  int x, y, ox, oy;         // 움직인 후의 좌표(x, y)와 움직이기 전의 좌표(ox, oy)

  public Draw3(String title){      // 생성자

    super(title);

    setSize(200,200);

    setVisible(true);

    g=this.getGraphics();       // 프레임의 Graphics 객체를 얻는다.

    g.setColor(Color.red);        // 그리기 색을 빨간 색으로 정한다.

 

    // 마우스 움직임 이벤트 처리

    addMouseMotionListener(new MouseMotionAdapter(){

        public void mouseDragged(MouseEvent e){        // 마우스가 움직이면

          x=e.getX(); y=e.getY();              // 마우스의 현재 위치를 알아온다.

 

          // 전 위치부터 현재 위치까지 직선을 긋는다.

          g.drawLine(ox, oy, x, y);

        

          ox=x; oy=y;                  // x와 y를 ox와 oy에 대입한다.

        }

    });

 

    // 마우스 이벤트 처리

    addMouseListener(new MouseAdapter(){

        // 마우스를 누르면 호출된다.

        public void mousePressed(MouseEvent e){

           ox=e.getX(); oy=e.getY();             // 마우스의 위치를 기억한다.

        }

    });

  }

  public static void main(String[] args){

    Frame f=new Draw3("도형 그리기");

  }

}


 

[그림 18-4] 곡선 그리기

 

 

 

이미지 그리기

로컬 컴퓨터에 있는 이미지뿐만 아니라 네트워크 상에 있는 이미지도 쉽게 불러올 수 있다. 이미지를 처리하는 과정은 크게 이미지를 불러오는 작업과 이미지를 그리는 작업으로 생각할 수 있다. 그런데 문제는 이미지를 그리는 작업은 Graphics 객체가 담당한다지만 이미지를 불러오는 작업은 Toolkit 객체가 담당한다는 것이다. 이는 이미지를 불러오는 작업과 이미지를 그리는 작업이 비동기적임 의미한다. 따라서 이미지가 완전히 다운로드 되었는지, 혹은 다운로드하고 있는 중인지하는 이미지의 상태를 추적하는 작업을 필요하다. URL 객체는 이미지의 위치를 나타낼 수 있고, MediaTracker 객체는 이미지의 상태를 추적할 수 있다.

 

이미지처리에 필요한 클래스와 그 역할을 정리하면 다음과 같다.

 

 

클래스

역할

Graphics

이미지를 그린다.

Image

이미지(그림)를 구현한다.

URL

이미지의 위치를 구현한다.

Toolkit

이미지를 불러온다.

MediaTracker

이미지의 상태를 추적한다.

 

 

 

Graphics 클래스

Graphics 클래스는 이미지를 확대 또는 축소하여 그리거나 이미지의 부분 영역을 그릴 수 있는 메소드, drawImage를 제공한다. drawImage 메소드는 ImageObserver라는 객체를 인수로 받는데 ImageObserver는 이미지의 정보를 전달받아서 이미지를 업데이트시키는 역할을 한다. 이미지가 완전히 불려지지 않았을 때 drawImage를 호출하면 ImageObserver 객체에게 그 정보가 전달되고 ImageObserver는 이미지를 관찰하고 있다가 이미지가 완전히 불려지면 이미지를 업데이트시킨다. 컴포넌트 클래스가 이 인터페이스를 구현하고 있다. 따라서 이미지가 그려질 컴포넌트를 ImageObserver로 두면 된다.

 

Graphics 클래스는 다음과 같이 이미지를 그리는 메소드를 제공한다.

 

 

Graphics 클래스의 유용한 메소드

 

public abstract boolean drawImage(Image img, int x, int y,

                                      ImageObserver observer);

이미지 img를 (x, y)에 그린다.

public abstract boolean drawImage(Image img, int x, int y, Color bgcolor,

                                      ImageObserver observer);

배경색을 bgcolor로 채운 후에 이미지 img를 (x, y)에 그린다.

public abstract boolean drawImage(Image img, int x, int y, int width,

                                      int height, ImageObserver observer);

이미지 img의 너비를 width로, 높이를 height로 확대하거나 축소하여 (x, y)에 그린다.

public abstract boolean drawImage(Image img, int x, int y,

                                    int width, int height,

                                    Color bgcolor, ImageObserver observer);

배경색을 bgcolor로 채우고 이미지 img의 너비를 width로 높이를 height로 확대하거나 축소한 후 (x, y)에 그린다.

public abstract boolean drawImage(Image img, int dx1, int dy1, int dx2,

                                    int dy2, int sx1, int sy1, int sx2, int sy2,

                                    ImageObserver observer);

이미지 img의 (sx1, sy1, sx2, sy2)영역을 (dx1, dy1, dx2, dy2)영역에 그린다.

두 영역의 크기가 같지 않는 경우 확대되거나 축소된다.

public abstract boolean drawImage(Image img, int dx1, int dy1, int dx2,

                                    int dy2, int sx1, int sy1, int sx2, int sy2,

                                    Color bgcolor, ImageObserver observer);

배경색을 bgcolor로 채우고 이미지 img의 (sx1, sy1, sx2, sy2)영역을 (dx1, dy1, dx2, dy2)영역에 그린다.

 

실제로 그리는 작업은 조금 후에 알아보고 Image 클래스와 URL 클래스를 살펴보자.

 

 

 

Image 클래스

Image 클래스는 그래픽 이미지를 표현하는 추상 클래스이다. 따라서 new 키워드를 사용하여 이미지 객체를 생성할 수는 없다. 대신에 Toolkit 객체로부터 이미지를 받아오면 된다.

 

Toolkit toolkit=Toolkit.getDefaultToolkit();      // 기본 툴킷 객체를 얻는다.

Image img=toolkit.getImage(URL객체);         // 이미지를 얻는다.

 

또는 Component 클래스의 createImage 메소드를 이용하여 새로운 이미지를 만들 수도 있다.

 

// 가로 크기가 100이고 세로 크기가 200인 이미지를 생성한다.

Image img = 컴포넌트.createImage(100, 200);

 

이렇게 만든 이미지는 더블 버퍼링에 사용될 수 있다. Toolkit 클래스와 더블 버퍼링 기법은 조금 후에 알아보고 Image 클래스의 유용한 메소드를 살펴보자.

 

 

Image 클래스의 유용한 메소드

 

public abstract Graphics getGraphics();

이미지의 Graphics 객체를 반환한다. 이미지를 수정할 때 사용된다.

public abstract int getWidth(ImageObserver observer);

이미지의 가로 크기를 반환한다.

public abstract int getHeight(ImageObserver observer);

이미지의 세로 크기를 반환한다.

 

 

 

URL 클래스

URL 클래스는 월드와이드웹(www)상의 URL(Uniform Resource Locator)을 표현하는 클래스이다. URL은 웹 상에 있는 특정 파일의 위치를 명시하기 위한 것으로 서비스의 종류, 서버의 위치(도메인), 파일 이름으로 구성된다.

 

URL의 구성

서비스의 종류(프로토콜)

http, ftp

서버의 위치(도메인)

java.sun.com

파일이름

index.html, image.gif

 

다음은 sun의 자바 홈페이지에 있는 어떤 그림에 대한 URL 객체를 생성하는 예이다.

 

URL url=new URL("http://java.sun.com/images/v4_java_logo.gif");

 

어떤 클래스에 대한 상대 경로를 이용하여 URL 객체를 얻을 수도 있다.

 

// 객체의 런타임 클래스 객체를 얻는다.

Class cl = 객체.getClass();

 

// 클래스와 같은 위치에 있는 파일에 대한 URL 객체를 얻는다.

URL url = cl.getResource(파일이름);

 

 

Url1.java

 

import java.awt.*;

import java.net.*;                      // URL을 import한다.

public class Url1{

  public static void main(String[] args){

    Url1 ob=new Url1();

    Class cl= ob.getClass();                 // ob에 대한 클래스 객체를 얻는다.

    URL url=cl.getResource("Url1.java");    // "Url1.java"의 Url 객체를 만든다.

    System.out.println(cl.toString());

    System.out.println(url.toString());

  }

}


 

출력 결과

 

class Url1

file:/C:/java2/source/Url1.java


 

 

URL 클래스는 네트워크 프로그래밍 장에서 다시 살펴보자.

 

 

 

Toolkit 클래스

이 클래스는 이미지를 불러오거나 화면에 대한 정보를 제공한다.

 

 

Toolkit 클래스의 유용한 메소드

 

public static synchronized Toolkit getDefaultToolkit()

기본 툴킷 객체를 반환한다.

public abstract Image getImage(String filename);

filename에 해당되는 이미지를 반환한다.

public abstract Image getImage(URL url);

url에 해당되는 이미지를 반환한다.

public abstract boolean prepareImage(Image image, int width, int height,

                                       ImageObserver observer);

너비가 width이고 높이가 height인 이미지를 그릴 수 있도록 준비한다.

width와 height에 -1을 대입하면 이미지의 원래 크기로 준비한다.

public abstract Dimension getScreenSize();

스크린의 사이즈를 반환한다.

 

getImage 메소드는 이미지의 위치(URL)만 생성할 뿐이지 이미지를 불러오는 것은 아니다. 그래픽 객체의 drawImage 메소드를 호출하면 비로소 이미지를 불러와서 그리게 된다. 이미지를 불러오는 시간이 소요되기 때문에 drawImage를 호출했을 때, 바로 그려지지 않는다. 그래서 이미지를 불러와 그릴 수 있는 상태로 만들어주는 prepareImage 메소드를 따로 만들어 두었다. 이 메소드를 호출하면 이미지를 불러오는 작업을 시작하고 이미지가 그려질 수 있는 상태가 되면 true를 반환한다. 그러나 이미지의 크기가 크거나 인터넷상에 있어서 불러오는 시간이 많이 소요되는 경우에는 prepareImage를 호출하여도 바로 준비시켜주지 못하고 false를 반환하는 경우가 있다. 이때는 다시 prepareImage를 호출하여 이미지가 준비된 상태인지 알아보아야 하는 불편함이 있다.

 

Frame f=new Frame();

Graphics g=f.getGraphics();                // 프레임의 그래픽 객체를 얻는다.

Toolkit toolkit=f.getToolkit();                // 프레임의 툴킷 객체를 얻는다.

Image img=toolkit.getImage(URL객체);     // 이미지를 얻는다.

toolkit.prepareImage(img, -1, -1, f);     // img를 그릴 수 있게 준비시킨다.

g.drawImage(img, 10,10, f);              // img를 그린다.

 

다음은 인터넷상에 있는 그림을 불러와 프레임에 그리는 예제이다. 그림이 존재하지 않아서 그려지지 않는다면 다른 URL을 사용해 보자.

 

image1.java

 

import java.awt.*;

import java.net.*;

public class Image1 extends Frame{

  Image img;

  Toolkit tkit;

  public Image1(String title){                  // 생성자

    super(title);

    tkit=getToolkit();                // this(프레임)의 Toolkit 객체를 얻는다.

    try{

      URL url=new URL("http://java.sun.com/images/v4_java_logo.gif");

 

      img = tkit.getImage(url);               // url에 대한 이미지 객체를 만든다.

      tkit.prepareImage(img,-1,-1,this);     // 이미지를 준비시킨다.

    }catch(MalformedURLException e){      // URL이 이상할 경우에 발생한다.

      System.out.println("URL이 올바르지 않습니다.");

    }

  }

  public void paint(Graphics g){

     g.drawImage(img,80,80,this);             // 이미지를 그린다.

  }

  public static void main(String[] args){

    Frame f=new Image1("이미지 그리기");

    f.setSize(200,200);

    f.setVisible(true);

  }

}


 

[그림 18-5] 이미지 그리기

 

 

 

MediaTracker 클래스

미디어 트래커는 많은 이미지의 상태를 점검하는데 편리하게 사용된다. 미디어 트래커는 다운로드할 이미지를 추가하거나 제거할 수 있는 리스트를 가지고 있다. 리스트에 이미지를 추가할 때는 이미지와 함께 아이디(ID)도 함께 추가한다. ID는 중복될 수 있고 같은 ID의 이미지들은 묶어서 처리할 수 있다.

 

 

MediaTracker 클래스의 유용한 생성자

 

public MediaTracker(Component comp)

주어진 컴포넌트(comp)에 그려질 이미지들을 추적하는 미디어 트래커를 만든다.

 

 

MediaTracker 클래스의 유용한 메소드

 

public void addImage(Image image, int id)

public void addImage(Image image, int id, int w, int h)

id를 가진 image를 리스트에 추가하고, w(너비)와 h(높이)로 이미지의 배율을 조정한다. id는 중복될 수 있다.

public void removeImage(Image image)

public void removeImage(Image image, int id)

public void removeImage(Image image, int id, int width, int height)

이미지를 목록에서 제거한다.

public boolean checkAll()

모든 이미지가 로딩(loading)되었으면 true를, 아니면 false를 반환한다.

public boolean checkID(int id)

public boolean checkID(int id, boolean load)

id를 가진 이미지를 체크한다. load에 true를 대입하면 로딩되지 않은 이미지를 로딩하기 시작한다.

public synchronized Object[] getErrorsAny()

public synchronized Object[] getErrorsID(int id)

에러가 발생한 이미지들을 모두 반환하거나 주어진 id의 이미지들을 반환하다.

public synchronized boolean isErrorAny()

public synchronized boolean isErrorID(int id)

로딩하는 동안 이미지에 에러가 있으면 true를, 없으면 false를 반환한다.

public void waitForAll() throws InterruptedException

public boolean waitForAll(long ms) throws InterruptedException

모든 이미지가 로딩될 때까지 무작정 기다리거나 ms(milli second)까지 기다린다.

도중에 에러가 발생하면 모두 로딩된 것으로 간주하므로 isErrorAny로 에러가 있는지 검사해보아야 한다.

public void waitForID(int id) throws InterruptedException

public boolean waitForID(int id, long ms) throws InterruptedException

주어진 id를 가진 이미지가 로딩될 때까지 기다리거나 ms까지 기다린다. 도중에 에러가 발생하면 모두 로딩된 것으로 간주한다.

 

 

다음 예제는 이미지를 모두 불러온 후에 프레임에 그리는 예제이다. 불러오지 못한 이미지가 있으면 에러 메시지를 출력하고 프로그램을 종료할 것이다. 그러면 지정한 URL에 해당하는 이미지가 없을 수도 있으므로 URL을 바꾸어 실행해 보자.

 

Image2.java

 

import java.awt.*;

import java.net.*;

public class Image2 extends Frame{

  Image[] img;                         // 이미지를 위한 배열

  Toolkit tkit;

  public Image2(String title){           // 생성자

    super(title);

    tkit=getToolkit();                   // this의 툴킷 객체를 얻는다.

    img=new Image[2];                // 2개의 이미지

    URL url;

    try{

      url=new URL("http://java.sun.com/docs/books/tutorial/figures/2d/2D-1.gif");

      img[0]=tkit.getImage(url);          // 이미지 객체를 생성한다.

 

      url=new URL("http://java.sun.com/docs/books/tutorial/figures/2d/2D-2.gif");

      img[1]=tkit.getImage(url);         // 이미지 객체를 생성한다.

 

      MediaTracker mTracker =new MediaTracker(this);  // 미디어 트래커 객체

 

      // 미디어 트래커에 이미지를 추가한다.

      mTracker.addImage(img[0],0);                   // ID = 0

      mTracker.addImage(img[1],1);                      // ID = 1

 

      System.out.println("이미지 로딩중...");

      mTracker.waitForAll();             // 이미지가 완전히 로딩되도록 기다린다.

 

      if(mTracker.isErrorAny()){                       // 이미지에 에러가 있으면

        System.out.println("이미지 다운로드 오류");      // 메시지를 출력한다.

        System.exit(1);                               // 프로그램을 종료한다.

      }

 

      System.out.println("이미지 로딩 완료");

    }catch(Exception e){

      System.out.println(e);

    }

  }

  public void paint(Graphics g){

    g.drawImage(img[0],20,30,this);   // 이미지를 그린다.

    g.drawImage(img[1],20,160,this);

  }

  public static void main(String[] args){

    Frame f=new Image2("이미지 그리기");

    f.setSize(450,250);

    f.setVisible(true);

  }

}


 

[그림 18-6] MediaTracker 활용

 

 

 

Graphics2D

Graphics2D는 Graphics 클래스를 상속하는 클래스이다. 이 클래스는 2D 그래픽 환경을 강력히 지원하는 진보된 그래픽 클래스이다. AWT 컴포넌트의 paint(Graphics g) 메소드의 인수로 넘어오는 Graphics 객체는 Graphics2D 객체로 형 변환이 가능하다.

 

public void paint(Graphics g){

  Graphics2D g2=(Graphics2D)g;    // Graphics2D 객체로 형 변환

}

 

사실 컴포넌트의 그래픽 객체는 Graphics 객체가 아니라 Graphics2D 객체이기 때문에 형 변환이 가능한 것이다. Graphics2D는 JDK1.2 이후에 추가된 API로 이전의 기본적인 그래픽 환경을 강력한 2D 그래픽 환경으로 개선시켰다. 그러므로 Graphics 객체를 Graphic2D 객체로 형 변환만 하면 2D 그래픽 환경을 사용할 수 있다.

 

 

 

기하학적 도형

java.awt.geom 패키지를 보면 기하학적인 도형을 나타내는 클래스들이 있다. 모두 Shape(도형) 인터페이스를 구현한다. Shape 인터페이스를 구현하는 클래스의 종류로 Line2D, Rectangle2D, Ellipse2D, GeneralPath 등이 있다. 2D 환경에서는 좌표계가 확대, 축소 또는 회전이 되기 때문에 이런 클래스들은 정수가 아닌 double 또는 float으로 이루어진 좌표를 사용한다.

 

 

Shape 인터페이스의 유용한 메소드

 

public boolean contains(double x, double y);

좌표 (x, y)가 도형 내부에 있는 점이면 true를, 아니면 false를 반환한다.

public boolean contains(double x, double y, double w, double h);

영역 (x, y, w, h)이 도형 내부에 있으면 true를, 아니면 false를 반환한다.

public Rectangle getBounds();

public Rectangle2D getBounds2D();

도형을 감싸는 사각형 영역을 반환한다.

public boolean intersects(double x, double y, double w, double h);

public boolean intersects(Rectangle2D r);

해당 영역이 도형과 겹치면 true를, 아니면 false를 반환한다.

 

Graphics2D 객체의 draw와 fill 메소드를 이용하면 도형을 그릴 수 있다.

 

 

Graphics2D 클래스의 유용한 메소드

 

public abstract void draw(Shape s);

도형의 외각을 그린다.

public abstract void fill(Shape s);

채워진 도형을 그린다.

 

 

 

Line2D

이 클래스는 평면상의 직선을 나타내는 추상 클래스이다. 자식 클래스로 double 좌표를 사용하는 Line2D.Double과 float 좌표를 사용하는 Line2D.Float이 있다. 생성자의 인수로 두 점의 좌표 (x1, y1), (x2, y2)을 필요로 한다.

 

import java.awt.*;

import java.awt.geom.*;

...

public void paint(Graphics g){

  Graphics2D g2=(Graphics2D)g;        // Graphics2D 객체로 형 변환한다.

  Line2D line1=new Line2D.Double(10.0, 30.0, 100.0, 100.0);  // Line2D 객체

  Line2D line2=new Line2D.Float(100.0f, 30.0f, 10.0f, 100.0f);

 

  g2.draw(line1);  // 도형을 그린다.

  g2.fill(line2);

}

 

 

 

Rectangle2D

이 클래스는 평면상의 사각형을 나타내는 추상 클래스이다. 자식 클래스로 Line2D와  마찬가지로 Rectangle2D.Double과 Rectangle2D.Float이 있다. 생성자의 인수로 왼쪽 상단의 꼭지점의 좌표(x, y)와 너비, 높이가 필요하다.

 

Rectangle2D rect1=new Rectangle2D.Double(10.0, 30.0, 100.0, 100.0);

Rectangle2D rect2=new Rectangle2D.Float(150.0f, 30.0f, 100.0f, 100.0f);

g2.draw(rect1);

g2.fill(rect2);

 

 

 

Ellipse2D

이 클래스는 평면상의 타원을 구현하는 추상 클래스이다. 생성자의 인수로 타원을 포함하는 사각형의 왼쪽 상단의 좌표(x, y)와 너비, 높이가 필요하다. 너비와 높이를 같게 하면 원이 된다.

 

Ellipse2D ellipse1=new Ellipse2D.Double(10.0, 30.0, 100.0, 100.0);

Ellipse2D ellipse2=new Ellipse2D.Float(150.0f, 30.0f, 100.0f, 100.0f);

g2.draw(ellipse1);

g2.fill(ellipse2);

 

다음 예제는 Line2D와 Rectangle2D, Ellipse2D를 이용하여 직선과 사각형, 타원(원)을 그리는 예제이다.

 

Shape1.java

 

import java.awt.*;

import java.awt.geom.*;

public class Shape1 extends Frame{

  public Shape1(String title){

    super(title);

  }

  public void paint(Graphics g){

    Graphics2D g2=(Graphics2D)g;

    Line2D line1=new Line2D.Double(50.0, 50.0, 250.0, 50.0);

    Rectangle2D rect1=new Rectangle2D.Double(50.0, 100.0, 100.0, 100.0);

    Ellipse2D ellipse1=new Ellipse2D.Double(200.0, 100.0, 100.0, 100.0);

    g2.draw(line1);

    g2.draw(rect1);

    g2.fill(ellipse1);

  }

  public static void main(String[] args){

    Frame f=new Shape1("평면 도형");

    f.setSize(350,250);

    f.setVisible(true);

  }

}


 

[그림 18-7] 2D 도형

 

 

 

GeneralPath

GeneralPath는 직선 또는 곡선으로 연결된 경로에 의해서 형성되는 도형을 구현한 클래스이다.

 

GeneralPath 클래스의 유용한 메소드

 

public synchronized void moveTo(float x, float y)

현재의 점을 (x, y)로 이동한다.

public synchronized void lineTo(float x, float y)

현재의 점에서 (x, y)까지 직선을 긋는다.

public void append(Shape s, boolean connect)

새로운 도형 s를 추가한다. connect가 true이면 이전의 도형과 연결한다.

public synchronized void closePath()

처음 점과 끝점을 연결하는 직선을 그어 경로를 닫는다.

 

단순한 예제이므로 이해하기 쉬울 것으로 생각된다.

 

Shape2.java

 

import java.awt.*;

import java.awt.geom.*;

public class Shape2 extends Frame{

  public Shape2(String title){

    super(title);

  }

  public void paint(Graphics g){

    Graphics2D g2=(Graphics2D)g;

    GeneralPath gp=new GeneralPath();  // GeneralPath 객체를 만든다.

    gp.moveTo(150,50);                  // (150, 50)으로 현재의 점을 옮긴다.

    gp.lineTo(150,250);                   // (150, 250)까지 직선을 긋는다.

    gp.lineTo(250,150);                   // (250, 150)까지 직선을 긋는다.

    gp.lineTo(50,150);                    // (50, 150)까지 직선을 긋는다.

    gp.closePath();                 // 처음 점과 끝점을 연결하여 경로를 닫는다.

    

    g2.draw(gp);                           // GeneralPath 객체를 그린다.

  }

  public static void main(String[] args){

    Frame f=new Shape2("GeneralPath");

    f.setSize(300,300);

    f.setVisible(true);

  }

}


 

[그림 18-8] GeneralPath

 

 

 

렌더링 속성

2D 그래픽 환경에서 도형이나 이미지를 그릴 때 선의 두께와 스타일, 칠하기 패턴과 색의 연산, 좌표의 변환 등을 적용할 수 있다. 이런 것들을 렌더링 속성(Rendering Attributes)이라고 하고 Stroke, Paint, Composite, Transform, Clip, RenderingHints 등이 있다.

 

 

Stroke 속성

스트로크(Stroke)란 도형의 외각선 모양을 결정하는 속성이다. 스트로크를 구현하는 클래스로 BasicStroke가 있다. BasicStroke 객체는 선의 두께와 스타일, 끝점(end cap)의 모양, 꼭지점(end join: 선과 선이 만나는 부분)의 모양을 설정한다.

 

 

BasicStroke 클래스의 유용한 상수

 

public final static int CAP_BUTT = 0;

선이 끝나는 곳의 모양을 사각형으로 설정한다.

public final static int CAP_ROUND = 1;

선이 끝나는 곳의 모양을 둥글게 설정한다.

public final static int CAP_SQUARE = 2;

모양은 CAP_BUTT와 같으나 선두께의 반만큼 나오게 설정한다.

public final static int JOIN_MITER = 0;

꼭지점의 모양을 날카롭게 설정한다.

public final static int JOIN_ROUND = 1;

꼭지점의 모양을 둥글게 설정한다.

public final static int JOIN_BEVEL = 2;

꼭지점의 날카로운 부분을 자른다.

 

 

BasicStroke 클래스의 유용한 생성자

 

public BasicStroke(float width, int cap, int join, float miterlimit,

                     float dash[], float dash_phase)

width(선의 두께), cap(끝점의 모양), join(꼭지점의 모양),

miterlimit(꼭지점 길이의 한계: 1이상 값), dash(점선의 패턴 길이 모양),

dash_phase(점선간의 공백 거리)가 설정된 스트로크 객체를 만든다.

public BasicStroke(float width, int cap, int join, float miterlimit)

BasicStroke(width, cap, join, miterlimit, null, 0.0f)와 같다.

dash가 null이고 dash_phase가 0이면 실선을 의미한다.

public BasicStroke(float width, int cap, int join)

BasicStroke(width, cap, join, 10.0f, null, 0.0f)와 같다.

public BasicStroke(float width)

BasicStroke(width, CAP_SQUARE, JOIN_MITER, 10.0f, null, 0.0f)와 같다.

public BasicStroke()

BasicStroke(1.0f, CAP_SQUARE, JOIN_MITER, 10.0f, null, 0.0f)와 같다.

 

BasicStroke를 그래픽 환경에 적용하려면 Graphics2D 객체의 setStroke 메소드를 호출한다.

 

g2.setStroke(new BasicStroke(30));

 

다음은 Stroke를 설정하여 도형을 그리는 예제이다.

 

Stroke1.java

 

import java.awt.*;

import java.awt.geom.*;

public class Stroke1 extends Frame{

  public Stroke1(String title){

    super(title);

  }

  public void paint(Graphics g){

    Graphics2D g2=(Graphics2D)g;

    

    // CAP_ROUND를 사용한 직선 그리기

    g2.setStroke(new BasicStroke(30,BasicStroke.CAP_ROUND,0));

    g2.draw(new Line2D.Double(50,50,200,50));

    

    // JOIN_ROUND로 사각형 그리기

    g2.setStroke(new BasicStroke(30,0,BasicStroke.JOIN_ROUND));

    g2.draw(new Rectangle2D.Double(50,100,50,50));

    

    // JOIN_BEVEL으로 사각형 그리기

    g2.setStroke(new BasicStroke(30,0,BasicStroke.JOIN_BEVEL));

    g2.draw(new Rectangle2D.Double(150,100,50,50));

    

    // JOIN_MITER로 점선 그리기

    float[] dash=new float[]{10,5,5,5};

    g2.setStroke(new BasicStroke(5,0,BasicStroke.JOIN_MITER,1.0f,dash, 0));

    g2.draw(new Rectangle2D.Double(50,200,150,50));

    

  }

  public static void main(String[] args){

    Frame f=new Stroke1("스트로크");

    f.setSize(250,300);

    f.setVisible(true);

  }  

}


 

[그림 18-9] Stroke 속성

 

 

Paint 속성

이 속성은 도형의 내부를 채울 때 적용되는 속성으로 단일 색 속성, 그라데이션 속성, 타일 속성이 있다.

 

Color

단일 색으로 도형 내부를 채운다.

GradientPaint

선형적으로 변하는 색으로 내부를 채운다. 그라데이션 효과

TexturePaint

작은 이미지를 반복해서 채운다. 타일 효과

 

Paint 속성을 적용하려면 Graphics2D 객체의 setPaint 메소드들 사용한다. 단일 색으로 채우려면 다음 코드와 같이 할 수 있다.

 

g2.setPaint( Color.red );        // 빨간 색으로 채우는 Paint를 적용한다.

 

 

다음으로 그라데이션 효과를 위한 GradientPaint 클래스를 살펴보자.

 

GradientPaint 클래스의 유용한 생성자

 

public GradientPaint(float x1, float y1, Color color1,

                      float x2, float y2, Color color2 )

시작점(x1, y1)의 색(color1)에서 끝점(x2, y2)의 색(color2)으로 변하는 Paint 객체를 만든다.

public GradientPaint(float x1, float y1, Color color1,

                      float x2, float y2, Color color2, boolean cyclic)

시작점(x1, y1)의 색(color1)에서 끝점(x2, y2)의 색(color2)으로 변하는 Paint 객체를 만든다. cyclic이 true이면 반복적으로 그라데이션 효과가 적용된다.

 

GradientPaint 객체를 생성하고 Graphics2D 객체에 적용하는 코드를 예로 들면 아래와 같다.

 

g2.setPaint( new GradientPaint(0, 0, Color.white, 50, 50, Color.blue, true) );

 

다음은 프레임의 생성자에서 네 개의 GradientPaint 객체를 생성한 후에 paint 메소드에서 GradientPaint 속성을 적용하여 사각형을 그리는 예제이다.

 

Gradient1.java

 

import java.awt.*;

import java.awt.geom.*;

public class Gradient1 extends Frame{

  Paint gPaint1, gPaint2, gPaint3, gPaint4;   // Paint 속성을 위한 변수들

  

  public Gradient1(String title){              // 생성자

    super(title);

 

    // GradientPaint 객체를 생성한다.

    gPaint1=new GradientPaint(10, 50, Color.white, 20, 60, Color.blue, true);

    gPaint2=new GradientPaint(100, 50, Color.white, 100, 30, Color.blue, true);

    gPaint3=new GradientPaint(190, 50, Color.white, 270, 250, Color.blue, false);

    gPaint4=new GradientPaint(280, 50, Color.white, 280, 250, Color.blue, false);

  }

  public void paint(Graphics g){

    Graphics2D g2=(Graphics2D)g;

    

    // GradientPaint를 적용하여 사각형을 그린다.

    g2.setPaint(gPaint1);

    g2.fill(new Rectangle2D.Double(10,50,80,200));

    g2.setPaint(gPaint2);

    g2.fill(new Rectangle2D.Double(100,50,80,200));

    g2.setPaint(gPaint3);

    g2.fill(new Rectangle2D.Double(190,50,80,200));

    g2.setPaint(gPaint4);

    g2.fill(new Rectangle2D.Double(280,50,80,200));

  }  

  public static void main(String []args){

    Frame f=new Gradient1("그라데이션 효과");

    f.setSize(400,300);

    f.setVisible(true);

  }

}


 

[그림 18-10] 그라데이션 효과

 

 

마지막으로 TexturePaint는 작은 그림을 반복적으로 채우기 위한 클래스이다. 작은 그림으로 BufferedImage 객체를 사용하는데 BufferedImage 클래스는 이미지의 각 픽셀의 정보를 버퍼에 기억하고 있어 프로그래머는 그 버퍼에 직접 접근하여 이미지의 정보를 바꿀 수 있다. 따라서 BufferedImage를 사용하면 이미지를 다양하게 처리할 수 있다.

 

BufferedImage 클래스의 유용한 생성자

 

public BufferedImage(int width, int height, int imageType)

imageType으로 설정된 주어진 크기(width×height)의 BufferedImage 객체를 만든다.

 

image type은 하나의 픽셀이 차지하는 비트 수와 RGB의 정보를 나타내는 것으로 상수로 정의 되어있다. 예를 들어 TYPE_INT_RGB은 한 픽셀의 크기가 4바이트(Int)이고 RGB는 각각 8비트씩 차지하는 이미지 타입을 나타낸다.

 

TexturePaint 속성은 BufferedImage 객체와 더불어 이미지를 맵핑시키는 사각형 영역에 대한 정보를 필요로 한다. 이 영역을 앵커(anchor)라고 하고 Rectangle2D 객체로 표현한다. TexturePaint 속성은 다음 그림과 같이 앵커를 기준으로 모든 방향으로 BufferedImage을 채운다.

 

 

anchor

 

 

실제로 TexturePaint 클래스의 생성자는 다음과 같다.

 

TexturePaint 클래스의 유용한 생성자

 

public TexturePaint( BufferedImage txtr, Rectangle2D anchor )

txtr을 anchor에 맵핑하는 TexturePaint 객체를 만든다.

 

다음은 TexturePaint 속성으로 도형을 그리는 예제이다.

 

Texture1.java

 

import java.awt.*;

import java.awt.geom.*;

import java.awt.image.*;

public class Texture1 extends Frame{

  Paint tPaint1, tPaint2, tPaint3, tPaint4;    // TexturePaint 속성을 위한 변수들

  

  public Texture1(String title){             // 생성자

    super(title);

 

    // 10×10 크기의 BufferedImage 객체를 만든다.

    BufferedImage buf=

               new BufferedImage(10,10,BufferedImage.TYPE_INT_RGB);

 

    // BufferedImage 객체의 Graphics2D 객체를 얻는다.

    Graphics2D g2buf=(Graphics2D)buf.getGraphics();

    

    // BufferedImage 객체에 채워진 원을 그린다.

    g2buf.setPaint(new Color(100,100,255)); // Paint 설정

    g2buf.fill(new Ellipse2D.Float(0,0,10,10)); // 채워진 원

 

    // 네 개의 TexturePaint 객체를 만든다.

    tPaint1=new TexturePaint(buf, new Rectangle.Float(10,50,10,10));

    tPaint2=new TexturePaint(buf, new Rectangle.Float(100,50,15,15));

    tPaint3=new TexturePaint(buf, new Rectangle.Float(190,50,20,20));

    tPaint4=new TexturePaint(buf, new Rectangle.Float(280,50,25,25));

  }

  public void paint(Graphics g){

  

    Graphics2D g2=(Graphics2D)g;

 

    // TextPaint 객체를 적용하여 네 개의 사각형을 그린다.

    g2.setPaint(tPaint1);

    g2.fill(new Rectangle2D.Double(10,50,80,200));

    g2.setPaint(tPaint2);

    g2.fill(new Rectangle2D.Double(100,50,80,200));

    g2.setPaint(tPaint3);

    g2.fill(new Rectangle2D.Double(190,50,80,200));

    g2.setPaint(tPaint4);

    g2.fill(new Rectangle2D.Double(280,50,80,200));

  }  

  public static void main(String []args){

    Frame f=new Texture1("타일 효과");

    f.setSize(400,300);

    f.setVisible(true);

  }

}


 

[그림 18-11] 타일 효과

 

 

Composite 속성

합성(Composite)은 이미지와 이미지가 겹칠 때 색상을 조합하는 속성이다. Composite를 구현하는 클래스로 AlphaComposite가 있다.

 

AlphaComposite 객체를 생성하려면 static 멤버인 getInstance 메소드를 이용한다.

 

public static AlphaComposite getInstance(int rule, float alpha)

AlphaComposite 객체를 얻는다. rule은 색의 합성 규칙. alpha는 투명도( 0.0f에서 1.0f사이의 값을 가진다.)

 

합성 규칙으로는 CLEAR, SRC_IN, SRC_OVER 등이 있는데 대표적으로 SRC_OVER 규칙으로 합성하면 그리는 이미지가 반투명하게 그려진다. 투명도가 0.0f이면 이미지가 완전 투명하게 그려지며 1.0f이면 완전 불투명하게 그려진다.

 

Graphics2D 객체의 setComposite 메소드를 사용하면 합성 속성이 적용된다.

 

AlphaComposite alpha;

alpha = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f);

g2.setComposite(alpha);

 

다음 예제는 투명 효과를 테스트해 보는 것이다.

 

Composite1.java

 

import java.awt.*;

import java.awt.geom.*;

import java.awt.image.*;

public class Composite1 extends Frame{

  public Composite1(String title){

    super(title);

  }

  public void paint(Graphics g){

    Graphics2D g2=(Graphics2D)g;

 

    // 빨간 원을 그린다.

    g2.setPaint(Color.red);

    g2.fill(new Ellipse2D.Float(50,50, 100,100));

 

    // 파란색 Paint 설정

    g2.setPaint(Color.blue);

 

   // 투명도를 0.0에서 0.01씩 증가시키며 원을 그린다.

    for(float i=0.0f; i<=1.0f;i+=0.01f){

      g2.setComposite(

             AlphaComposite.getInstance(AlphaComposite.SRC_OVER, i)

      );

      g2.fill(new Ellipse2D.Float(100,50,100,100));

      try{

        Thread.sleep(100);        // 변화를 관찰하기 위해 간격을 둔다.

      }catch(Exception e){}

    }

  }

  public static void main(String []args){

    Frame f=new Composite1("투명 효과");

    f.setSize(250,200);

    f.setVisible(true);

  }

}


 

[그림 18-12] 투명 효과

 

 

Transform 속성

java2D 환경은 좌표를 자유롭게 변환할 수 있는 강력한 인터페이스를 기본으로 제공한다.

 

java.awt.geom 패키지의 AffineTransform 클래스는 좌표의 평행 이동, 회전, 스케일링(확대/축소), 반전(flip), 변형(shearing)의 좌표 변환을 구현한다. 이러한 좌표 변환은 3 × 3 행렬로 표현되고 이 행렬과 좌표 (x, y)를 곱하여 변형된 좌표 (x', y')를 구한다.

 

[ x']   [  m00  m01  m02  ] [ x ]   [ m00x + m01y + m02 ]

[ y'] = [  m10  m11  m12  ] [ y ] = [ m10x + m11y + m12 ]

[ 1 ]   [   0    0    1   ] [ 1 ]   [         1         ]

 

좌표 변환에 사용되는 3 × 3 행렬을 변환 행렬이라고 하며 변환 행렬 각 원소의 기능은 다음과 같다.

 

변환 행렬의 원소

기능

m00

x 좌표의 확대/축소

m10

y 좌표의 변형

m01

x 좌표의 변형(shearing)

m11

y 좌표의 확대/축소

m02

x 좌표의 평행 이동

m12

y 좌표의 평행 이동

 

좌표를 원하는 형태로 자유자재로 변환하려면 선형 대수에 대한 기초 지식이 있어야 할 것이다. 하지만 기본적인 좌표 변환은 AffineTransform 클래스와 Graphics2D가 제공하기 때문에 편리하게 이용할 수 있다.

 

 

AffineTransform 클래스의 유용한 생성자

 

public AffineTransform()

기본 AffineTransform 객체를 만든다.

public AffineTransform(double m00, double m10,

                        double m01, double m11,

                        double m02, double m12)

인수들을 변환 행렬로 하는 AffineTransform 객체를 만든다.

public AffineTransform(float[] flatmatrix)

public AffineTransform(double[] flatmatrix)

flatmatrix의 각 원소를  m00, m10, m01, m11, m02, m12로 하는 AffineTransform 객체를 만든다.

 

 setTransform 메소드를 사용하면 AffinTransform 객체를 Graphics2D에 적용할 수 있다.

 

AffineTransform affine=new AffineTransform(...);

...

g2.setTransform(affine);

 

 

Graphics2D도 다음과 같은 좌표 변환 메소드를 직접 제공하고 있어서 쉽게 좌표를 변환할 수 있다.

 

Graphics2D 클래스의 유용한 메소드

 

public abstract void translate(double tx, double ty);

좌표를 x축 방향으로 tx, y축 방향으로 ty만큼 평행 이동한다.

public abstract void rotate(double theta);

원점을 기준으로 각 theta만큼 회전한다. 각도의 단위는 radian이다.

public abstract void scale(double sx, double sy);

좌표를 x축 방향으로 sx, y축 방향으로 sy만큼 확대하거나 축소한다.

public abstract void shear(double shx, double shy);

좌표  y축을 shx, x축을 shy만큼 찌그러트린다.

 

 

다음은 프레임의 원점을 가운데로 평행 이동시키고 x축과 y축을 Line2D로 그려보는 예제이다.

 

Transform1.java

 

import java.awt.*;

import java.awt.geom.*;

public class Transform1 extends Frame{

  public Transform1(String title){

    super(title);

  }

  public void paint(Graphics g){

    Graphics2D g2=(Graphics2D)g;

    g2.translate(100, 100);                // 원점을 (100, 100)로 이동시킨다.

    g2.draw(new Line2D.Float(-100, 0, 100, 0));     // x축을 그린다.

    g2.draw(new Line2D.Float(0, -100, 0, 100));     // y축을 그린다.

    g2.fill(new Ellipse2D.Float(50, 50, 20, 20));      // (50, 50)에 원을 그린다.

  }

  public static void main(String []args){

    Frame f=new Transform1("평행 이동");

    f.setSize(200, 200);

    f.setVisible(true);

  }

}


 

[그림 18-13] 평행 이동

 

다음으로 원점을 중심으로 회전하는 변환에 대한 예제를 해보자. "시계 방향" 버튼을 누르면 시계 방향으로 좌표축이 15도 회전하고, "반 시계 방향" 버튼을 누르면 시계 반대 방향으로 좌표축이 15도 회전할 것이다.

 

Transform2.java

 

import java.awt.*;

import java.awt.geom.*;

import java.awt.event.*;

public class Transform2 extends Frame implements ActionListener{

  int theta;

  Button clockwise=new Button("시계 방향");

  Button counterClockwise=new Button("반 시계 방향");

  Graphics2D g2;

  public Transform2(String title){               // 생성자

    super(title);

    setLayout(new FlowLayout());              // 레이아웃 변경

    add(clockwise);                           // 버튼 추가

    add(counterClockwise);

    clockwise.addActionListener(this);          // 이벤트 처리

    counterClockwise.addActionListener(this);

  }

  public void actionPerformed(ActionEvent ae){

    if(ae.getSource()==clockwise)                  // "시계 방향" 버튼을 누르면

      theta=15;                                   // 회전각을 15도로 한다.

    else if(ae.getSource()==counterClockwise)     // "반 시계 방향" 버튼을 누르면

      theta=-15;                                  // 회전각을 -15도로 한다.

    repaint();                                    // paint를 호출한다.

  }

  public void paint(Graphics g){

    if(g2==null){

      g2=(Graphics2D)getGraphics();

      g2.translate(150,150);                         // 좌표 축 이동

    }

    g2.rotate(Math.toRadians(theta));              // theta만큼 회전한다.

    g2.draw(new Line2D.Float(-100, 0, 100, 0));     // 축과 원을 그린다.

    g2.draw(new Line2D.Float(0, -100, 0, 100));

    g2.fill(new Ellipse2D.Float(50, 50, 20, 20));

  }  

  public static void main(String []args){

    Frame f=new Transform2("좌표축 회전");

    f.setSize(300, 300);

    f.setVisible(true);

  }

}


 

[그림 18-14] 좌표축 회전

 

 

Graphics2D와 AffineTransform의 scale 메소드를 사용하면 좌표계를 확대, 축소, 또는 반전시킬 수 있다.

 

scale( 2 , 2 )

좌표계를 2배 확대한다.

scale( 0.5 ,0.5 )

좌표계를 1/2로 축소한다.

scale( 1, -1 )

좌표계를 x축을 중심으로 반전한다.

scale( -1, 1 )

좌표계를 y축을 중심으로 반전한다.

 

다음은 좌표축의 확대, 축소, 반전을 테스트해보는 것이다. 이전의 예제와 그 구성이 비슷하므로 이해하기 어렵지 않을 것이다.

 

Transform3.java

 

import java.awt.*;

import java.awt.geom.*;

import java.awt.event.*;

public class Transform3 extends Frame implements ActionListener{

  double scale=1;

  Button flipX=new Button("x축 반전");

  Button flipY=new Button("y축 반전");

  Button scaleUp=new Button("2배 확대");

  Button scaleDown=new Button("1/2로 축소");

  Graphics2D g2;

  public Transform3(String title){               // 생성자

    super(title);

    setLayout(new FlowLayout());

 

    add(flipX); add(flipY); add(scaleUp); add(scaleDown);

 

    flipX.addActionListener(this); flipY.addActionListener(this);

    scaleUp.addActionListener(this); scaleDown.addActionListener(this);

  }

  public void actionPerformed(ActionEvent ae){

    if(ae.getSource()==flipX)

      g2.scale(1, -1);      // 좌표계를 x축을 중심으로 반전한다

    else if(ae.getSource()==flipY)

      g2.scale(-1, 1);      // 좌표계를 y축을 중심으로 반전한다.

    else if(ae.getSource()==scaleUp)

      g2.scale(2, 2);       // 좌표계를 2배 확대한다.

    else if(ae.getSource()==scaleDown)

      g2.scale(0.5, 0.5);   // 좌표계를 1/2로 축소한다.

    repaint();

  }

  public void paint(Graphics g){

    if(g2==null){

      g2=(Graphics2D)getGraphics();

      g2.translate(150,150);

    }

    

    g2.draw(new Line2D.Float(-100, 0, 100, 0));

    g2.draw(new Line2D.Float(0, -100, 0, 100));

    g2.fill(new Ellipse2D.Float(50, 50, 20, 20));

  }  

  public static void main(String []args){

    Frame f=new Transform3("스케일링");

    f.setSize(300, 300);

    f.setVisible(true);

  }

}


 

[그림 18-15] 스케일링

 

 

 

Clip 속성

클립(Clip)이란 그리기 작업이 수행될 영역을 말한다. 일단 클립 영역이 지정되면 클립 영역 이외의 영역에는 그려지지 않는다. 따라서 관심 있는 영역에만 그리기 작업을 하려면 클립 영역으로 정하면 된다. 사각형 모양의 영역뿐만 아니라 Shape를 구현하는 도형도 클립 영역이 될 수 있다.

 

 

Graphics 클래스의 유용한 메소드

 

public abstract Rectangle getClipBounds();

paint 메소드가 호출되었을 때 새로 그려질 영역을 반환한다.

public abstract void setClip(int x, int y, int width, int height);

사각형 [x, y, width, height]를 클립 영역으로 정한다.

public abstract void setClip(Shape clip);

도형 clip을 클립 영역으로 정한다.

public abstract Shape getClip();

클립 영역을 Shape형으로 반환한다.

public boolean hitClip(int x, int y, int width, int height)

[x, y, width, height]와 현재의 클립 영역이 겹치면 true를, 아니면 false를 반환한다.

 

다음은 사진의 이미지를 타원 모양의 클립 영역에 그리는 예제이다.

 

Clip1.java

 

import java.awt.*;

import java.awt.geom.*;

public class Clip1 extends Frame{

  Image img;

  public Clip1(String title){                  // 생성자

    super(title);

 

    // 이미지 객체를 생성한다.

    img=getToolkit().getImage(getClass().getResource("/images/baby.gif"));

  }

  public void paint(Graphics g){

    // 클립 영역을 지정하고 이미지를 그린다.

    g.setClip(new Ellipse2D.Float(50,30,180,260));

    g.drawImage(img, 0,0, this);

  }

  public static void main(String []args){

    Frame f=new Clip1("클리핑");

    f.setSize(300, 300);

    f.setVisible(true);

  }

}


 

[그림 18-16] 클리핑

 

 

 

더블 버퍼링

더블 버퍼링은 도형이나 그림을 컴포넌트에 바로 그리지 않고 다른 곳의 버퍼에 도형이나 그림을 그린 다음에 버퍼에 그려진 이미지를 컴포넌트에 그리는 기법을 말한다. 애니메이션이나 게임과 같이 짧은 시간에 많은 이미지를 그리면 화면에 깜빡임이 생기거나 그리는 작업 과정이 보인다. 이런 경우에 더블 버퍼링을 이용하여 한번에 그리면 화면에 그리는 작업 과정이 보이지 않으며 화면의 깜빡임이 줄어든다.

 

[그림 18-17] 더블 버퍼링

 

여기서 버퍼란 Image 클래스의 객체로 구현된다. Image 객체를 생성한 후, 그 Image 객체의 그래픽 객체를 얻어와서 그리기 작업을 하면 된다. 이때 버퍼의 크기는 컴포넌트의 크기와 같아야 할 것이다.

 

Image buffer=컴포넌트.createImage(width,height);  // 버퍼를 만든다.

Graphics g=buffer.getGraphics();             // 버퍼의 그래픽 객체를 얻는다.

g.drawImage(...); ...                          // 버퍼에 이미지를 그린다.

...drawImage(buff, 0, 0, ...);                 // 버퍼를 컴포넌트에 그린다.

 

더블 버퍼링을 사용하면 이미지를 버퍼에 그리는 동안 컴포넌트에는 그리지 않기 때문에 컴포넌트에 바로 그리는 경우보다 속도 면에서 조금은 늦을 것이다. 따라서 그리는 작업을 최소화하여 반응속도를 개선하는 것이 좋다.

 

다음은 더블 버퍼링을 사용한 경우와 그렇지 않은 경우를 비교하기 위한 예제이다. 시스템에 따라 정도의 차이가 있으나 더블 버퍼링을 하지 않은 경우에 화면이 깜빡이는 것을 확인할 수 있다.

 

DBuffering1.java

 

import java.awt.*;

import java.awt.event.*;

public class DBuffering1 extends Frame implements ActionListener{

  Button b1= new Button("바로 그리기");

  Button b2= new Button("버퍼 사용하기");

  public DBuffering1(String title){               // 생성자

    super(title);

    setLayout(new FlowLayout());

    add(b1);                                    // 버튼을 추가한다.

    add(b2);

    b1.addActionListener(this);                   // 이벤트 처리를 한다.

    b2.addActionListener(this);

  }

  

  // 그래픽 객체 g에 color색으로 직선을 긋는 메소드

  private void drawLines(Graphics g, Color color){

    g.setColor(color);

    for(int i=0;i<getHeight();i++){                   // 선으로 채운다.

      g.drawLine(0,i,getWidth(),i);

    }

  }

  private void drawOnFrame(){              // 프레임에 바로 그리는 메소드

    Graphics g=getGraphics();

    g.clearRect(0,0,getWidth(),getHeight());    // 프레임에 그려진 이미지를 지운다.

    drawLines(g,Color.red);                   // 프레임에 빨간 색으로 선을 그린다.

  }

  private void drawOnBuffer(){        // 버퍼에 그려서 프레임에 복사하는 메소드

  

    // 프레임과 같은 크기의 버퍼를 생성한다.

    Image buffer=createImage(getWidth(), getHeight());

  

    Graphics g=buffer.getGraphics();         // 버퍼의 그래픽 객체를 얻는다.

    drawLines(g, Color.blue);                // 버퍼에 파란색으로 선을 그린다.

 

   // 버퍼에 그려진 이미지를 프레임에 그린다.

    getGraphics().drawImage(buffer,0,0,this);

  }

  public void actionPerformed(ActionEvent ae){

    if(ae.getSource()==b1)               // b1을 누르면

      drawOnFrame();                  // 프레임에 바로 그린다.

    else if(ae.getSource()==b2)          // b2를 누르면

      drawOnBuffer();                 // 버퍼에 그려서 프레임에 복사한다.

  }

  public static void main(String []args){

    Frame f=new DBuffering1("더블 버퍼링");

    f.setSize(300, 300);

    f.setVisible(true);

  }

}


 

[그림 18-18] 더블 버퍼링 테스트

 

 

java2D 그래픽은 나중에 Applet 게임과 네트워크 게임을 만들 때 응용되어진다.

 

 

 

[출처] 2D Graphics|작성자 원준아빠



구글이 마이크로소프트(MS)의 웹브라우저인 ‘인터넷 익스플로러(IE) 6’ 버전 지원을 중단키로 하면서 국내 누리꾼들이 비상이 걸렸다. IE 6 사용자가 줄어들고 있는 외국과 달리 국내에선 여전히 IE 6 이용자 수가 많기 때문. 심지어 지난해 말부터는 다시 늘어나 누리꾼 절반 가까이가 IE 6를 쓰고 있는 실정이다.

■구글 “익스플로러 6, 3월부터 지원중단”

구글은 G메일과 구글 독스(Docs), 캘린더 등 자사 서비스가 오는 3월 1일부터 IE 6에 대한 지원을 중단할 예정이라고 지난달 30일(현지시간) 공지했다. 그간 구글은 유튜브와 자사 사이트들에서 익스플로러 사용자들에게 버전을 업그레이드할 것을 권고해 왔었다. IE 6가 웹표준을 지키지 않는데다 보안이 취약하다는 것이다.

그러나 구글이 아예 IE 6에 대한 배척에 나선 직접적인 계기는 최근 벌어진 중국에서의 해킹 사건 때문이다. 구글은 이날 공지를 통해 “최근 중국으로부터 브라우저의 취약점을 파고든 정교한 사이버 공격으로 문제가 발생했다”며 “향후 비슷한 사건의 방지를 위해서도 사용자들이 브라우저를 업그레이드할 것을 권장한다”고 밝혔다.
구글은 인터넷 익스플로러7 버전 이상, 파이어폭스 3.0 이상, 구글 크롬 4.0 이상, 애플 사파리 3.0 이상의 브라우저를 사용해 줄 것을 당부했다. 구글의 이런 조치에 앞서 프랑스와 독일은 자국민들에게 인터넷 익스플로러 사용을 자제하라고 권고한 바 있다.

■IE 6 사용비율, 세계 13%·국내 50%

IE 6는 2000년대 초반 돌풍을 일으킨 윈도XP 운영체제에 끼워 팔렸다. 그러나 뒤이어 나온 브라우저들에 비해 현저히 보안이 취약하고 웹표준을 지키지 않아 인터넷 환경을 악화시킨다는 지적을 받아 왔다. 심지어 제작사인 MS가 사용자들에게 익스플로러8 등 웹표준과 보안성을 강화한 제품으로 변경할 것을 권하고 있을 정도다.

이 때문에 IE 6에 대한 퇴출 운동은 그간 전세계적으로 진행돼 왔다. 해외에서 일어난 ‘IE 6 노모어(Nomore)’, ‘IE 6 머스트 다이(Must Die)’ 등의 캠페인에 이어 지난해 말엔 국내에서도 한 웹 개발자가 ‘익스플로러6 이제 그만’이라는 홈페이지(ie6nomore.kr)를 열고 100만명을 목표로 서명운동과 배너달기에 나서기도 했다.

그러나 이같은 캠페인으로 전세계 점유율은 지속적인 하락 추세를 그려왔지만 국내에서는 오히려 지난해 9월 이후 IE 6 사용자의 비율이 오르는 기현상이 벌어지고 있다. 시장조사기관 스탯카운터에 따르면 전세계 IE 6 이용자 비율은 지난해 1월 23%에서 올들어 13.4%로 꾸준히 감소했지만 국내 IE 6 이용자 비율은 지난해 9월 39%로 저점을 찍은 뒤 올 1월에는 50%로 다시 올랐다.

구글코리아 관계자는 “당장 IE 6로 구글 서비스를 이용하지 못하는 것은 아니나 앞으로 구글 서비스의 새로운 기능이 나와도 IE 6에서는 이를 사용하지 못할 가능성이 있다”며 “또 IE 6 사용으로 발생하는 버그를 수정하는 등의 지원책도 더 이상 제공되지 않을 것”이라고 설명했다. 이 관계자는 “국내 IE 6 이용자들도 버전을 업그레이드하거나 다른 브라우저를 이용할 것을 권장한다”고 덧붙였다.

[디자인 패턴]

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

 디자인 패턴은 많은 프로그래머들이 성공적인 프로젝트에서 무수히 반복해 사용했던 문제 해결 기술을 발견해 정형화한 것이다.
 각 패턴은 특정 맥락의 문제를 효과적으로 해결할 수 있게 해주며, 패턴들은 해결하는

 문제의 맥락(GoF의 용어로는 Intent)이 다르다.

 분명 패턴을 잘 이해하고 사용하면, 수많은 개발자, 연구자들의 “아하(Aha)!” 경험을

 (그들이 들였던 노력에 비해서는) 손쉽게 사용할 수 있게 된다.
 하지만 패턴은 빈번히 발생하는 ‘특정’ 문제에 대해 적용할 수 있는 해결책이기
 때문에 객체들 간의 게임에 법칙에 관한 일반 룰까지 알려주지는 않는다.

 패턴이 좋은 게임의 규칙을 이용해 객체들의 관계를 맺고 있음은 분명하다
 (좋은 게임의 규칙을 사용하고 있지 않다면, 특정 문제에 대해서도 좋은 해결책이 나올 수 없다).
 그런데 이 때 패턴이 암묵적으로 사용하는 게임의 법칙을 모르면,
 패턴이란 고도의 게임 전술을 유용하게 구사할 수 없게 된다.
 패턴은 성공한 프로젝트 코드들에서 발견, 일반화되는 과정에서 코드에서
 디자인으로 추상화됐기 때문에 이들 다시 코드로 구체화하려면 패턴이 사용하는
 게임의 법칙을 알아야 한다.
 이를 모른다면 장님이 코끼리 다리 만지듯 패턴을 사용하게 될 위험이 있다.
 패턴을 사용해 프로그램을 만들었는데, 괜히 복잡해지기만 하던 걸? 글쎄….

 

===============================================================================================
[객체지향 설계의 원칙]
 
- 소프트웨어 설계 모델의 메타적인 원리가 디자인 패턴의 단위라고 한다면
  디자인 패턴에 등장하는 좋은 구조들에 대한 메타적인 원리가 (ISP, SRP, DIP, OCP, LSP)정도 된다.

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

 

- LSP::리스코프 치환 원리
  "서브 타입은 언제나 기반 타입으로 교체할 수 있어야 한다."
  상속은 구현 상속(extends 관계)이든 인터페이스 상속(implements 관계)이든
  궁극적으로는 다형성을 통한 확장성 획득을 목표로 한다.
  LSP 원칙도 역시 서브 클래스가 확장에 대한 인터페이스를 준수해야 함을 의미한다.
  다형성과 확장성을 극대화하려면 하위 클래스를 사용하는 것보다 상위의 클래스(인터페이스)를 이용하는 것이 좋다.


주의점::

 “서브 클래스에서는 기반 클래스의 사전 조건과 같거나 더 약한 수준에서 사전 조건을 대체할 수 있고,
   기반 클래스의 사후 조건과 같거나 더 강한 수준에서 사후 조건을 대체할 수 있다.”

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

 

- SRP::단일 책임 원리
  SRP는 하나의 클래스에 한 가지 책임을 가르치는 원칙이다.
  우리는 설계 관점에서 우리가 인식하지 못하는 SRP 위반을 자주 하게 된다.
  이 위반을 경계하기 위해 깊은 통찰력이 필요하지도 않다.
  단지 머리에 ‘책임’이란 단어를 상기하는 습관이면 된다

 

  적절한 책임 분배는 객체지향 원리들의 대전제 격인 OCP뿐 아니라 다른 원리들을 적용하는 기초가 되어준다

  설계자는 분리의 ‘크기(granularity)’를 고민한다.


  작은 단위로 섬세하게 사용할 수 있는 미세단위(fine-grained)로 구분할 것인가, 아니면 단순하지만

  입도가 큰(COARSE-GRAINED) 단위로 구분할 것인가에 대해서 말이다.
  이 문제의 경우 애플리케이션에서 문제 영역이 각각 서로 다른 케이스 바이 케이스로 이루어지기 때문에 명백히

  일관적으로 적용할 가이드라인을 제공하기 힘들다.

  하지만 그 기준은 대상에 대한 복잡도, 크기, 용도가 된다.
  복잡도가 높고 부피가 큰데 반해 그 용법이 단순하다면 COARSE-GRAINED가 적합하다.
  역으로 복잡도가 낮고 부피가 작으며 용법이 다양하다면 fine-grained가 적합하다.

 

 SRP 위반의 악취는
   -‘여러 원인에 의한 변경(divergent change)’
   -‘산탄총 수술(shotgun surgery)’
  을 들 수 있다.

 

  여러 원인에 의한 변경은 한 클래스를 여러 가지 다른 이유로 고칠 필요가 있을 때 발생한다.
  즉, 하나의 클래스에 여러 책임이 혼재하고 있어서 하나의 책임의 변화가 다른 책임에게 영향을 준다.

  산탄총 수술이란 하나의 책임이 여러 클래스에 분산되어 있기 때문에 발생한다.
  한 클래스가 너무 많은 책임을 맡고 있어도 곤란하지만,
  책임을 식별하지 못해 이를 담당할 클래스를 만들지 않고 여러 클래스에 흩뿌려 놓는 것 또한 문제가 있다.
  이는 보통 프로그램의 전체 책임을 올바로 분담하지 못해서 발생하게 된다.


 SRP를 적용하면 클래스의 숫자가 늘 수는 있다.
 하지만 클래스 숫자의 증가가 프로그램의 복잡도 증가와 비례하는 것은 아니다.
 오히려 SRP를 잘 따르는 프로그램은 적절한 책임 분배로 인해 클래스 숫자와
 프로그램의 복잡도가 반비례하는 경향이 있다고도 할 수 있게 된다.


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

 

- OCP::개방 폐쇄 원리
 소프트웨어 구성 요소(컴포넌트, 클래스, 모듈, 함수)는 확장에 대해서는 개방돼야 하지만
 변경에 대해서는 폐쇄되어야 한다고 말한다.
 변경을 위한 비용은 가능한 줄이고 확장을 위한 비용은 가능한 극대화해야 한다는 의미다.
 목적은 앞의 예처럼 객체간의 관계를 단순화해 복잡도를 줄이고, 확장·변경에 따른 충격을 줄이는 데 있다.

 

주의점::

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

 모듈 영역에서 예측하지 못한 확장 타입을 만났을 때 인터페이스 변경하려는 안과 어댑터를 사용하려는

 안 사이에서 갈등하게 된다.
 위의 두 예에서처럼 변경의 충격이 적은 후자를 택하는 경우가 대부분이다.
 한 번 정해진 인터페이스는 시간이 갈수록 사용하는 모듈이 많아지기 때문에 바꾸는

 데 엄청난 출혈을 각오해야 한다.
 자바의 deprecated API가 대표적인 경우다.

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

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


=====================================================================================================
High Cohesion, Loose Coupling::높은 응집도, 낮은 결합도
=====================================================================================================

 

낮은 응집도를 갖는 구조는 변경이나,확장 단계에서 많은 비용을 지불해야 하며 높은 결합도의 경우도 마찬가지이다.

응집도는 ‘하나의 클래스가 하나의 기능(책임)을 온전히 순도 높게 담당하고 있는 정도’를 의미하며
이들은 서로 조화될수록 그 구조는 단순해진다.

응집도가 높은 동네에서 내부 개체가 변했을 때 다른 개체에 충격을 주는 것은 오히려 당연한 징후이다.
이들은 하나의 책임아래 서로 유기적인 관계를 갖고 있기 때문에 내부 개체가 변했을 때 다른 개체의 변경 확률이 높아진다.
마치 예쁜 부츠를 사면 부츠에 어울리는 치마를 입어야 하듯이…

응집도의 종류는 다양한데 다음은 권장할 만한 순기능적 응집 관계들이다.

 

* 기능적 응집(Functional Cohesion)
일관된 기능들이 집합된 경우를 말하며 <그림 3>의 데이터 맵퍼는 DB 처리라는 기능 항목의 높은 응집성을 갖는다.

 

* 순차적 응집(Sequential Cohesion)
한 클래스 내에 등장하는 하나의 소작업(메쏘드)의 결과가 다음 소작업(메쏘드)의
입력으로 사용되는 관계(파이프라인 방식의 처리 체인 관계).

 

* 교환적 응집(Communicational Cohesion)
동일한 입력과 출력 자료를 제공하는 메쏘드들의 집합을 말하며,
팩토리 클래스는 전형적인 교환적 응집도가 높은 인터페이스를 갖는다.

 

* 절차적 응집(Procedural Cohesion)
순서적으로 처리되어야 하는 소작업(메쏘드)들이 그 순서에 의해 정렬되는 응집관계

 

* 시간적 응집(Temporal Cohesion)
시간의 흐름에 따라 작업 순서가 정렬되는 응집관계

 

* 논리적 응집(Logical Cohesion)
유사한 성격의 개체들이 모여 있을 경우를 말하며 java.io 클래스들의 경우가 대표적인 예이다.

 

이와 반해 결합도는 ‘클래스간의 서로 다른 책임들이 얽혀 있어서 상호의존도가 높은 정도’
를 의미하며 이들이 조합될수록 코드를 보기가 괴로워진다.
이유는 서로 다른 책임이 산만하고 복잡하게 얽혀있기 때문에 가독성이 떨어지고 유지보수가 곤란해지기 때문이다.
이유는 필요 없는 의존성에 있다.
마치 키보드의 자판 하나가 고장나도 키보드 전체를 바꿔야 하는 것처럼.
하나의 변경이 엄청난 민폐를 야기하는 관계이다.


다음은 수용할 수 있는 수준의 결합 관계들이다.

 

* 자료 결합(Data Coupling)
두 개 이상의 클래스가 매개변수에 의해서 결합 관계를 가지므로 낮은 수준의 결합도로 연관되는 경우

 

* 스탬프 결합(Stamp Coupling)
자료 결합의 경우에서 매개변수 일부만을 사용하는 경우

 

* 제어 결합 (Control Coupling)
두 클래스간의 제어 이동이 매개변수를 이용하여 사용되는 경우로 커맨드 패턴이 대표적인 사례이다.


+ Recent posts