Databinding _ 우선 공부해야 할 것들

우선 공부해야 할 것들


Annotation

자바 어노테이션(Java Annotation)은 자바 소스 코드에 추가하여 사용할 수 있는 메타데이터의 일종이다. 보통 @ 기호를 앞에 붙여서 사용한다. JDK 1.5 버전 이상에서 사용 가능하다. 본질적인 목적은 소스코드에 메타데이터를 표현하는 것. 단순히 부가적인 표현뿐만이 아닌 리플렉션을 이용하면 어노테이션 지정만으로도 원하는 클래스를 주입하는 것이 가능해진다.

참고 : Java Annotation이란?, Java - Annotation(어노테이션 활용 예제1), Java Annotation: 인터페이스 강요로부터 자유를…(어노테이션 활용 예제2), 깔끔한 정리

위로


Annotation Processing

컴파일 단계에서 어노테이션만 가지고 자바코드를 만들어내는데 사용됨. Room, DataBinding, Glide, ButterKnife, Retrofit 등에서 활용. 어노테이션들을 가지고 어노테이션 프로세서는 지지고 볶음.

어노테이션 프로세서를 활용하면 어노테이션만으로도 여러가지 모델 클래스들을 만들어낼 수 있는 팩토리를 구성할 수 있다.

그래서 Room 쓸 때 @Entity, @Database 와 같은 어노테이션들을 붙이면 알아서 모델 클래스들을 생성해주는 듯 싶다. 그리고 Room 사용할 때도 그래들에 어노테이션 프로세서도 함께 추가해줘야한다는 것이 기억난다.

참고 : Annotation Processing : Don’t Repeat Yourself, Generate Your Code., Annotation Processing 101 (번역)

위로


Reflection

리플렉션이란 객체를 통해 클래스의 정보를 분석해내는 프로그램 기법. 투영, 반사라는 사전적인 의미를 지니고있다. 리플렉션은 구체적인 클래스 타입을 알지 못해도 그 클래스의 메소드, 타입, 변수들을 접근할 수 있도록 해주는 자바 API이다.

그런데 한가지 의문점이 있다. “내가 만드는 프로그램의 코드 흐름인데, 내가 사용할 클래스의 이름과 타입을 모르는 경우가 있을까?” 일반적으로 만나기 힘든 경우지만, 코드 작성 시점에는 어떤 타입의 클래스를 사용할 지 모르는 경우가 있다. 다시말해, 런타임에 현재 실행되고있는 클래스를 가져와서 실행해야한다는 것이다.

대표적으로 프레임워크나 IDE에서 이러한 동적 바인딩을 이용한 기능을 제공하는데 IntelliJ의 자동완성, 스프링 프레임워크의 어노테이션과 같은 기능이 코드를 설계하고 작성할 당시에는 사용될 클래스가 어떤 타입인지 알 수 없지만, 리플렉션을 이용해서 코드를 일단 작성하고 런타임에 확인해서 활용할 수 있도록 하는 메커니즘이다.

객체의 타입은 알고있지만 형변환을 할 수 없는 상태에서 리플렉션으로 객체의 메서드를 호출할 수 있다.

리플렉션의 가장 기초적인 쓰임은, 클래스에서 정의한 메소드가 무엇인지 찾아내는 것이다. getDeclaredMethods 를 통해서 메소드 리스트, getMethods를 사용해서 상속된 메소드에 대한 정보를 얻을 수 있다.

그 외 제공하는 기능들
  • Reflection 을 사용한 Set up
  • Simulating the instanceof Operator
  • 생성자에 대한 정보 얻기
  • Class Field 찾기
  • 이름으로 메소드 실행하기
  • 새로운 객체 만들기
  • 필드값 바꾸기

참고 : 자바의 리플렉션, Java Reflection 개념 및 사용법

위로


Observer Pattern

상태를 가지고 있는 주체 객체상태의 변경을 알아야 하는 관찰 객체(Observer Object)가 존재하며 이들의 관계는 1:1이 될 수도 있고 1:N이 될 수가 있다.

