https://developer.android.com/topic/libraries/architecture/datastore

 

SharedPreferences

SharedPreferences 는 키-값 의 데이터를 저장하기 위해 사용합니다. 이때, 저장은 File 에 이루어집니다.

File 에 저장을 하고 읽는 다는 것은, 데이터를 유지할 수 있다는 것을 의미합니다.

 

저는 "앱 사용자설정" 등의 설정값들을 저장하기 위해 많이 사용합니다.


DataStore

DataStore 도 키-값 의 데이터를 저장하기 위해 사용합니다. 역시나 저장은 File 에 이루어집니다.

 

그렇다면 왜 DataStore 를 사용하는 것이 더 좋을까요?

(반대의견이 있을 수도 있다고... 생각...합니다만.. 일단 작성해보겠습니다.)

 

1. DataStore 는 코틀린 코루틴을 사용한 Flow 를 사용하여 비동기적으로 저장됩니다.

(SharedPreferences 는 읽는 용도로 사용될때 비동기를 지원하였습니다.)

 

2. DataStore 는 내부적으로 DisPatchers.IO 로 동작하고 있기 때문에 MainThread 로부터 안전합니다.

 

위 두 내용만 본다면, 음... 그다지.. 그냥 기존에 쓰던걸 쓰면 안되나? 그게 더 편할듯.. 이라고 생각하실 수 있습니다.


Preferences DataStore vs Proto DataStore

 

Preferences DataStore

- 위에 말한 두가지의 장점을 포함하여 SharedPreferences 보다 몇가지 개선된 점을 갖고 있는 DataStore 입니다.

 

Proto DataStore 

- 이것이 진짜입니다.

1. Protocol buffers 를 이용하여 객체를 저장할 수 있다.

(Preferences 는 객체를 저장할 수 없기 때문에 직렬화와 역직렬화를 위해 번거로운 작업이 있었습니다.)

 

2. 타입안전성을 제공합니다. 

Preferences DataStore 역시 타입에 대한 안전성을 제공하지는 않았습니다. 그러나, Proto DataStore 는, 1번에 작성 된 것처럼

Protocol buffers 를 사용하기 때문에 타입에 대한 안전성을 제공합니다.

 

3. Key 를 사용할 필요가 없습니다.

Proto DataStore 는 지정되어있는 타입을 인식하고 제공하기 때문에 Key 를 사용할 필요가 없습니다.


SharedPreferences VS PreferencesDataStore vs ProtoDataStore

 

https://developer.android.com/codelabs/android-preferences-datastore#3

 

 

 

참고 https://developer.android.com/topic/libraries/architecture/datastore

 

Datastore  |  Android 개발자  |  Android Developers

Datastore   Android Jetpack의 구성요소. Jetpack Datastore는 프로토콜 버퍼를 사용하여 키-값 쌍 또는 유형이 지정된 객체를 저장할 수 있는 데이터 저장소 솔루션입니다. Datastore는 Kotlin 코루틴 및 Flow를

developer.android.com

'BackUp (관리중지) > Android 이론' 카테고리의 다른 글

Android CodeLab [Compose 개요]  (2) 2022.11.12
Android Service  (0) 2021.05.14
Android Coroutine [코루틴]  (0) 2021.05.04
[Android Jetpack] LiveData란,  (0) 2021.04.21
Fragment 와 Fragment LifeCycle 분석  (0) 2021.04.19

서비스(Service)

서비스(Service)는 사용자와 상호 작용하지 않고 더 오래 실행되는 작업을 수행하려는 응용 프로그램 구성 요소 또는 다른 응용 프로그램이 사용할 수 있는 기능을 제공하는 응용 프로그램 구성 요소이다.

사용자와 상호 작용하지 않고 : UI 가 존재하지 않습니다.
더 오래 실행되는 작업을 수행 : 백그라운드에서 작업을 수행합니다.
다른 응용 프로그램이 사용할 수 있는 : A 앱에서 B 앱의 기능을 사용할 수 있습니다. (B앱의 서비스가 이를 제공해야함)

