개발을 하는데 있어서 이미 검증이 된 여러 가지 디자인 패턴들이 존재한다. MVC, MVP, MVVM 등..최근에는 MVVM을 많이 사용하고 있다고 한다.
그 중 MVC패턴은 Model - View - Controller를 이용하여 데이터 간의 이동이나 사용자에게 어떻게 화면을 보여주고 그것들을 컨트롤하는 방법에 대해 영역을 분할하고 각 구성 요소에게 역할을 부여하는 개발 방식이다.
처음 생각하기로 안드로이드 개발 체계는 이미 MVC를 적용한 것 아닌가라는 생각을 했다.
그런데 이건 내가 MVC를 적절하게 적용하지 못한 것이다.
이 자체를 이해하긴 하였으나, Model같은 경우 일반적으로 안드로이드 개발환경에서 모두 만드는 경우는 잘 없을 것이다. 보통 API통신을 통해서 백엔드에 저장된 정보를 가져오는 것이 일반적일텐데 이러한 부분들에 대해서 자세히 살펴보았다.
가정 상황 : 사용자에 대한 정보를 서버와 통신해서 사용자에게 보여주는
package com.example.pra_mvc
data class User(
val id: Int, val name: String
)
모델에 대한 데이터 클래스를 하나 만들었다.
통신을 통해 데이터를 받은 후 이 클래스를 이용해 값을 저장하고 View에 보여줄 것이다.
추가적인 모델 부분은 Controller에서도 있다.
🚨 모델을 뷰를 몰라야한다 이 점을 주의하며 작성
`<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/txt_title"
android:text="MVC TEST"
android:textSize="44dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.276" />
<TextView
android:id="@+id/txt_ID"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="유저 ID : "
android:textSize="36dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.298"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.435" />
<TextView
android:id="@+id/txt_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="유저 이름 : "
android:textSize="36dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.338"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.579" />
</androidx.constraintlayout.widget.ConstraintLayout>`
xml은 크게 어려운 것 없이 데이터 클래스가 가진 값들을 잘 보여주기 위해서 id값만 잘 조정해주었다.
MVC에서 가장 많은 역할을 하는 컨트롤러!
class MainActivity : AppCompatActivity() {
private lateinit var usernameTextView: TextView
private lateinit var userIDTextView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// View에 선언되있는 값이랑 매칭(초기화해두기)
usernameTextView = findViewById(R.id.txt_name)
userIDTextView = findViewById(R.id.txt_ID)
// Model 생성
val user = User(id = 1, name = "근재")
// Model이 가진 데이터를 View에 설정해주자
usernameTextView.text = "유저의 이름 : ${user.name}!"
userIDTextView.text = "유저의 ID : ${user.id}}"
}
}
컨트롤러는 Model, View를 다 아는 것이 맞지만, 이렇게 만들고 나니 조금 걸렸던 부분은 컨트롤러에서 직접 Model을 만드는 부분이었다. 뭔가 분리가 더 되어서 결합도는 조금 더 낮출 수 있다면 좋겠다는 느낌을 받았고 개선을 해보았다.
View나 모델 클래스가 존재하는 것은 그대로이나 여기서 더욱 커플링을 낮추기 위해서 노력해보자!
목표는 컨트롤러에서 직접적으로 API를 부르는 코드가 들어가있는 것은 애초에 보기에도 안 좋거니와 커플링은 증가시키는 행동이다. 우리는 이를 분리할 필요가 있다!
#Controller와 Model 사이의 결합도를 낮추는 것의 이점.
이러한 이유들에 있어 충분히 코드를 개선할 이유는 존재!
우리는 의존성 주입(Dependency Injection)을 이용하여 개선할 것이다!
data class User(
val id: Int, val name: String
)
interface UserModel {
fun getUser(): User
}
이 곳에서는 Model관련 데이터를 받고, 처리할 수 있는 함수들을 넣어두면 된다.
굳이 인터페이스를 만드는 이유는 UserModel 인터페이스가 Model을 추상화할 수 있다는 점이다!
이 점이 이해가 잘 안될 수 있는데 컴공을 전공한 사람이라면
class 도형{
fun 치수제기
}
class 사각형 : 도형 {
}
이런 식으로 가장 일반적이고 틀이 될 수 있는 클래스를 하나 만들고 나머지는 이를 상속받아서 기본적인 특성을 유지하면서 확장?하는 형태로 한다는 것을 자바나 C++에서 배울 것이다. 이를 생각하면 이러한 행동을 하는 것이 납득이 조금 더 잘 갈 것 같다. 아 물론 클래스를 이용하여 다중 상속을 할 경우 메소드 출처의 모호성을 문제로 자바(코틀린)에서는 클래스를 통한 다중 상속은 지원하지 않기에 해당 코드에서는 인터페이스로 두었다!
class UserModelImpl : UserModel {
override fun getUser(): User {
// 데이터를 생성하거나 가져오는 로직을 구현
// 보통은 백엔드와 통신을 해서 데이터를 가져오는 부분을 구현할 것이다!
return User(id = 1, name = "근자")
}
}
의존성 주입을 위한 클래스로 우리가 만들어둔 인터페이스를 상속 받아서 만들어진다!
이 부분에서 인터페이스에서 만들어둔 getUser를 실제로 구현하면 된다!
class MainActivity : AppCompatActivity() {
private lateinit var usernameTextView: TextView
private lateinit var userIDTextView: TextView
private lateinit var userModel: UserModel//유저 모델
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// View에 선언되있는 값이랑 매칭(초기화해두기)
usernameTextView = findViewById(R.id.txt_name)
userIDTextView = findViewById(R.id.txt_ID)
// UserModel 구현체를 생성 또는 !주입!받을거다
userModel = UserModelImpl()
// Model에 있는 데이터를 가져와서
val user = userModel.getUser()
// Model이 가진 데이터를 View에 설정해주자
usernameTextView.text = "유저의 이름 : ${user.name}!"
userIDTextView.text = "유저의 ID : ${user.id}"
}
}
이렇게 하면 의존성 주입을 정상적으로 진행할 수 있다!!!