반응형

예시 코드

interface Strategy {
    fun execute(a: Int, b: Int) : Int
}

class ConcreteStrategyAdd : Strategy {
    override fun execute(a: Int, b: Int): Int {
        return a + b
    }
}

class ConcreteStrategySubtract : Strategy {
    override fun execute(a: Int, b: Int): Int {
        return a - b
    }
}

class ConcreteStrategyMultiply : Strategy {
    override fun execute(a: Int, b: Int): Int {
        return a * b
    }
}

class Context(private var strategy: Strategy) {

    fun executeStrategy(a: Int, b: Int): Int {
        return strategy.execute(a, b)
    }

    fun setStrategy(strategy: Strategy) {
        this.strategy = strategy
    }
}
  • Client
    fun main() {
        val context = Context(ConcreteStrategyAdd())
    
        println(context.executeStrategy(10, 5))
    
        context.setStrategy(ConcreteStrategySubtract())
        println(context.executeStrategy(10, 5))
    
        context.setStrategy(ConcreteStrategyMultiply())
        println(context.executeStrategy(10, 5))
    }

개념

Strategy Pattern이란 객체의 특정 행동(메소드)에 대해 다양한 알고리즘을 효율적으로 사용할 수 있게 하거나, 실행 중에 다른 알고리즘으로 전환할 수 있게 하는 디자인 패턴이다.

*효율적 → 코드 재사용성이 높고, 변화에 대응하기 좋은 구조

  1. 특정한 행동을 인터페이스로 만들고,
  1. 구체적인 행동 클래스를 구현한다.
  1. 행동을 할 주체(객체)에서 인터페이스를 참조하고, 구현체로는 인터페이스를 구현한 클래스를 받는다.
    • 행동을 하는 주체(객체)는 행동을 요청하지만 실제로 어떤 행동이 일어나는 지는 모른다.

비유

  • 네비게이션 어플

    상황

    • 기능 → 목적지 안내
    • 행동 → 목적지로 가는 경로 생성
    • 변화

      → 자차 경로, 도보 경로, 자전거 경로 등, 경로에 대한 옵션이 추가될 수 있음

      → 최단 시간, 최소 환승, 최소 비용 등, 경로 탐색 알고리즘이 달라질 수 있음

    적용

    1. 경로를 구하는 알고리즘을 인터페이스로 만든다.
      interface RouteStrategy {
      	fun buildRoute(from: LngLat, to: LngLat) : Route
      }
    1. 구체적인 경로 알고리즘 클래스를 구현한다.
      class MinimumTimeRoute : RouteStrategy {
      	override fun buildRoute(from: LngLat, to: LngLat) : Route {
      		// algorithm..
      	}
      }
      
      class MinimumCostRoute : RouteStrategy {
      	override fun buildRoute(from: LngLat, to: LngLat) : Route {
      		// algorithm..
      	}
      }
    1. 네비게이션은 루트를 구해달라는 요청만 하고, 실제로 어떤 알고리즘을 사용했는지는 모른다.
      class Navigator(private val routeStrategy: RouteStrategy) {
      
      	fun drawRoute(from: LngLat, to: LngLat) {
      		val route = buildRoute(from, to)
      		// draw to screen...
      	}
      
      	fun buildRoute(from: LngLat, to: LngLat) : Route {
      		routeStrategy.buildRoute(from, to)
      	}
      }
      fun main() {
      	val navi = Navigator(MinimumCostRoute())
      	navi.drawRoute(LngLat(37.232, 126.232), LngLat(37.56, 127.022))
      }

장단점

장점

  • OCP (개방-폐쇄 원칙)
    • 확장에는 열려있고, 수정에는 닫혀 있어야 한다.

단점

  • 행동에 대한 알고리즘 별로 없고, 잘 변하지 않는다면 보일러플레이트 코드가 많이 생긴다.
  • 클라이언트가 전략을 선택해야한다.


출처

https://refactoring.guru/ko/design-patterns/strategy

https://ko.wikipedia.org/wiki/전략_패턴


Uploaded by N2T

728x90
반응형

0. 코루틴이란?

  • 코루틴은 비동기 실행을 간단하게 하기위한 concurrency design pattern이다.
    • suspendable computation 기법을 사용한다.
      • 함수의 실행을 특정 지점에서 일시 중단하고 나중에 다시 실행할 수 있는 기법
  • 코루틴(Coroutine)은 코틀린(Kotlin)에만 존재하는 것이 아니다.
    • Python, Go, C#, Js 등에서도 사용하는 개념이다.

1. Main Routine, Sub Routine, Co Routine

Subroutine vs Coroutine

  • 일반적으로 한 함수에서 다른 함수를 실행할 때, 호출되는 함수를 서브루틴이라고 부른다.
fun register() {
	...
	getDeviceToken()
	...
}