* 서비스는 별도의 프로세스가 아니다.
- 서비스를 백그라운드에서 돌릴 수 있지만, 서비스 자체가 별도의 프로세스는 아닙니다. 따로 지정하지 않는 이상, 서비스는 앱의 프로세스와 동일한 프로세스에서 동작합니다.

* 서비스는 쓰레드가 아니다.
- 흔히 앱에서 백그라운드쓰레드로 작업을 처리하는 경우가 있는데,
서비스는 단지, 백그라운드에게 "수행해야 할 작업" 이 있다 를 "알리는" 것일 뿐입니다.

* 서비스는 프로세스 내의 주 쓰레드에서 동작하기 때문에, MP3 재생 등과 같은 기능을 하려면 별도의 쓰레드를 서비스 내에 만들어 주어야 한다.

서비스 LifeCycle

  • onStartCommand() 시스템이 이 메서드를 호출하는 것은 또 다른 구성 요소(예: 액티비티)가 서비스를 시작하도록 요청하는 경우입니다. 이때 startService()를 호출하는 방법을 씁니다. 이 메서드가 실행되면 서비스가 시작되고 백그라운드에서 무한히 실행될 수 있습니다. 이것을 구현하면 서비스의 작업이 완료되었을 때 해당 서비스를 중단하는 것은 개발자 본인의 책임이며, 이때 stopSelf() 또는 stopService()를 호출하면 됩니다. 바인딩만 제공하고자 하는 경우, 이 메서드를 구현하지 않아도 됩니다.
  • onBind() 시스템은 다른 구성 요소가 해당 서비스에 바인딩되고자 하는 경우(예를 들어 RPC를 수행하기 위해)에도 이 메서드를 호출합니다. 이때 bindService()를 호출하는 방법을 사용합니다. 이 메서드를 구현할 때에는 클라이언트가 서비스와 통신을 주고받기 위해 사용할 인터페이스를 제공해야 합니다. 이때 IBinder를 반환하면 됩니다. 이 메서드는 항상 구현해야 하지만, 바인딩을 허용하지 않으려면 null을 반환해야 합니다.
  • onCreate() 시스템은 서비스가 처음 생성되었을 때(즉 서비스가 onStartCommand() 또는 onBind()를 호출하기 전에) 이 메서드를 호출하여 일회성 설정 절차를 수행합니다. 서비스가 이미 실행 중인 경우, 이 메서드는 호출되지 않습니다.
  • onDestroy() 시스템이 이 메서드를 호출하는 것은 서비스를 더 이상 사용하지 않고 소멸시킬 때입니다. 서비스는 스레드, 등록된 리스너 또는 수신기 등의 각종 리소스를 정리하기 위해 이것을 구현해야 합니다. 이는 서비스가 수신하는 마지막 호출입니다.

Context  |  Android 개발자  |  Android Developers

developer.android.com

백준에서 코틀린으로 알고리즘 문제를 풀던 중에, 테스트코드를 직접 입력하고, 출력이 맞는지 눈으로 확인하는 과정이 너무 힘들었다.

 

그래서 테스트 코드를 사용해보고자 하였다.

 

Android 에서 Junit 으로 잠시 사용해본 적은 있기에 어려울 것 같지는 않았으며, 실제 사용해보니 동일? 했다.

 

 

먼저, 테스트를 하고자하는 클래스를 우클릭하면 Test 코드를 쉽게 생성할 수 있다.

 

 

 

그럼 이렇게, 해당 클래스이름 뒤에 Test 가 붙으면서 internal class 가 하나 생성된다.

테스트하고자 메소드는 생성할 때, 체크하는 부분이 있다.

선택하게 되면, 구현체는 없는 메소드가 생성되어있다.

 

