본문 바로가기

OOP, FP/스칼라로 만드는 테트리스

03. [스칼라 테트리스] GUI, 스윙 컴포넌트 관련 클래스 분석 -1-

오늘은 UI를 분석하기 전에, 스칼라의 Swing에 대해 공부했다.

스칼라는 JVM 기반으로 구동된다. 자바의 광범위한 라이브러리를 가져다 쓸 수 있으며,

문법 또한 자바와 상단 부분 비슷하다. 아직 스칼라 책을 두번정도 읽어봤을 뿐, 딱히 무언가를 만들어 본 적이 없다. 뿐만 아니라, 스칼라로 프로그램을 만들어본 경험이 없기 때문에 트레잇, 패턴 매칭, 높은 다형성과 같은 스칼라의 장점이 잘 이해가 가질 않는다.


스칼라의 장점은 무엇일까?

일반적인 함수형 언어와 같이 의도하지 않았던 부작용으로 부터 발생하는 이슈들을 줄이기 위해 함수형 프로그래밍 테크닉을 익힐 수 있다. 부작용은 이전에 함수형 자바스크립트에서 공부한 부수 효과와 같은 것이다.

함수에서 출력(반환) 이 아닌 외부의 상태에 영향을 미치는 효과를 부작용 또는 부수 효과라고 한다.

함수형은 기본적으로 불변 자료구조를 추구하며, 부수 효과를 갖지 않는 순수 함수로 프로그래밍 하는것을 지양한다. 하지만, 스칼라는 불변 val와 가변 var 키워드를 제공한다. var와 같은 가변성을 제공하는 것은, 스칼라가 자바 개발자들도 친근하게 접근 가능하도록 하기 위함이라고 생각한다. 자바스크립트에서 let과 var보다 const를 쓰는 것이 아름답다고 하듯이, 스칼라에서도 var에서 val로 넘어가는 것이 목표라고 본다.


또한, OOP에서 사용하던 코드보다 더 간단해지고 표현력이 있어진다.

JAVA, C#, C++와 같은 정적 타입 언어에 익숙하다면 타입과 구두점, 정형화된 코드를 없앨 수 있다.

스칼라는 정적 언어이지만 타입 추론을 가능케 하여 동적 언어처럼 보인다.

명백하게 타입을 구체화하지 않아도 어떤 타입 불일치던간에 코드가 실행되기 전에 잡아낸다.

스칼라를 사용하면 자바나 C++처럼 타입 안정성을 갖춘 고성능 언어의 표현을 루비나 파이썬처럼 짧고 표현적인 코드로 작성할 수 있다. 위와 같은 이유로 인해 함수형 프로그래밍의 장점을 모두 누리면서도 타입 안정성을 갖춘 표현적인 코드를 작성할 수 있다.


이제 다시 테트리스의 GUI 코드 분석

  1. ScalTris라는 메인 클래스로 화면 틀을 구성한다.

    1. SimpleSwingApplication이라는 추상 클래스를 상속한다.

      1. top이라는 추상 메소드를 오버라이드 하여 프레임을 구성한다.

        1. 프레임은 타이틀, 메뉴 바, 버튼 등 모든 스윙 컴포넌트를 담는다.

    2. TetrisPanel 이라는 클래스의 인스턴스를 생성한다.

      1. 패널은 프레임에 포함된다.

    3. ScalTris는 프레임을 생성하고, TetrisPanel이라는 패널을 포함시킨다.

      1. 패널과 프레임의 차이는 무엇일까?

        1. 우선 둘 다 UI 컴포넌트를 담을 수 있는 틀이다.

          1. 프레임

            1. 프레임은 자체적으로 존재할 수 있는 독립적인 객체이다.

            2. 윈도우 창의 크기를 수정할 수 있고, 움직일 수 있다.

            3. 윈도우 창은 타이틀바와 종료 버튼이 있다.

            4. 일반적으로 패널이 포함되어 있는 최상위 컨테이너이다.

          2. 패널

            1. 패널은 독자적으로 존재할 수 없다.

            2. 패널을 표시하거나 존재하기 위헤서는 반드시 프레임이나 윈도우와 같은 부모 컨테이너가 필요하다.

            3. 프레임 또는 다른 패널의 내부 영역이다.

            4. 컴포넌트들을 그룹핑하는데 사용한다.

    4. AWTWindow 의 pack 이라는 메소드를 이용하여 서브 컴포넌트의 사이즈 및 레이아웃에 맞추어, window 사이즈를 변경한다.

    5. 즉, 프레임을 담당하는 Scaltris 클래스는 윈도우 프레임을 구성하며, 상단의 메뉴바와 게임 패널을 구성한다.

      1. 메뉴바의 메뉴에는 네가지가 있다.

        1. 새 게임

        2. 중지

        3. 도움말

        4. 종료

  2. TetrisPanel이라는 클래스로 게임 패널을 구성한다.

    1. Panel이라는 추상 클래스를 상속한다.

      1. paintComponent 이라는 추상 메소드를 오버라이드 하여 내부 컴포넌트를 구성한ㄷ.

    2. BoardController 라는 클래스의 인스턴스를 생성한다.

      1. 키 입력, 게임 종료 등의 이벤트 감지를 위해 사용된다

      2. 현재 화면에 생성된 블록을 생성한다.

      3. 다음에 생성될 블록을 생성한다.

    3. preferredSize 메소드를 이용하여 패널의 사이즈를 조정한다.

    4. Graphics2D 객체를 이용하여 배경색과 그 영역을 지정하며 이미지를 그린다.

      1. RescaleOp 클래스를 이용하여 Block 이미지 픽셀 단위로 재조정한다.

        1. 이 클래스는 소스 이미지이의 데이터를 픽셀 단위로 재조정한다.

    5. BoardController로부터 Block 인스턴스를 받아서 블록 이미지를 그린다.

      1. 기본적으로 블록의 position(x, y)를 이용하여 블록 이미지를 그린다.

      2. 가장 먼저 고스트 블록을 그린다.

        1. 고스트 블록이란 현재 블록이 떨어질 위치에 투명 스케일을 씌워 그린 블록이다.

      3. 두번 째로, 현재 생성된 블록을 그린다.

      4. 세번 째로, 다음으로 생성될 블록을 그린다.

      5. 마지막으로 현재 스코어를 그린다.


