[Scala] 패키지와 임포트

smlee·2023년 8월 17일
0

Scala

목록 보기
18/37
post-thumbnail

이 글은 Programming in Scala 4/e를 읽고 작성한 글입니다.


규모가 큰 프로그램을 작성할 때는 프로그램의 여러 부분이 서로 의존하는 정도를 나타내는 커플링(coupling)을 최소화하는 것이 중요하다.
프로그램의 한 부분에서 언뜻 보기에 아무 문제 없을 것 같은 코드를 조금 변경했는데, 다른 부분에 엄청난 문제가 생길 수 있다. 이런 위험을 커플링을 줄임으로써 감소시킬 수 있다.
커플링을 최소화하는 방법 중 하나는 모듈화 스타일로 프로그램을 작성하는 것이다.

모듈화 스타일
프로그램을 더 작은 여러 모듈로 나눈다. 각 모듈에는 내부외부가 있다.
모듈의 내부(구현)에 대해 작업할 때는 같을 모듈을 가지고 작업하는 프로그래머와 협력하면 된다.
모듈의 외부(인터페이스)를 변경해야 하는 경우에만 다른 모듈을 가지고 협력하면 된다.

패키지 안에 코드 작성하기

스칼라 코드는 Java 플랫폼의 전역 패키지(global package) 계층 안에 있다.
현 챕터 이전의 예제 코드들은 모두 이름 없는 패키지 안에서 작성했었다. 스칼라에서는 두 가지 방법으로 이름이 있는 패키지 안에 코드를 작성할 수 있다.

(1) package 절 사용

package bobsrockets.navigation

class Navigator

이 예시문의 package절은 bobsrockets.navigation패키지 안에 Navigator라는 클래스를 넣는 코드이다.
위의 패키지절에서 bobsrockets라는 조직/단체에서 개발한 navigation 소프트웨어라는 것을 짐작할 수 있다.

참고
스칼라 코드는 자바 생태 시스템의 일부이므로 공개하는 패키지 이름을 만들 때 자바의 관습대로 도메인 이름을 역으로 사용하는 것이 좋다. 따라서 위 패키지절의 더 좋은 이름은 com.bobsrockets.navigation이 더 좋을 것이다.

(2) 패키징(packaging)

C#의 네임스페이스와 유사한 형태로 package절 다음에 중괄호를 넣는 방법 역시 패키지 안에 코드를 작성하는 방법이다.

package bobsrockets.navigation {
	class Navigator
}

위와 같이 packag절 다음 중괄호가 오고 중괄호 안에 있는 정의는 모두 해당 패키지 속하는 것이다.

간단한 예시라면 pacakge절을 사용할 것이며, 한 파일 안에 여러 패키지를 넣을 때는 패키징을 사용할 것이다.

package bobsrockets {
	package navigation {
    	// bobsrockets.navigation 패키지 안
        
        class Navigator
        
        package tests {
        	class NavigatorSuite
        }
    }
}

위의 코드는 같은 파일 안에 테스트 코드를 포함시키되 별도의 패키지에 테스트 코드를 집어넣는 것이다. 즉, 커플링을 최소화하는 것이다.

간결하게 관련 코드에 접근하기

코드를 패키지 계층으로 나누는 이유는 코드를 훑어볼 때 도움을 주기 위해서도 있지만, 컴파일러도 같은 패키지 안에 있는 코드가 서로 관련 있음을 알 수 있게 하기 때문이다.
이러한 연결성을 통해 전체 경로를 포함하지 않는 간단한 이름을 사용할 수 있다.

package bobsrockets {
	package navigation {
    	class Navigator {
        	val map = new StarMap
        }
        
        class StarMap
    }
    
    class Shop {
    	// bobsrockets.navigation.Navigator로 전체 경로를 쓸 필요가 없다. 
    	val nav = new navigation.Navigator
    }
    
    package fleets {
    	class Fleet {
        	//bobsrockets.Ship이라는 전체 경로를 쓸 필요가 없다.
            
            def addShip() = new Ship
        }
    }
}

위와 같이 전체 경로를 포함하지 않아도 참조할 수 있다.


