클래스 계층 구조를 이해하는 것은 Android 앱 개발에 중요하다.
위와 같은 계층 구조를 가지는 클래스들을 구현해보자!
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
}
먼저 Dwelling 클래스를 구현하자.
추상 클래스
는 완전히 구현되지 않아서 인스턴스화할 수 없는 클래스다.
아직 어떤 자재로 만들지 결정되지 않았으므로 buildingMaterial
을 초기화 할 수 없다. abstract으로 선언한다. capacity
변수도 마찬가지.
hasRoom
은 방이 남아있는지 여부를 반환하는 함수이다. 고정된 거주자 수는 추상 클래스가 private으로 받는다.
class SquareCabin(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
}
Dwelling 클래스 아래에서 SquareCabin이라는 클래스를 만들자. 뒤에 :Dwelling(residents)
을 붙여서 상속받은걸 표시.
residents
은 이미 Dwelling에서 val로 선언한 걸 재사용하는 것이므로 여기서는 val로 선언할 필요 없다.
Dwelling에서 abstract으로 선언한 buildingMaterial
과 capacity
를 명시해줘야 한다. override 키워드를 사용하여 이 속성이 상위 클래스에서 정의되었고 이 클래스에서 재정의될 거라고 나타낸다.
fun main() {
val squareCabin = SquareCabin(6)
with(squareCabin) {
println("\nSquare Cabin\n============")
println("Capacity: ${capacity}")
println("Material: ${buildingMaterial}")
println("Has room? ${hasRoom()}")
}
}
메인 함수에서의 사용 예제이다. with
구문을 사용하면 println("Capacity: ${squareCabin.capacity}")
이렇게 번거롭게 쓰지 않아도 된다.
open class RoundHut(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
}
class RoundTower(
residents: Int,
val floors: Int = 2) : RoundHut(residents) {
override val buildingMaterial = "Stone"
override val capacity = 4 * floors
}
RoundHut 클래스 앞에는 open
키워드를 붙여야 한다. abstract 클래스나 open 키워드로 표시된 클래스에서만 상속할 수 있기 때문이다. RoundHut는 아래에 RoundTower 클래스를 두기 위해 open 키워드를 붙여야 한다.
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
abstract fun floorArea(): Double
}
추상 클래스에 추상 함수를 선언할 수 있다.
이제 Dwelling의 모든 서브 클래스에서는
override fun floorArea(): Double {
//코드..
}
를 구현해야 한다.
import kotlin.math.PI
open class RoundHut(val residents: Int,
val radius: Double) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
override fun floorArea(): Double {
return PI * radius * radius
}
}
val roundHut = RoundHut(3, 10.0)
생성자를 업데이트하여 radius를 받도록 하자. 바닥 면적 계산에 필요한 파이 값은 math 라이브러리에서 임포트해 가져오자. (SquareCabin은 원이 사각형으로 바뀐것만 빼면 RoundHut와 비슷하므로 패스.. )
class RoundTower(residents: Int, radius: Double,
val floors: Int = 2) : RoundHut(residents, radius) {
override val buildingMaterial = "Stone"
override val capacity = 4 * floors
override fun floorArea(): Double {
return super.floorArea() * floors
}
}
RoundHut의 서브 클래스인 RoundTower은 super
키워드를 사용하여 상위 클래스의 함수(PI * radius * radius
)를 사용할 수 있다. 얘도 원형 바닥의 면적을 구해야 하는 건 똑같으므로!
XML(확장성 마크업 언어)
은 텍스트를 구성하는 방법이며 태그, 요소, 속성으로 구성된다. XML을 사용하여 Android 앱의 레이아웃을 정의할 수 있다.
view
들은 findViewById()
을 통해 접근할 수 있지만... UI가 더 복잡해짐에 따라 findViewById()
를 사용하는 것이 번거로워질 수 있다.
이런 단점을 극복하기 위해 Android는 뷰 결합이라는 기능을 제공한다.
buildFeatures {
viewBinding = true
}
build.gradle(app)의 안드로이드 섹션에 위 코드를 추가하고 sync해주자.
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
}
MainActivity.kt에서 setContentView
부분을 수정하자.
findViewByID와 View Binding의 차이는 위 그림과 같다.
앱의 각 View마다 findViewById()를 호출하는 대신, 결합 객체를 한 번 만들고 초기화한다.
MainActivity.kt 코드를 살펴보면서 공부해보자.
lateinit var binding: ActivityMainBinding
이 부분이 결합 객체의 최상위 변수를 선언하는 부분이다.
lateinit
는 코드가 변수를 사용하기 전에 먼저 초기화할 것임을 확인해 주는 키워드이다.
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
이후 onCreate()안에서 binding변수를 초기화해주고, 컨텐츠 뷰를 설정한다.
// findViewById() 사용하는 방법
val myButton: Button = findViewById(R.id.my_button)
myButton.text = "A button"
//view binding을 사용한 혁신적인 방법!
val myButton: Button = binding.myButton
myButton.text = "A button"
이제 findViwById 없이 view를 찾을 수 있다.
참고자료:https://developer.android.com/courses/pathways/android-basics-kotlin-unit-2-pathway-1