브루노 마스의 이런 노래가 요즘 좋습니다.
오늘은 챌린지반 특강을 복습하며, 과제를 수행했습니다.
기존의 싱글톤, 전략 패턴은 사용했었기에, 옵저버와 데코레이터 패턴을 위주로 공부했습니다.
옵저버는 게임에서 자주 듣던 용어라서 대충 예상은 됐습니다.
자주 언급되는 애는 바로 스타크래프트에서 나온 옵저버인데, 저는 해본 적이 없어서 잘 모르겠습니다.
어쨌든, 뜻은 관찰자라는 뜻이고, 특정 대상의 상태가 변화되면 그걸 관측하고 특정 행동을 취합니다.
우선 코드입니다.
interface Observer {
fun onReceive(news: String)
}
interface Subscriber {
fun subscribe()
}
class NewsLetter(private var observer: Observer) {
fun publish() {
val news = "2024/07/15 소식입니다."
observer.onReceive(news)
}
}
class SubscriberA : Observer, Subscriber {
override fun onReceive(news: String) {
println("구독자 A가 신문을 받았습니다.")
println("신문 내용: $news")
}
override fun subscribe() {
println("구독자 A가 구독을 시작합니다.")
val newsLetter = NewsLetter(this)
newsLetter.publish()
}
}
class SubscriberB : Subscriber {
override fun subscribe() {
println("구독자 B가 구독을 시작합니다.")
NewsLetter(object : Observer {
override fun onReceive(news: String) {
println("구독자 B가 신문을 받았습니다.")
println("신문 내용: $news")
}
}).publish()
}
}
Observer라는 인터페이스에 집중해주셔야 합니다. 저 Observer는 onReceive라는 메소드를 갖고 있고,
이걸 구현하는 것이 중요하기에, 아래와 같이 구독자(옵저버)를 만들어줍니다.
class SubscriberA : Observer, Subscriber {
override fun onReceive(news: String) {
println("구독자 A가 신문을 받았습니다.")
println("신문 내용: $news")
}
override fun subscribe() {
println("구독자 A가 구독을 시작합니다.")
val newsLetter = NewsLetter(this)
newsLetter.publish()
}
}
이 옵저버는 onReceive를 구현하고, subscribe라는 함수를 통해 뉴스 레터로부터 정보를 뿌리게 만듭니다.
class NewsLetter(private var observer: Observer) {
fun publish() {
val news = "2024/07/15 소식입니다."
observer.onReceive(news)
}
}
이 뉴스 레터는 Observer 인터페이스를 구현하는 객체의 onReceive에 news 소식을 전달해주며 호출합니다.
그러면, onReceive로 신문 내용이 호출되게 됩니다.
이게 옵저버 패턴 하면 많이 나오는 예제인데, 저는 의문점이 들었습니다.
그러면 뉴스가 발행했을 때, 구독자들이 어떻게 알아?
그래서 저는 다음과 같은 방법을 사용했습니다.
interface ObserverPractice {
fun onReceive(str: String)
}
class SubscriberPractice: ObserverPractice {
override fun onReceive(str: String) = println("$str \n")
}
class NewsLetterPractice {
// 구독자 관리
private val subscribers: MutableList<ObserverPractice> = mutableListOf()
// 뉴스 내용
private var str = "구독해주셔서 감사합니다."
// 뉴스 내용 변경
fun changeNews(s: String) {
str = s
publish()
}
// 구독자 추가
fun register(listener: ObserverPractice) = subscribers.add(listener)
// 뉴스 발행
fun publish() {
for(i in 0..<subscribers.size) {
println("${i+1} 번 구독자가 받은 내용 : ")
subscribers[i].onReceive(str)
}
}
}
일단 아까 했던 것처럼 Observer Interface를 먼저 만들어줍니다.
이후, 이를 구현하는 Subscriber를 만들고, onReceive 함수까지 구현해줍니다.
여기까지는 똑같고, 이후가 달라집니다. 이전에 쓴 코드는 어떻게 보면 대상의 행동이 제약적이었습니다.
그래서 다음과 같이 대상에게 행동을 좀 더 줬습니다.
class NewsLetterPractice {
// 구독자 관리
private val subscribers: MutableList<ObserverPractice> = mutableListOf()
// 뉴스 내용
private var str = "구독해주셔서 감사합니다."
// 뉴스 내용 변경
fun changeNews(s: String) {
str = s
publish()
}
// 구독자 추가
fun register(listener: ObserverPractice) = subscribers.add(listener)
// 뉴스 발행
fun publish() {
for(i in 0..<subscribers.size) {
println("${i+1} 번 구독자가 받은 내용 : ")
subscribers[i].onReceive(str)
}
}
}
subscribers라는 구독자 리스트를 관리할 수 있게 하고,
뉴스 발행도, 구독자 모두에게 들어갈 수 있도록 만들었습니다.
interface Drink {
val price: Int
fun showInfo() = println("$this => 가격 : ${price}원")
}
interface Coffee: Drink
interface CoffeeDecorator: Coffee
interface Tea: Drink
interface TeaDecorator: Tea
class SimpleCoffee: Coffee {
override val price: Int = 2000
override fun toString(): String = "커피"
}
class Americano: Coffee {
override val price: Int = 3500
override fun toString(): String = "아메리카노"
}
class MilkDecorator(private val coffee: Drink): CoffeeDecorator, TeaDecorator {
override val price: Int
get() = 500 + coffee.price
override fun toString(): String = "우유 넣은, $coffee"
}
class SyrupDecorator(private val coffee: Coffee): CoffeeDecorator {
override val price: Int
get() = 300 + coffee.price
override fun toString(): String = "시럽 넣은, $coffee"
}
class WhipCreamDecorator(private val coffee: Coffee): CoffeeDecorator {
override val price: Int
get() = 1000 + coffee.price
override fun toString(): String = "휘핑 크림 얹은, $coffee"
}
class RedTea: Tea {
override val price: Int = 4000
override fun toString(): String = "홍차"
}
데코레이터는 말 그대로 장식입니다. 크리스마스 트리 장식처럼 추가적으로 장식을 달 수 있습니다.
과제에서는 커피에 시럽, 우유같은 옵션을 추가하는 방식으로 사용했습니다.
class SimpleCoffee: Coffee {
override val price: Int = 2000
override fun toString(): String = "커피"
}
먼저, 기본적으로 꾸며줄 한 객체가 필요합니다. 저희는 커피로 하기로 해서 우선 기본 커피로 정했습니다.
interface Coffee: Drink
interface CoffeeDecorator: Coffee
class MilkDecorator(private val coffee: Drink): CoffeeDecorator, TeaDecorator {
override val price: Int
get() = 500 + coffee.price
override fun toString(): String = "우유 넣은, $coffee"
}
class SyrupDecorator(private val coffee: Coffee): CoffeeDecorator {
override val price: Int
get() = 300 + coffee.price
override fun toString(): String = "시럽 넣은, $coffee"
}
class WhipCreamDecorator(private val coffee: Coffee): CoffeeDecorator {
override val price: Int
get() = 1000 + coffee.price
override fun toString(): String = "휘핑 크림 얹은, $coffee"
}
그리고, CoffeeDecorator Interface를 만들고, 해당 인터페이스를 구현하는 다양한 Decorator들을 만듭니다.
이때, 각 데코레이터들은 Coffee를 구현하는 객체들을 받을 수 있어야 합니다.
저같은 경우는 RedTea까지 만들고, 우유를 넣어줘서 MilkDecorator가 둘 다 받을 수 있게 해줘서 해당 부분만
다르게 설정했습니다.
val coffee = SimpleCoffee()
val milkCoffee = MilkDecorator(coffee)
val syrupCoffee = SyrupDecorator(milkCoffee)
val finalCoffee = WhipCreamDecorator(SyrupDecorator(syrupCoffee))
이후, Main에서 위에서처럼 포장하듯이 만들어주면, 데코레이터 패턴의 완성입니다.
여기서 살짝 아쉬운 점은 바로 아래와 같은 부분입니다.
interface Drink {
val price: Int
fun showInfo() = println("$this => 가격 : ${price}원")
}
인터페이스는 기본적으로 추상화를 전제로 깔고 가는데, 코드가 너무 더러워서 공통된 부분인 showInfo에 기능을 부여하고 말았습니다...
이 부분은 최종적으로 과제가 끝나면, 더 알아보겠습니다.
참고로 위 코드는 이 블로그를 참고했습니다.
이 내용은 이 블로그를 참고했습니다.
아직 확실하게 전부 공부한 것은 아니기에, 가볍게 정리하는 식으로 쓰겠습니다.