fun getDeviceToken() {
	...
}
  • getDeviceToken() 함수는 register() 함수의 서브루틴이다.

 

  • 코루틴은 호출한 함수의 안에서 수행되는 것이 아니라 호출한 함수와 함께 수행되는 루틴이다.

  • coroutine1안에서 coroutine2가 실행되는 것이 아니다.
  • 각 코루틴은 하나의 routine이며 쓰레드를 공유하며 함께 실행되는 것이다.
    • 따라서 병렬적으로 처리하는 것이 아닌 동시적으로 처리하는 기법이다.

Concurrency vs Parallelism

  • CPU의 코어는 1개의 프로세스만 실행할 수 있다.
  • 싱글 코어 CPU에서는 멀티태스킹을 Concurrent하게 처리한다.
    • 실제로 프로세스가 동시에 실행되지 않는다.
    • 시분할을 통해 동시에 실행되는 것 처럼 동작한다.
 

  • 멀티태스킹을 Parallel하게 하려면 코어나 CPU가 여러개 있어야한다.
    • 같은 시간에 여러 프로세스가 실행된다.

2. 동작방식 - H/W

Thread

  • 쓰레드는 같은 프로세스 내에서 Heap, Code, Data를 공유하며, Stack영역을 독립적으로 가진다.
    • Thread단위로 작업하면 context switching 비용이 프로세스에 비해 적어진다.

Coroutine

  • 코루틴의 작업 단위는 Coroutine Object로 JVM의 Heap에 적재된다.
  • 한 쓰레드에서 Coroutine Object만 변경되기 때문에 context switching이 필요없다.
    • 프로그래밍적으로 switching을 할 수 있다.
    • Coroutine을 Lightweight Thread라고도 한다.

3. 사용

코루틴은 크게 3가지로 구성된다.

  • Coroutine Scope
  • Coroutine Context
  • Coroutine Builder

코루틴이 어떤 범위에서 동작하는 지에 대한 Scope를 정하고, 어떤 쓰레드에서 코루틴을 실행할 지 Context를 설정하고, 코루틴 객체를 만드는 Builder로 구성된다.

Scope

  • CoroutineScope
    • 코루틴이 필요할 때 만들고, 필요하지 않을 때 취소할 수 있는 Scope이다.
    • 안드로이드에서는 라이프사이클을 따라간다.
  • GlobalScope
    • Application이 종료될때 까지 유지되는 Scope이다.
    • Singleton으로 만들어져 있다.
    • 메모리 누수가 생기기 쉽다.

Context

Dispatchers

코루틴이 실행이되는 쓰레드를 지정

  • Default
    • 많은 CPU 연산이 필요한 작업을 하기위한 백그라운드 쓰레드
  • IO
    • File, Network, DB IO를 위한 쓰레드
  • Main
    • UI 쓰레드

Builder

launch

  • Job 객체 반환
val job: Job = CoroutineScope(Dispatchers.IO).launch {
 // 비동기함수()
}

async

  • Deferred 객체 반환
    • 람다 안에 마지막 줄이 반환된다.
    • await() 메소드가 호출되면 async가 실행되고 실행을 완료하고 결과를 반환할 때 까지 기다린다.
suspend fun doWorld() {  // this: CoroutineScope
  val deferredInt: Deferred<Int> = CoroutineScope(Dispatchers.IO).async {
		println(1)
		2
	}
  println(3)

	val result: Int = deferredInt.await()
  
  println("$result")
  println(4)
}

// 결과
3
1
2
4

runBlocking

  • 현재 쓰레드를 block하고, 코루틴 함수를 실행한다.

withContext

  • Dispatcher 변환
    • Main 스레드에서 작업하는 중에 DB작업 같은 IO작업이 필요할때 사용할 수 있다.
CoroutineScope(Dispatchers.Main).launch {
	updateUI1()
	updateUI2()
	withContext(Dispatchers.IO) {
		insertUserData()
	}
	updateUI3()
}

Job & Deferred

  • 코틀린에서는 Coroutine Object를 Job이나 Deferred로 정의한다.
    • Deferred는 결과값을 가지는 Job이다.
  • 코루틴의 상태를 가지고 있고 흐름을 관리할 수 있다.
    • start()
      • 중단 없이 실행됨
      • 실행을 기다리지 않는다. 호출한 쓰레드가 종료되면 같이 종료
    • join()
      • Job의 동작이 완료될 때 까지 코루틴 일시 중단
    • cancel()
      • 코루틴을 종료하도록 유도한다. 대기하지 않는다.
    • cancelAndJoin()
      • 코루틴 종료 신호를 보내고 종료할 때 까지 기다린다.
    • cancelChildren()
      • 자식 코루틴 종료
  • Job states cycle
                                          wait children
    +-----+ start  +--------+ complete   +-------------+  finish  +-----------+
    | New | -----> | Active | ---------> | Completing  | -------> | Completed |
    +-----+        +--------+            +-------------+          +-----------+
                     |  cancel / fail       |
                     |     +----------------+
                     |     |
                     V     V
                 +------------+                           finish  +-----------+
                 | Cancelling | --------------------------------> | Cancelled |
                 +------------+                                   +-----------+