포팅 글을 작성하면서 문득, 함수와 메소드의 차이가 생각났다.

메소드는 특정 클래스의 인스턴스에서만 호출할 수 있는 기능이며, 함수는 언제 어디서든 호출 할 수 있는 기능이다.

OOP인 자바를 기반으로하는 스칼라에서는 어떤 것이 함수고 어떤 것이 메소드일까?

  1. 스칼라는 객체-함수형 프로그래밍이라고 불린다.

    1. 메소드

      1. 일반적인 객체 지향 프로그래밍의 메소드와 같다.

      2. 특정 클래스에서 정의된다.

      3. 특정 클래스의 인스턴스에서만 호출할 수 있다.

    2. 함수

      1. 스칼라에서 함수는 객체이다.

      2. apply 메소드를 사용하면 클래스나 객체의 용도가 주로 하나만 있는 경우를 표현할 수 있다.


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

class Foo { }

object FooMaker {

   def apply() = new Foo // FooMaker의 apply()는 Foo 클래스의 인스턴스를 반환한다.

}

val newFoo = FooMaker()

// newFoo: Foo = foo@5b83f762

// 위와 같이 FooMaker()를 호출하면 자동으로 apply()를 호출한다.

class Bar {

   def apply() = 0 // Bar의 apply()는 Int = 0을 반환한다.

}

val bar = new Bar

// bar: Bar = Bar@47711479

bar()

// Int = 0

Colored by Color Scripter

cs


      1. 함수는 Trait의 집합이다.

        1. 인자를 하나만 받는 함수는 Function1 트레잇의 인스턴스이다.

        2. Trait에는 apply()가 정의되어 있다.


1

2

3

4

5

6

7

object addOne extends Function1[Int, Int] {

   def apply(m: Int): Int = m + 1

}

// defined module addOne

addOne(1)

// res2: Int = 2

cs


          1. 이로 인해 함수를 호출하듯 객체를 호출할 수 잇다.

  1. 스칼라는 apply()를 통한 편리한 문법을 통해 객체와 함수 프로그래밍 양쪽을 잘 통합할 수 있다.

    1. 클래스를 여기저기 넘기면서 함수처럼 호출해 사용할 수 있다.

    2. 스칼라에서 함수는 한꺼풀 벗겨보면 단지 클래스의 인스턴스일 뿐이다.

  2. 그렇다면, 클래스의 메소드를 정의할 때마다 실제로 Function의 인스턴스가 만들어지는 것일까?

    1. 결론부터 말하자면 그렇지 않다.

    2. 클래스 내부의 메소드는 메소드이다.

      1. 일반적인 클래스를 만들고 메소드를 정의하면 그 자체는 함수가 아닌 메소드란 소리다.

      2. 그런 클래스에서 def로 메소드를 정의할 때마다 Function의 인스턴스를 만들면 굉장히 비효율적이다.

    3. 스칼라 인터프리터에서 정의한 개별 메소드가 Function의 인스턴스이다.

    4. 예외로 Function을 확장한 클래스를 만들어 함수를 정의할 수 있다.


1

2

3

4

5

6

7

8

9

class AddOne extends Function1[Int, Int] {

   def apply(m: Int): Int = m + 1

}

// defined class AddOne

val plusOne = new AddOne()

// plusOne: AddOne = <function1>

plusOne(1)

// res0: Int = 2

cs



이번 포팅에서는 화면 그리기와 관련된 클래스만 분석했다.

지금까지 전체 분석, 자료 구조, 그리기 기능에 관해서 분석했다.

다음 포팅에서는 이벤트와 관련된 클래스를 분석해야곘다.