이 사진이 요약본입니다.
MVC는 Model-View-Controller의 줄임말로, 이 세 가지 구성 요소로 이루어진 형태입니다.
Model은 세 가지 패턴 모두 동일하게, 로직과 데이터같은 부분을 가리킵니다.
View의 경우, MVC에선 layout.xml 부분에 해당합니다. 단순히 UI를 보여주는 부분입니다.
Controller는 Activity.kt 부분입니다. UI의 데이터를 바꿔주며, 입력받는 부분입니다.
MVC는 사실 처음 접하게 되면, 제일 흔하게 쓰는 방식입니다. 저도 이런 방식으로 썼던 것 같은데,
이 경우 Controller의 힘이 굉장히 강해지게 되고, View와 Model 사이 결합도가 강해지게 됩니다.
그래서 나온 게 MVP입니다.
Model은 이전과 같고, View는 layout.xml과 Activity.kt를 일컫습니다.
Presenter는 View와 Model 사이의 다리같은 역할로, Model, View와 1대1 관계를 가지게 됩니다.
기존의 MVC의 문제 중 하나였던 애매한 경계 대신 Presenter를 추가해, 구분짓게 됐습니다.
그렇기에, View와 Presenter의 결합도가 강해지게 됩니다.
MVVM는 MVP를 개선한 버전입니다. MVP와 Model, View의 내용은 같지만, VM으로 대체됐습니다.
ViewModel은 Presenter와 달리, View로부터 변경됐다는 점만 받아들이게 됩니다.
그리고, 이를 통해 ViewModel에서 변경만 해주고, View는 이걸 가져가서 화면을 구성합니다.
그래서 LiveData를 활용합니다.
일단 제가 아는 부분은 여기까지입니다... 더 공부하고 오겠습니다!
이제 공부했던 내용들을 다듬을 시간입니다.
잘 다듬고 오겠습니다.
끝.