서로의 정보를 넘기고 받는 과정에서 정보의 단위가 클 수록, 객체들의 규모다 클 수록, 각 객체들의 관계가 복잡할 수록 점점 구현하기 어려워지고 복잡성이 매우 증가할 것이다. 이러한 기능을 할 수 있도록 가이드라인을 제시해 주는 것이 바로 옵저버 패턴이다.

  • 옵저버 패턴은 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들에게 연락이 가고 자동으로 정보가 갱신되는 1:N 의 관계를 정의한다.

  • 연결은 인터페이스를 이용하여 느슨한 결합성을 유지한다. 주체, 옵저버 인터페이스를 적용한다.

  • 옵저버 패턴은 푸시 방식과 풀 방식으로 언제든지 구현할 수 있다.

  • JAVA에서 기본으로 Observable 클래스와 Observer 인터페이스를 제공한다.

  • Swing, Android 등 UI와 관련된 곳에서 이 옵저버 패턴이 많이 사용된다. (물론 이보다 더 많다)

참고 : 디자인패턴 - 옵저버 패턴(Observer Pattern), 디자인패턴 - 옵저버 패턴 (observer pattern)


Android Example

요구사항 : +, - 버튼을 누르면 TextView의 내용을 변경. 추가적으로 add 버튼을 누르면 옵저버를 attach하고, delete 버튼을 누르면 detach 한다.

각 클래스를 정의할 때 중요한 것은 import 가 있으면 안된다 (다른 클래스와 결합되어있으면 안된다) -> 객체지향적으로 설계하자!

Publisher 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
open class Publisher<T>(defaultValue: T) {
private val observers = ArrayList<Observer<T>>() // 이 클래스를 구독하고 있는 옵저버 리스트
private var value: T = defaultValue
var allowInit = true // 퍼블리셔의 상태를 최신으로 유지할지 여부(옵저버가 detach 된 상태에서는 갱신되지 않는다.)

fun add(observer: Observer<T>) {
if (!observers.contains(observer)) {
observers.add(observer)
if (allowInit) observer.update(value)
}
}

fun delete(observer: Observer<T>) {
observers.remove(observer)
}

protected fun setValue(value: T) {
this.value = value
notifyObserver()
}

protected fun getValue(): T = value

private fun notifyObserver() {
observers.forEach { it.update(value) }
}
}

Publisher 클래스를 상속한 MyPublisher

1
2
3
4
5
6
7
8
9
class MyPublisher : Publisher<Int>(0) {
fun increase() {
setValue(getValue() + 1)
}

fun decrease() {
setValue(getValue() - 1)
}
}

MainActivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class MainActivity : AppCompatActivity(), Observer<Int> {
private val publisher = MyPublisher()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

publisher.allowInit = false

btn_plus.setOnClickListener {
publisher.increase()
}

btn_minus.setOnClickListener {
publisher.decrease()
}

btn_add.setOnClickListener {
publisher.add(this)
}

btn_delete.setOnClickListener {
publisher.delete(this)
}
}

override fun update(value: Int) {
textView.text = value.toString()
}
}

몇 시간 고민하다 혼자 해결 못하고 결국 배웠다. 다음부턴 삽질시간이 길어지면 바로 중단하고 다음 단계로 넘어가자.

위로


Factory Method Pattern

팩토리 메소드 패턴을 사용하는 이유는 클래스간의 결합도를 낮추기 위한것이다. 결합도라는 것은 간단히 말해 클래스의 변경점이 생겼을 때 얼마나 다른 클래스에도 영향을 주는가이다. 팩토리 메소드 패턴을 사용하는 경우 직접 객체를 생성해 사용하는 것을 방지하고 서브 클래스(팩토리 메소드 클래스)에 위임함으로써 보다 효율적인 코드 제어를 할 수 있고 의존성을 제거한다. 결과적으로 결합도를 낮춰 유지보수가 용이해진다.

참고 : 팩토리 메소드 패턴(Factory Method Pattern), [Design_Pattern] 팩토리 메서드 패턴(Factory Method Pattern)

Abstract Factory Pattern

추상 팩토리 패턴은 많은 수의 연관된 서브 클래스를 특정 그룹으로 묶어 한번에 교체할 수 있도록 만든 디자인 패턴이다. 예를 들어 특정 라이브러리를 배포하는데 OS별로 지원하는 기능이 상이하다면 추상 팩토리 패턴을 사용해 OS별 기능 변경을 통합적으로 변경 할 수 있다.

참고 : 추상 팩토리 패턴(Abstract Factory Pattern)

위로


Builder Pattern