예외처리

  • CoroutineExceptionHandler를 사용하여 예외처리를 할 수 있다.
    • 코루틴 안에서 예외 발생 시 exception을 handler로 전달하여 처리하는 흐름을 만들 수 있다.
    • launch vs async
val handler = CoroutineExceptionHandler { _, exception -> 
    println("CoroutineExceptionHandler got $exception") 
}
val job = GlobalScope.launch(handler) { // root coroutine, running in GlobalScope
    throw AssertionError()
}
val deferred = GlobalScope.async(handler) { // also root, but async instead of launch
    throw ArithmeticException() // Nothing will be printed, relying on user to call deferred.await()
}
joinAll(job, deferred)

4. 예시

  • 앱에 로그인 했을 때, Sendbird User를 만들고 디바이스 토큰을 등록하는 상황
    • 유저등록이 완료된 후, 디바이스 토큰을 등록해야 한다.
  • callback
fun initializeSendbird() {
	createUser() {
		registerToken() {
			doSomething() 
		} 
	}
}
  • rx
fun initializeSendbird() {
	Observable
		.just(...)
		.observeOn(MAIN_Thread)
		.subscribeOn(IO_Thread)
		.flatMap { () -> createUser() }
		.flatMap { () -> registerToken() }
		.subscribe( { () -> doSomthing() }, { fail() })
}
  •  coroutine
    • 비동기 작업을 순차적으로 작성할 수 있다.
      • initializeSendbird() 함수가 코루틴이기 때문에, createUser()를 호출하고, 코루틴을 빠져나간다. (다른 스레드에서 실행)
      • createUser()가 완료되면 코루틴으로 들어와서 다음 함수를 호출한다.
suspend fun initializeSendbird() {
    val result = createUser()
    registerToken(result)
    doSomething()
}

 

5. 동작방식 - S/W

  • 코루틴은 어떻게 suspendable하게 동작하는가?
  • 코루틴은 CPS(Conrinuation Passing Style)이라는 형태의 코드로 변환된다.
    • label로 중단점을 나누고, 함수 실행 완료되면, resume을 호출하여 코루틴을 재개한다.
suspend fun myCoroutine(cont: MyContinuation) {
    val userData = fetchUserData()
    val userCache = cacheUserData(userData)
  updateTextView(userCache)
}

⬇️

fun myCoroutine(cont: MyContinuation) {
    when(cont.label) {
        0 -> {
            cont.label = 1
            fetchUserData(cont)
        }
        1 -> {
            val userData = cont.result
            cont.label = 2
            cacheUserData(userData, cont)
        }
        2 -> {
            val userCache = cont.result
            updateTextView(userCache)
        }
    }
}

fun fetchUserData(cont: MyContinuation) {
    val result = "[서버에서 받은 사용자 정보]"
    cont.resumeWith(Result.success(result))
}

출처

[Coroutine] Coroutine(코루틴)과 Subroutine(서브루틴)의 차이 - Coroutine이란 무슨 뜻일까?

Coroutine, Thread 와의 차이와 그 특징

알기쉬운 코루틴 이론::Android Studio에서 Kotlin으로#28

코틀린 코루틴(coroutine) 개념 익히기 · 쾌락코딩

Coroutine exceptions handling | Kotlin

[Kotlin] Coroutine - 코루틴의 내부 구현

728x90
반응형

구현하고 싶은 상황

  1. 현재 웹 뷰로 웹 컨텐츠를 보고있는 상황
  2. FCM을 통해 알림이 온다.
  3. 알림을 누르면 웹 뷰 안에서 새로운 웹 페이지로 이동하게 하고싶다.

구현 방법

  1. Intent만들기 + Flag 설정
  2. 웹뷰를 띄우는 Activity에서 onNewIntnet 오버라이드하기

 

val intent = Intent(this, WebViewActivity::class.java).apply {
    flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
    putExtra("url", "www.example.com")
}

val pendingIntent = PendingIntent.getActivity(this, 1, intent, PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE)

val notification = NotificationCompat.Builder(this, CHANNEL_ID)
    .setContentTitle(title)
    .setContentText(body)
    .setContentIntent(pendingIntent)
    .setAutoCancel(true)
    .build()

notificationManager.notify(CHAT_NOTIFICATION_ID, notification)

이런 식으로 PendingIntent와 Notification을 만든다.

 

@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);

    webView.loadUrl(intent.getExtras().getString("url"));
}

그리고 WebViewActivity 안에서 onNewIntent를 오버라이딩하면된다.

728x90
반응형

문제 상황

Notification을 클릭하면 Intent에 담긴 Extra의 url을 웹뷰로 띄우는 작업이다.

Intent로 전달하는 url을 바꾸어도 이전 url이 계속 로드되는 현상이 발생했다.

해결

pending intent의 request code때문이었다.

PendingIntent.getActivity(this, 1, intent, PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE)

request code인 2번째 파라미터 값을 변경해주면 된다. 

728x90

+ Recent posts