그 이후에 테스트하고자 하는 클래스를 생성하고, assertEquals 를 사용하여, 입력에 따른 출력이 내가 생각한 결과와 같는지 비교하면 된다.

 

만약 flase 라면, ERROR 가 발생한다 :)

 

이제 테스트코드를 하나씩 입력하고, 주석으로 가리고 제출하는 등의 번거로운 작업을 피할 수 있게 되었다 홓...

1번 문제 

생각보다 쉬웠다.

5~10분정도 걸린 것 같다.

 

 

2번 문제

분명, 어디서 많이 봤는데.. 이 문제는 시간복잡도를 고민해야하는데...

2가지 고민을 했다.

1. 시간복잡도를 고려하여 문제를 풀어낸다

2. 일단 정확성으로 풀고, 이후에 다시 풀어낸다.

 

나는 2번을 생각했고, 정확성만 처리하고 넘겼다. 총 고민에서 넘김까지 15~20분 정도 걸린것 같다.

 

3번 문제

시간을 적지않게 소모하는 구현문제라고 봐야할 것 같다.

1시간 가량을 이 문제 구현에 소모했다..

 

그러고나니, 30분안에 2번문제의 시간복잡도를 고려하여 다시 풀어내야하는데.....

결국 만족스러운 결과를 내지 못했다.. (속상하네..)

 

결과는 2솔 + 2번 부분점수

일 것으로 생각된다....

 

어짜피 Android 개발밖에 안해봐서.. 면접에 간다해도 iOS 직군이니 떨어지겠지...?

어떻게 공부는 하면할수록, 알면알수록, 내가 모르는게 너무 많다는걸 느끼면서 자신감이 떨어지는 것 같기도....

 

왓챠 과제전형을 진행하면서, 코루틴을 사용하는 도중에 lifecycle 과 coroutine 동작에 대한 학습이 부족함을 느꼈습니다.

그래서 다시한번 Coroutine 에 대해 정리를 하고자 합니다.

 

코루틴은 코틀린때문에 등장한 것이 아니라고 합니다.

코루틴의 개념은 훨씬 이전부터 존재해왔으며 (언제부터일까... 그것까진 안찾아봤어요.. )

오히려 저같은 후세대 개발자들은 안드로이드에서 코틀린을 공식언어로 적용하면서 만났을 것이라고 생각합니다.

 

 

1. MainRoutine 과 SubRoutine

코루틴에 대해서 알기 전에, 메인루틴과 서브루틴에 대해서 알아야 합니다.

메인루틴은 흔히, 메인메소드라고 생각하면 됩니다.

서브루틴은 메인함수 내에서 동작하는 메소드라고 생각하시면 됩니다.

 

    private fun mainRoutine() {
        val nameA = subRoutine1()
        val nameB = subRoutine2()
        val nameC = subRoutine3()
    }

    private fun subRoutine1() : String {
        return "subRoutine1"
    }
    private fun subRoutine2() : String {
        return "subRoutine1"
    }
    private fun subRoutine3() : String {
        return "subRoutine1"
    }

위 코드는 mainRoutine() 에서 순차적으로 subRoutine1() -> subRoutine2() -> subRoutine3()

으로 동작합니다.

 

순차적으로 동작하는 만큼, subRoutine1() 에서 while(true) 에 break; 가 없다면, subRoutine2() 과 subRoutine3()은 

절대 동작할리가 없겠죠? 만약 while 탈출 조건이 있다고 해도, 탈출하기 전까지는 다음 코드가 실행되지 않습니다.

 

이게 왜 중요한가???? 

Thread 동작

하나의 쓰레드 내부코드는 위에서 설명한 것처럼 모두 순차적으로 진행됩니다.

멀티쓰레드는 내부코드가 여러개로 나뉘는 것이 아닌, 순차적으로 진행되는 각 쓰레드를 여러개 두고 돌아가며 실행시키는 것이라고 보면 됩니다.

 

 

Routine 이 무엇인지 알았나요?