프로그래밍을 하다보면, 패키지들이 뒤섞여서 상호참조를 하는 경우가 발생하곤 한다.

// launch.scala 파일

package launch {
	class Booster3
}

// bobsrockets.scala 파일
package bobsrockets{
	package navigation {
    	package launch {
        	class Booster1
        }
        
        class MissionControl {
        	val booster1 = new launch.Booster1
            val booster2 = new bobsrockets.launch.Booster2
            val booster3 = new _root_.launch.Booster3
        }
    }
    
    package launch {
    	class Booster2
    }
}

위 코드에서 우리는 _root_라는 코드가 보일 것이다.
_root_는 스칼라가 제공하는 사용자가 작성한 모든 패키지 외부에 존재하는 패키지이다.
즉, 최상위 패키지는 _root_의 멤버라고 생각하면 된다.

임포트

스칼라에서 패키지와 그 멤버는 import 절을 통해 불러올 수 있다.

package bobsdelights

abstract class Fruit {
	val name: String
    val color: String
}

object Fruits {
	object Apple extends Fruit("apple", "red")
    object Orange extends Fruit("orange", "orange")
    object Pear extends Fruit("pear", "yellowish")
    
    val menu = List(Apple, Orange, Pear)
}

위와 같은 패키지가 있다고 생각해보자.

// Fruit에 간단하게 접근
import bobsdelights.Fruit

// bobsdelights의 모든 멤버에 간단하게 접근
import bobsdelights._

// Fruits의 모든 멤버에 간단하게 접근
import bobsdelights.Fruits._

위와 같은 import문들로 필요에 따라 접근할 수 있다.

첫번째 import문인 import bobsdelights.Fruit는 자바의 상글타입 임포트에 해당한다.
두번째 import문은 자바의 주문식 임포트와 같다. Java에서는 *을 사용하는 것과 달리 스칼라에서는 _을 사용한다.
세번째 임포트 절은 정적 클래스 필드를 불러오는 자바 임포트에 속한다.

스칼라의 import
위의 예시만 보면 스칼라와 자바의 import와 같다고 생각할 수 있지만, 크게 3가지가 차이 난다.
1. Scala는 어느곳에서나 나타날 수 있다.
2. 패키지 뿐만 아니라 싱글톤 또는 일반 객체도 참조할 수 있다.
3. 불러온 멤버 이름을 숨기거나 다른 이름을 지칭할 수 있다.

스칼라의 유연한 import

import Fruits.{Apple, Orange}

위와 같이 중괄호로 임포트 셀렉터(import selector)를 사용하면 원하는 멤버들만 임포트하며, 멤버들을 감추거나 이름을 바꿀 수 있다.

import Fruits.{Apple => McIntosh, Orange}

위의 임포트 셀렉터는 Apple이라는 객체를 Fruits.Apple이나 McIntosh로 이름을 바꾸어 참조할 수 있는 것이다.
즉, =>를 사용하여 이름을 바꾸어 임포트할 수 있는 것이다.

import Fruits.{Pear=>_, _}

그렇다면 위의 임포트문은 어떻게 동작할까? 이는 Pear를 제외한 모든 구문을 불러온다는 것이다.

원래이름 => _절은 불러올 이름 중에서 "원래 이름만" 제외하는 효과가 있다.

암시적 import

스칼라는 모든 프로그램에 몇 가지 import를 항상 추가시킨다.

import java.lang._
import scala._
import Predef._

위 3개의 패키지는 항상 포함한다.

보호 스코프

스칼라는 접근 수식자의 의미를 지정자(quialifier)로 확장할 수 있다. private[X]protected[X]의 형태로 지정 가능하다.
이러한 접근 지정자가 있는 수식자는 매우 세밀한 접근 제어를 가능케 한다.

package bobsrockets

package navigation {
	private[bobsrockets] class Navigator {
    	protected[navigation] def useStarChart() {}
        
        class LegOfJourney {
        	private[this] val speed = 200
        }
    }
}

위와 같이 접근제어자 않에 패키지명을 넣음으로써 접근 가능한 곳들을 제한할 수 있다.

📚 Reference

  • Programmin in Scala 4/e - Chapter 13 패키지와 임포트

0개의 댓글