예시 코드

class Subject {
    private val observers = mutableListOf<Observer>()

    fun registerObserver(observer: Observer) {
        observers.add(observer)
    }

    fun unregisterObserver(observer: Observer) {
        observers.remove(observer)
    }

    fun notifyObservers() {
        for (observer in observers) {
            observer.notifyObserver()
        }
    }
}
interface Observer {
    fun notifyObserver()
}

class ConcreteObserverA : Observer {
    override fun notifyObserver() {
        // Do something
    }
}

class ConcreteObserverB : Observer {
    override fun notifyObserver() {
        // Do something
    }
}

개념

Observer Pattern은 한 객체의 상태변화를 다른 객체 그룹에 알려야 할 때 사용되는 디자인 패턴이다. 상태변화를 알려야하는 객체 그룹을 미리 알 수 없거나, 그룹요소들이 동적으로 변경되는 경우 사용할 수 있다.

비유

  • 단체 채팅방

    상황

    • 기능 → 단체 채팅방
    • 행동 → 한 명이 채팅을 보내면 채팅방의 구성원들이 알람 수신

    적용

    interface EventListener {
        fun update(msg: String)
    }
    
    class ChattingMessageListener(val name: String) : EventListener {
        override fun update(msg: String) {
            createPushNotification(msg)
        }
    }
    class ChattingRoom {
        private val members = mutableListOf<ChattingMessageListener>()
    
        fun addMember(member: ChattingMessageListener) {
            members.add(member)
        }
    
        fun removeMember(with = member: ChattingMessageListener) {
            members.remove(member)
        }
    
        fun sendMessageToAllMembers(msg: String, author: String) {
            for (member in members) {
                member.update(msg, author)
            }
        }
    }
    
    fun main() {
        val chattingRoom = ChattingRoom()
        
        val members = arrayOf<ChattingMessageListener>(
            ChattingMessageListener("John"),
            ChattingMessageListener("Choi"),
            ChattingMessageListener("Yoon")
        )
        
        members.forEach { member -> chattingRoom.addMember(with = member) }
    
        chattingRoom.sendMessage("Hello", members[1].name)
    }
  • 주식 알림

    상황

    • 기능 → 주식 가격 알림
    • 행동 → 종목 가격이 변경되면 투자자들에게 알림을 보내줌

    적용

    abstract class Stock(val symbol: String, private val price: Double) {
    	private val investors = mutableListOf<IInvestor>()
    
    	fun attach(investor: IInvestor) {
    		investors.add(investor)
    	}
    
    	fun detach(investor: IInvestor) {
    		investors.remove(investor)
    	}
    
    	fun notify() {
    		for (investor in inverstors) {
    			investor.update(this)
    		}
    	}
    
    	fun setPrice(price: Double) {
    		this.price = price
    		notify()
    	}
    }
    class Apple(symbol: String, price: Double) : Stock(symbol, price)
    interface IInvestor {
    	fun update(stock: Stock)
    }
    class Investor(val name: String) : IInvestor {
    	override fun update(stock: Stock) {
    		println("$name is notified that ${stock.symbol}'s price change to ${stock.price}")
    	}
    }

장단점

장점

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

단점

  • 레이스 컨디션
    • 알림을 보내는 중에 옵저버가 등록됐을 때
    • 알림을 보내는 중에 옵저버가 등록해제됐을 때
  • 순환 실행
    • 이벤트 X가 발생했을 때, 옵저버 A가 옵저버 B를 갱신하고, 옵저버 B가 옵저버 A를 갱신한다면 순환 실행이 일어난다.


출처

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

https://ko.wikipedia.org/wiki/옵서버_패턴


Uploaded by N2T

예시 코드

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

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 - 코루틴의 내부 구현

구현하고 싶은 상황

  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를 오버라이딩하면된다.

+ Recent posts