MainRoutine 내부에 SubRoutin, 그 안에 SubRoutine, 그 안에 SubRoutine ... 이렇게 존재한다고 하면,

결국 마지막 SubRoutine 의 코드부터 끝이 나야 그 다음 Soutine 들이 종료가 가능한거죠.. 음?.. 어...?

스택이 떠오르네요. Routine 은 스택으로 동작하는 군요!

 

어찌보면, 당연한 흐름이겠습니다만, 이런식으로 깊게 깊게 들어가면 끝이 없겠...네요... 이친구는 여기까지!

코루틴은 무엇일까?

코루틴은 시작부터 종료까지 suspend 와 resume 이 여러번 호출될 수 있습니다.

이게 무엇이냐면,

subRoutineA 가 실행된중에 잠시 suspend 하여 탈출하고, subRoutineB 를 실행하다가 subRoutineB 를 끝내거나,

subRoutineB 도 잠시 중지한다음 subRoutineA 를 다시 실행할 수 있다는 것입니다.

 

* 핵심은 return 이 아니기 때문에 subRoutineA 가 중지된 부분부터 이어서 실행이 가능하다는 것이죠!

 

    private fun mainRoutine() {
        subRoutine1()
        subRoutine2()
        subRoutine3()
    }

    private fun subRoutine1() {
        var count = 0
        while(count<5) {
            Thread.sleep(500)
            println("subRoutine1")
            count += 1
        }
    }

    private fun subRoutine2() {
        var count = 0
        while(count<5) {
            Thread.sleep(500)
            println("subRoutine2")
            count += 1
        }
    }

    private fun subRoutine3() {
        var count = 0
        while(count<5) {
            Thread.sleep(500)
            println("subRoutine3")
            count += 1
        }
    }

이 코드는 코루틴이 아닌 일반적인 코드입니다. 해당 코드들은 어떻게 동작할지 상상이 가죠?

 

그렇다면 다음 코드를 볼까요?

    private fun mainRoutine() {
        MainScope().launch {
            println("Start subRoutine1")
            subRoutine1()
        }

        MainScope().launch {
            println("Start subRoutine2")
            subRoutine2()
        }

        MainScope().launch {
            println("Start subRoutine3")
            subRoutine3()
        }
    }

    private suspend fun subRoutine1() {
        var count = 0
        while(count<5) {
            delay(500)
            println("subRoutine1")
            count += 1
        }
    }

    private suspend fun subRoutine2() {
        var count = 0
        while(count<5) {
            delay(500)
            println("subRoutine2")
            count += 1
        }
    }

    private fun subRoutine3() {
        var count = 0
        while(count<5) {
            Thread.sleep(500)
            println("subRoutine3")
            count += 1
        }
    }

subRoutine1, 2, 3 은 모두 코루틴으로 실행을 시켰습니다.

 

그럼, 1,2,3,1,2,3,1,2,3,1,2,3,1,2,3, 순으로 돌까요?

 

정답은 틀렸습니다.

 

subRoutineA 가 실행된중에 잠시 suspend 하여 탈출하고, subRoutineB 를 실행하다가 subRoutineB 를 끝내거나,

subRoutineB 도 잠시 중지한다음 subRoutineA 를 다시 실행하려면

 

suspend 로 메소드를 작성해야합니다.

 

즉, subRoutineA 와 subRoutineB 는 서로 잠시 탈출하고, 다음대상실행, 탈출 , 실행 으로 동잡합니다만,

 

subRoutineC 는 suspend 키워드가 없기 때문에  선점한 이후에 탈출하려면 subRoutineC 가 종료되어야만 가능합니다.

 

따라서 실행결과는 아래와 같습니다.

 

이러한 동작은 멀티쓰레드로 만들어낼수도 있습니다.

그러나, 엄청나게 큰 차이는

 

멀티쓰레드 : 쓰레드간 컨텍스트 스위칭으로 서로 다르게 동작