빌더 패턴은 추상 팩토리 패턴이나 팩토리 메소드 패턴처럼 새로운 객체를 만들어서 반환하는 패턴이긴 하지만 실제 동작 방식은 조금 다르다. 빌더 패턴은 생성자에 들어갈 매개 변수가 많든 적든 차례차례 매개 변수를 받아들이고, 모든 매개 변수를 받은 뒤에 이 변수들을 통합해서 한번에 사용한다.

Builder pattern으로 해결할 수 있는 것
  • 불필요한 생성자를 만들지 않고 객체를 만든다.
  • 데이터의 순서에 상관 없이 객체를 만들어 낸다.
  • 사용자가 봤을때 명시적이고 이해할 수 있어야 한다.
builder pattern의 예시
  • Retrofit

  • Observable

  • Glide, Picasso

참고 : 빌더 패턴(Builder Pattern), [pattern 04] 빌더 패턴-마지막에 () 빼야함


Android Example

요구사항 : Change 버튼을 누르면 ViewState 인스턴스를 만들고, 이 인스턴스 어딘가에 전달하면 거기서 ViewState 안에있는 값을 가지고 A,B,C를 숨기고 보여주기

ViewState 클래스
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class ViewState private constructor(builder: Builder) {
val state1 = builder.state1
val state2 = builder.state2
val state3 = builder.state3

class Builder {
var state1: Boolean = false
var state2: Boolean = false
var state3: Boolean = false

fun checkOne(state: Boolean): Builder {
state1 = state
return this
}

fun checkTwo(state: Boolean): Builder {
state2 = state
return this
}

fun checkThree(state: Boolean): Builder {
state3 = state
return this
}

fun build() = ViewState(this)
}
}
MainActivity
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

buttonChange.setOnClickListener {
setupView(checkList())
}

buttonClear.setOnClickListener {
setupView(checkList(true))
}
}

private fun checkList(clear: Boolean = false): ViewState {
val builder = ViewState.Builder()
if (!clear) builder.checkOne(check1.isChecked)
.checkTwo(check2.isChecked)
.checkThree(check3.isChecked)
return builder.build()
}

// 코틀린 버전 checkList
private fun checkList2(clear: Boolean = false): ViewState {
return ViewState.Builder().apply {
if (!clear) {
checkOne(check1.isChecked)
checkTwo(check2.isChecked)
checkThree(check3.isChecked)
}
}.build()
}

private fun setupView(viewState: ViewState) {
if (viewState.state1) textView1.visibility = View.VISIBLE
else textView1.visibility = View.GONE

if (viewState.state2) textView2.visibility = View.VISIBLE
else textView2.visibility = View.GONE

if (viewState.state3) textView3.visibility = View.VISIBLE
else textView3.visibility = View.GONE
}
}

위로


Singleton Pattern

단 하나의 인스턴스만 생성해 사용하는 디자인 패턴. 같은 인스턴스가 필요할 때 기존에 생성된 인스턴스를 사용하도록 함. 이렇게 인스턴스를 유일하게 사용해야하는 경우가 언제인지는 직접 파악해야한다.

싱글톤 패턴을 쓰는 이유
  • 단 한번의 new로 생성하여 고정된 메모리 영역을 얻기 때문에, 메모리 낭비를 방지할 수 있다.
  • 싱글톤으로 만들어진 인스턴스는 전역 인스턴스이기 때문에 다른 클래스와 데이터를 공유하기가 쉽다.
    • 하지만 인스턴스가 너무 많은 일을 하거나 많은 데이터를 공유시킬 경우 결합도가 높아져 개방-폐쇄 원칙을 위배하게 된다(객체 지향 설계 원칙에 어긋남).

자바에서는 생성자를 private으로 선언하여 new 를 사용한 객체 생성이 불가능하도록 한다. 보통 static 멤버로 getInstance() 를 정의해서 객체를 리턴하도록 정의한다. (thread safe를 위한 방법이 아래 참고 링크에 설명되어있다.)

코틀린은 그냥 object 로 생성하면 된다.

  • 근데 생성자에 파라미터가 필요한 경우는 생각해봐야할 듯 하다.

참고 : 싱글톤 패턴을 쓰는 이유와 문제점, 싱글튼 패턴(Singleton Pattern), Kotlin Singleton, Kotlin Companion Object

위로


Share