코루틴 : 단일쓰레드에서 함수를 잠시 일시정지하고 다른 함수를 실행하는 식으로 동작

 

Suspend 메소드는 어떤식으로 동작할까?

 

아래와 같은 suspend 메소드를 변경시켜보겠습니다.

정확하게 suspend 가 변하는 부분은 훨씬 복잡할 것입니다.

어떤 메소드를 사용할지 결정하는 등에 대한 부분도 고려가 될것으로 판단되구요..

중요한건 결국 메소드를 스위칭 한다는 것이죠

즉, 1과 2가 서로 어떻게 돌아가면서 실행되어야 하는데요,

 

따라서 아래와 같이 변경해 보았습니다.

label 을 두고, 현재 메소드 실행 이후에 어떤 메소드를 실행할기 결정시키는 것인데요, 공부한바에 의하면

코루틴의 suspend 는 저런식으로 label 가지고 결정한다고 합니다. 물론 훨씬 복잡하겠지만요 :)

 

멀티쓰레드 vs 코루틴  누가 좋을까?

 

멀티쓰레드를 스위칭하는 작업은 코루틴에서 함수를 스위칭하는것보다 훨씬 많은 메모리를 필요로 합니다.

즉, 사용 메모리 : 멀티쓰레드 > 코루틴

 

그러나, 코루틴은 단일쓰레드 내에서 여러개를 번갈아가며 실행시키는 것입니다.

(이는 단일코어에서 멀티쓰레드를 번갈아가며 실행시키는 것과 유사하다고 봐야할것 같습니다.)

 

즉 10개의 Task 가 있다면,

 

1. 단일코어 멀티쓰레드 : 1개의 코어에 10개의 쓰레드를 동작시키고...

  - 처리속도

멀티쓰레드의 컨텍스트 스위칭 시간 vs 코루틴 labeling 시간 중 누가 더 빠른가?

를 봐야할 것 같습니다. 개인적으로는 최소 같거나, 코루틴이 더 빠를것이라고 생각합니다.

 

2. 멀티코어 멀티쓰레드 : 3개의 코어가 3,3,4 의 쓰레드를 동작시키고...

 - 처리속도

멀티코어 멀티쓰레드 > 코루틴

 

그러나, 이 부분은 좀더 깊이있게 공부해서 어떤것이 좋은지 확인이 필요할것 같습니다.

제가 아직 이 판단은 잘 서지 않네요.. 

그러나, 쓰레드를 정말 제대로 잘짜는게 아니라면 코루틴이 좋지 않을까 싶...흡...

GlobalScope vs MainScope vs lifecycleScope 등등등

안드로이드에서 코루틴을 사용하다보면,

 

~~~Scope.launch {

...

}

 

이런식으로 많이 사용을 하게 되는데요.

대체 어떤 스코프를 써야하는지에 대한 생각이 들었던 적이 있었습니다. 다시한번 코루틴을 정리한 이유도 이때문이구요.

 

1. GlobalScope : 자체적인 쓰레드풀을 하나 갖고 있는 곳에서 실행됩니다.

즉 정말, Global 한 영역에서 동작시킨다고 봐야합니다.

MainActivity 에서 호출한다면, Application 이 해당 코루틴의 동작구간이 된다고 봐야합니다.

 

2. MainScope : MainThread 에 해당하는 영역에서 실행됩니다.

MainActivity 에서 호출한다면, MainActivity 가 해당 코루틴의 동작구간이 된다고 봐야합니다.

 

3. lifecycleScope : 호출되는 라이프사이클에 맞게 실행됩니다.

 

NullPointException 의 고통을 받고 싶지 않다면, 사용되는 코루틴이 LifeCycle 에 맞게 생성, 제거 될 수 있도록 신경을 써야 할 것 같습니다.

 

즉, 저는 앞으로 앱을 개발할 때, lifecycleScope 를 제일 많이 사용하게 될 것 같고, 그러지 못하는 경우에는 cancel 을 적절하게 사용하고, 제대로 동작할 수 있도록 고민하며 개발을 진행해야 한다고 판단했습니다.

의존성 주입(DI) 는 안드로이드 뿐만 아니라, 객체지향 프로그래밍을 할 때 한번쯤 듣는 단어입니다.

의존성이란

하나의 객체가 동작함에 있어서 다른 객체를 반드시 필요로 하는 경우를 말합니다.

A 클래스에서 B클래스를 반드시 필요로 한다. 즉, B클래스가 없으면 A클래스는 동작하지 않는다.

이런 경우 A클래스는 B클래스에 의존하고 있다. 라고 보면 될 것 같습니다.

의존성 주입(DI) 란,

의존성을 갖는 객체가 있다면, 그 객체가 의존하고 있는 객체를 외부에서 주입해주는 것입니다.

 

DI 를 사용하는 이유

지금부터 상황을 토대로 DI 를 사용하는 이유에 대해서 시작하겠습니다.

이 상황은 DI 설명을 위해 지어낸 상황입니다. (현실성에서 굉장히 떨어질 수 있습니다.)

 

Chapter1. 처음엔 1개 뿐이였어

요청 : 안드로이드 앱 개발자를 구하고 있습니다. 이 개발자는 AndroidStudio 를 사용해서 앱을 개발해야 합니다.
답변 : 안드로이드 개발자 클래스를 전달드리겠습니다. 필요하실 때마다 해당 클래스를 생성하시면, 안드로이드 앱 개발자를 얻게 되실겁니다.
class AndroidDeveloper {
  AndroidStudio androidStudio;
  
  AndroidDeveloper() {
  	androidStudio = new AndroidStudio();
  }

  void createAppAOS() {
  	androidStudio.createApp();
  }
}

Chapter2. 비슷하지만, 새로 만들면 돼

요청 : 저번에 안드로이드 개발자클래스를 잘 쓰고있습니다. 근데 이번엔 iOS 앱 개발자를 구하고 있습니다. iOS 앱 개발자는 Swift 를 사용해야 합니다.
답변 : iOS 개발자 클래스를 전달드리겠습니다... 역시 필요하실 때마다 해당 클래스를 생성하시면 됩니다.
class iOSDeveloper {
  xCode xCode;
  
  iOSDeveloper() {
  	xCode = new xCode();
  }

  void createAppiOS() {
  	xCode.createApp();
  }
}

Chapter3. 안좋은 예감이 들어

요청 : iOS 개발자가 그만두었습니다. 이참에 그냥 안드로이드 개발자가 iOS 개발도 할 수 있게 만들어주면 안되나요?
답변 : 네 알겠습니다... ㅂㄷㅂㄷ 수정해드릴게요
class AndroidDeveloper {
  AndroidStudio androidStudio;
  xCode xCode;
  
  AndroidDeveloper() {
  	androidStudio = new AndroidStudio();
  	xCode = new xCode();
  }

  void createAppAOS() {
  	androidStudio.createApp();
  }

  void createAppiOS() {
  	xCode.createApp();
  }
}

Chapter4. 이럴줄 알았어

질문 : 이거 혹시 그전 iOS 개발자 클래스도 그대로 쓸 수 있는거 맞죠?
답변 : 네 맞습니다...
요청 : 그냥 안드로이드 개발자, iOS 개발자, 두개 다 가능한 개발자 세개로 만들어주세요.
답변: 예예...
class AndroidDeveloper {
  AndroidStudio androidStudio;
  AndroidDeveloper() {
  	androidStudio = new AndroidStudio();
  }

  void createAppAOS() {
  	androidStudio.createApp();
  }
}

class iOSDeveloper {
  xCode xCode;
  iOSDeveloper() {
  	xCode = new xCode();
  }

  void createApp() {
  	xCode.createApp();
  }
}

class AppDeveloper {
  AndroidStudio androidStudio;
  xCode xCode;
  AppDeveloper() {
  	androidStudio = new AndroidStudio();
  	xCode = new xCode();
  }

  void createAppAOS() {
  	androidStudio.createApp();
  }

  void createAppiOS() {
  	xCode.createApp();
  }
}

Chapter5. 그렇게 1개에서 100개까지 늘어났지

요청 : 이번엔 Spring..
요청 : 이번엔 ~~~~~

...

요청 : 이번엔 풀스택~~

 

Chapter6. 절망의 순간

요청 : 보니까 개발자가 사용하는 툴을 생성하더라구요? 그 툴에서 사용하는 언어도 선택할 수 있게 해주세요.
답변 : 그러니까.. 이런식으로요?
class AndroidStudio {
Language language;
  AndroidStudio(Language language) {
  	this.language = language;
  }
  void createApp() {
	//...
  }
}
요청 : 어 맞아요. 그런데 어라... 이전에 제공해주신 코드들이 모두 에러가 나요

 

Chapter7. 회고의 순간

아.... 이거 사용하고 있는 코드들 다 찾아서 다 고쳐줘야하네...

 

이게 다 개발자 생성하는 클래스마다 싹다 툴 클래스에 의존하고있어서그래!!

애초에  개발자 클래스를 생성할 때 툴을 전달받게 했으면, 개발자 클래스를 생성하는 부분에서만 고치면 편할텐데 하............

 

다음에는 이런 부분은 진짜 고려해서 작성해야겠다!!

 

 

Chapter8. 이런 상황을 겪은 나에게 필요한 건 뭐다!?

"Constructor injection" , 바로 생성자 주입이라는 것이다!!!


 

Chapter9. 근데.. 만약에 생성자를 직접 안넣고  그냥 set 으로 전달받으면 어쩌지?

class AndroidDeveloper {
  AndroidStudio androidStudio;
  AndroidDeveloper(String lang) {
  	androidStudio = new AndroidStudio();
  	androidStudio.setLanguage(lang);
  }
  void createAppAOS() {
  	androidStudio.createApp();
  }
}

 

이렇게 AndroidStudio 객체가 언어를 필요로 하는데, 이 언어를 setLanguage 처럼 직접 넣어주는 경우를

"Filed injection" , 바로 필드 주입 이라고 한다.

 

 

 


의존성 주입의 장점

  • Unit Test가 용이해진다.
  • 코드의 재활용성이 높아진다.
  • 객체 간의 의존성(종속성)을 줄이거나 없앨 수 있다.
  • 객체 간의 결합를 낮추기 때문에 유연하게 코드를 작성하며, 유지보수가 쉬워진다.
  • 보일러 플레이크 코드가 줄어든다.

위 예시에서는 의존성 주입의 장점들을 모두 담아내지 못했습니다. (ex. TestCode , 재활용성 등 )

결국 클린하게 코드를 작성하기 위하여, 객체지향을 객체지향답게 작성하기 위하여

 

"의존성 주입" 은 선택이 아니라, 필수입니다.

 

아마 DI 를 도와주는 라이브러리를 사용하지 않아서, DI 를 모른다. 적용해본적 없다. 라고 생각하시는 분들이 계실지도 모르겠습니다.

그러나, DI 는 꼭 라이브러리가 있어야만 하는 것이 아닙니다. 여러분은 알게 모르게 사용하셨을 수도 있습니다. ( 의존성 주입만을 담당하는 Class 를 따로 둔다거나 하는 것까지 아니더라도)

겁먹지 말고, 클린코드를 위하여 편안한 유지보수로 미래의 "자신"이 더 행복하기 위하여 이제는 시작해봅시다.

 

'BackUp (관리중지) > CS 학습' 카테고리의 다른 글

개발자 기초지식 [ 내용요약 ]  (0) 2022.11.12
REST API  (0) 2021.04.28
Java GC  (0) 2021.04.28
GC ( Garbage Collection )  (0) 2021.04.28
동시성 이슈  (0) 2021.04.27

동적배열은 배열의 크기가 가변으로 변할 수 있는 배열입니다.

따라서 실행시간에 그 크기가 결정됩니다.

 

그러나, 동적배열도 배열이기 때문에 배열의 가장 중요한 특징은 일치합니다

 

그 특징은 "메모리 시작주소를 기준으로 연속적으로 할당되어 있다" 라고 볼 수 있습니다.

 

 

어떻게 메모리에 연속적으로 할당될 수 있죠?

다음과 같은 경우를 볼까요?

0x000000 USER[0]

0x000001 USER[1]

0x000002 USER[2]

0x000003 NAME[0]

0x000004 NAME[1]

0x000005

0x000006

0x000007

0x000008

 

이같은 경우에 USER[3] 이 필요하다면 어쩌죠?

NAME[0], NAME[1] 의 주소를 뒤로 옮기고 USER[3] 을 저장해야하나요?

 

아닙니다.

예시가 저럴뿐 저 사이에 얼마나 많은 메모리가 사용되고 있는지 모르는데, 그처럼 어마무시한 짓은 할 수 없습니다.

 

따라서 아래와 같은 순서로 진행합니다

1. 기존 USER 를 복사

0x000000 USER[0]

0x000001 USER[1]

0x000002 USER[2]

0x000003 NAME[0]

0x000004 NAME[1]

0x000005 USER[0]

0x000006 USER[1]

0x000007 USER[2]

0x000008

 

2. 새로운 USER[3] 을 추가

0x000000 USER[0]

0x000001 USER[1]

0x000002 USER[2]

0x000003 NAME[0]

0x000004 NAME[1]

0x000005 USER[0]

0x000006 USER[1]

0x000007 USER[2]

0x000008 USER[3]

 

3. 기존 USER 제거

0x000000

0x000001

0x000002

0x000003 NAME[0]

0x000004 NAME[1]

0x000005 USER[0]

0x000006 USER[1]

0x000007 USER[2]

0x000008 USER[3]

 

그럼 배열이 하나 추가될때마다 계속 복사하나요?

생각만해도 비효율적인 속도가 느껴지나요?

몇개가 추가될지 모르는데, 추가될때마다 복사하고 기존껏을 제거한다면, 시간복잡도가 상당해질 것이 예측이 됩니다.

 

그래서 동적배열의 경우 SIZE * 2 씩 미리 메모리 영역을 할당해둡니다.

 

0x000000 USER[0]

0x000001 USER[1] 을 위해 미리 차지해둠

0x000002 NAME[0]

0x000003 NAME[1]

 

이같은 경우가 있다면 USER[1] 을 추가하는 순간에는 복사 및 기존제거 행위는 일어나지 않습니다.

 

저 상황에서 USER[2] 를 추가한다면,

0x000000

0x000001 

0x000002 NAME[0]

0x000003 NAME[1]

0x000004 USER[0]

0x000005 USER[1]

0x000006 USER[2]

0x000007 USER[3] 을 위해 미리 차지해둠

0x000008

0x000009

 

마찬가지로 이런 상황에서 USER[3] 을 추가한다면

0x000000

0x000001

0x000002

0x000003 NAME[0]

0x000004 NAME[1]

0x000005

0x000006

0x000007

0x000008 USER[0]

0x00009 USER[1]

0x000010 USER[2]

0x000011 USER[3]

0x000012 USER[4] 를 위해 미리 차지

0x000013 USER[5] 를 위해 미리 차지

0x000014 USER[6] 를 위해 미리 차지

0x000015 USER[7] 를 위해 미리 차지

 

이렇게 동작한다고 보시면 됩니다.

 

 

'BackUp (관리중지) > 자료구조 & 알고리즘' 카테고리의 다른 글

[자료구조] 배열  (0) 2021.04.28

+ Recent posts