The singleton pattern is one of the creational design patterns that is used to limit the instances of a class to one. That means with the Singleton pattern, only one instance of a class exists in the whole project with global access.
Singleton is a creational design pattern that lets you ensure that a class has only one instance while providing a global access point to this instance.
Singleton Pattern은 객체의 인스턴스가 오직 1개만 생성되는 패턴으로 생성자가 여러 차례 호출되어도 실제로 생성되는 객체는 하나이고 맨 처음에 생성된 이후에 호출된 생성자는 최초의 생성자가 생성한 객체를 return한다.
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
public void say() {
System.out.println("hi, there");
}
}
Singleton Pattern의 기본적인 구현 방법으로 유의해야할 점은 다음과 같다.
다른 방법으로는 companion object
를 사용하는 방법이 있다.
class EventManager private constructor() {
companion object{
private val instance = EventManager()
fun sharedInstance(): EventManager {
return instance
}
}
}
먼저 companion object
는 클래스 내부의 자바의 static과 비슷한 역할을 수행한다고 볼 수 있다.
(물론 정확하게 분류하면 companion object는 객체이며 하나의 클래스에는 오직 하나의 companion object만 존재할 수 있는 등 엄연히 다르다.)
private constructor()
를 통해 외부에서 직접 인스턴스를 생성할 수 없다.EventManager
클래스 내부에 companion object
가 있기 때문에, sharedInstance()
메서드에 EventManager.sharedInstance()
와 같이 직접 접근할 수 있다.EventManage
의 유일한 인스턴스가 companion object
내부에서 생성되어 이 변수는 private이므로 외부에서는 접근할 수 없다.여기서 보다 더 안전하게 Singleton Pattern을 구현하고자 한다면 Singleton 인스턴스를 @Votaile
로 표시하면 된다.
@Volatile
은 변수에 대한 스레드 간 일관성을 보장하기 위해 사용되는 키워드로 주로 멀티스레드 환경에서 특정 변수에 여러 스레드가 동시에 접근할 때, 각 스레드가 항상 최신의 값을 읽도록 강제하는 역할을 한다.
class Singleton private constructor() {
companion object {
@Volatile private var instance: Singleton? = null
fun getInstance() =
instance ?: synchronized(this) {
instance ?: Singleton().also { instance = it }
}
}
}
이 코드에서는 instance가 null일 경우 synchronized
블록에 진입하여 인스턴스를 생성한다. 이 블록은 한 번에 하나의 스레드만 진입할 수 있어, 여러 스레드가 동시에 인스턴스를 생성하는 것을 방지한다.
@Volotile
로 표시했을 때는 다음과 같은 이점이 존재한다.
1. 메모리 측면
2. 데이터 공유가 쉽다
@Volatile
을 사용하거나 설계에 유념1. 확장성 및 테스트 가능성 감소
2.복잡성 및 위험 증가
Adapter Pattern은 기존 클래스의 인터페이스를 사용하고자 하는 다른 인터페이스로 변환해주어 호환되지 않는 인터페이스를 가진 객체들이 협업할 수 있도록 어댑터로서 사용되는 구조 패턴이다.
Adapter Pattern의 구성요소는
// A 인터페이스
interface AInterface {
fun methodA()
}
// B 인터페이스
interface BInterface {
fun methodB()
}
// A 인터페이스 구현 클래스
class AClass : AInterface {
override fun methodA() {
println("A 클래스의 methodA 호출")
}
}
// 어댑터 클래스: B 인터페이스를 구현하면서 내부적으로 A 클래스를 사용
class AtoBAdapter(private val aClass: AClass) : BInterface {
override fun methodB() {
println("어댑터가 B 인터페이스의 methodB를 호출함.")
aClass.methodA() // A 클래스의 methodA를 호출하여 B 인터페이스에 맞게 동작
}
}
// 클라이언트 코드: B 인터페이스를 기대
fun clientCode(bInterface: BInterface) {
bInterface.methodB()
}
// 사용 예시
fun main() {
val aClass = AClass() // A 인터페이스를 구현하는 클래스
val adapter = AtoBAdapter(aClass) // AtoBAdapter로 AClass를 B 인터페이스에 맞게 변환
clientCode(adapter) // 클라이언트 코드는 B 인터페이스를 사용하는 것처럼 작동
}
AClass
객체를 AtoBAdapter
로 감싸서 clientCode()
에 전달함으로써 AClass
를 BInterface
처럼 사용할 수 있다.
Adapter Pattern은 호환 작업 방식에 따라 두 종류로 나뉜다.
- 객체 어댑터(Object Adatper)
- 클래스 어댑터(Class Adatper)
객체 어댑터는 Composition(합성)을 통해 구현한다.
객체 어댑터 방식은 기존 클래스(Adaptee)의 인스턴스를 Adapter 클래스 내부에 포함시켜 필요한 인터페이스(Target)를 변환해주는 방식으로 Adatper 클래스가 원래 클래스의 인스턴스를 포함하고, 해당 인스턴스의 메서드를 호출하여 인터페이스 간의 호환성을 제공하는 방식이다.
코드로 보면 이와 같은 형태이다.
interface TargetInterface {
fun request() // 클라이언트가 호출하는 메서드
}
class AdapteeClass {
fun specificRequest() {
println("기존 클래스의 specificRequest 메서드 호출")
}
}
class ObjectAdapter(private val adaptee: AdapteeClass) : TargetInterface {
override fun request() {
println("어댑터에서 request를 호출하여 specificRequest로 변환")
adaptee.specificRequest()
}
}
fun clientCode(target: TargetInterface) {
target.request()
}
fun main() {
val adaptee = AdapteeClass()
val adapter = ObjectAdapter(adaptee)
clientCode(adapter)
}
Client는 TargetInterface
의 request()
를 호출하지만 ObjectAdapter
를 통해 AdapteeClass
의 speicifcRequest()
가 호출된다.
장점
단점
클래스 어댑터는 상속(Inheritance)를 통해 구현된다.
Adapter 클래스가 두 인터페이스를 모두 구현하고 기존 클래스의 기능을 상속하여 원하는 인터페이스로 변환하는 방식의 Adapter이다.
객체 어댑터와 다른 점은 Adaptee(Service)를 상속했기 때문에 따로 객체 구현없이 바로 코드 재사용이 가능하다는 것이다.
즉 클래스 어댑터는 클라이언트와 서비스 양쪽에서 행동들을 상속받기에 객체를 래핑할 필요가 없다.
그러나 클래스 어댑터는 다중 상속을 통해 구현되므로 다중 상속을 지원하지 않는 언어에서는 구현하기 용이하지 않다.
코드로 보면 이와같은 형태이다
interface BInterface {
fun methodB()
}
open class AClass {
fun methodA() {
println("A 클래스의 methodA 호출")
}
}
class ClassAdapter : AClass(), BInterface {
override fun methodB() {
println("어댑터에서 B 인터페이스의 methodB를 호출함")
methodA()
}
}
fun clientCode(bInterface: BInterface) {
bInterface.methodB()
}
fun main() {
val adapter = ClassAdapter()
clientCode(adapter)
}
Client
는 ClassAdpater
는 BInterface
를 구현하고, AClass
를 상속하여 methodB()
를 override하여 상속받은 methodA()
를 호출한다.
장점
단점
1.호환성 제공
2.재사용성 증가
3.유연성 제공
복잡도 증가
옵서버 패턴(observer pattern)은 객체의 상태 변화를 관찰하는 관찰자들, 즉 옵저버들의 목록을 객체에 등록하여 상태 변화가 있을 때마다 메서드 등을 통해 객체가 직접 목록의 각 옵저버에게 통지하도록 하는 디자인 패턴이다. 주로 분산 이벤트 핸들링 시스템을 구현하는 데 사용된다. 발행/구독 모델로 알려져 있기도 하다.
- 위키피디아
즉 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 알림을 보내는 방식으로 상태 변화가 여러 객체에 전파되어야 하는 경우에 다른 객체의 상태 변화를 별도의 함수 호출 없이 즉각적으로 알 수 있어 이벤트에 대한 처리를 자주해야 하는 프로그램에서 매우 유용하다.
Observer Pattern의 구성요소는 패턴의 이름대로 객체를 Observe하는 Observer
와 Observe되는 Subject
로 이루어져있다.
(Subject
는 Publisher
, Observable
라고도 하고 Observer
는 Subscriber
라고도 한다.)
Subject
는 기본적으로 3개의 기능을 갖는다. 각각 attach
, detach
, notify
로
attach
detach
notify
의 기능을 수행한다.
코드로 보면
class Subject {
private val observers = mutableListOf<Observer>()
fun move() {
println("Subject 이동")
notify("Subject가 이동함")
}
fun stop() {
println("Subject 정지")
notify("Subject가 멈춤")
}
fun attach(observer: Observer) {
observers.add(observer)
}
fun detach(observer: Observer) {
observers.remove(observer)
}
fun notify(message: String) {
observers.forEach { it.update(message) }
}
}
중요한 점은 Subject의 상태가 변하면 notify()
를 호출하여 Observer
를 update
해준다.
Observer
는 Subject
가 notify
한 내용을 통해 update
하는 기능이 필요하다.
코드로 보면
interface Observer {
fun update(message: String)
}
class ConcreteObserver(private val name: String) : Observer {
override fun update(message: String) {
println("$name received message: $message")
}
}
Subject
로 부터 notify
된 내용을 토대로 update
메소드를 통해 상태 변화를 자동으로 수행할 수 있다.
참고로 Java에서는 Observer 클래스를 지원했었으나 멀티스레딩 환경에서의 비효율성과 유연성 문제로 Java 9부터 Deprecated 되었다.
그러나 여기서도 동시성에 대한 문제가 발생할 수 있다.
예를 들어 notify
가 실행되는 동안 다른 스레드에서 새로운 Observer
가 attach
를 통해 등록되면, 이 새로운 Observer
는 현재 진행 중인 알림 과정에 포함되지 않는 문제가 발생할 수 있다.
이것을 해결하는 다양한 방법들 중 하나는 lock
을 이용하는 것이다.
Lock은 스레드가 공유 자원에 접근할 때, 하나의 스레드만 접근하도록 잠금(lock)을 걸어 데이터 일관성을 보장하는 도구
어느 한 메소드가 실행중이라면 다른 메소드를 실행하지 못하도록 lock을 걸어주는 것이다.
import java.util.concurrent.locks.ReentrantLock
class Subject {
private val observers = mutableListOf<Observer>()
private val lock = ReentrantLock()
fun attach(observer: Observer) {
lock.lock()
try {
observers.add(observer)
} finally {
lock.unlock()
}
}
fun detach(observer: Observer) {
lock.lock()
try {
observers.remove(observer)
} finally {
lock.unlock()
}
}
fun notify(message: String) {
val snapshot = lock.withLock { observers.toList() }
for (observer in snapshot) {
observer.update(message)
}
}
}
interface Observer {
fun update(message: String)
}
class ConcreteObserver(private val name: String) : Observer {
override fun update(message: String) {
println("$name received message: $message")
}
}
fun main() {
val subject = Subject()
val observer1 = ConcreteObserver("Observer 1")
val observer2 = ConcreteObserver("Observer 2")
subject.attach(observer1)
subject.attach(observer2)
subject.notify("New Update!")
}
위 코드에서는 notify
, attach
, detach
메소드에서 lock
을 사용하여 동시에 Observer
가 추가되거나 제거되는 상황을 방지하고 notify
메소드에서는 Observer
목록을 스냅샷으로 복사하여, 알림을 보내는 도중에 목록이 변경되는 문제를 방지한다.
그 외에는 @Synchronized
블록을 사용하는 방법도 있다.
class Subject {
private val observers = mutableListOf<Observer>()
@Synchronized
fun attach(observer: Observer) {
observers.add(observer)
}
@Synchronized
fun detach(observer: Observer) {
observers.remove(observer)
}
@Synchronized
fun notify(message: String) {
// Snapshot을 만들어 동시성 문제 방지
val snapshot = observers.toList()
for (observer in snapshot) {
observer.update(message)
}
}
}
1.느슨한 결합
2.확장성
1.메모리 누수 가능성
2.알림 순서 보장 어려움
https://medium.com/@ZahraHeydari/singleton-pattern-in-kotlin-b09380c53b14
https://velog.io/@seongwon97/%EC%8B%B1%EA%B8%80%ED%86%A4Singleton-%ED%8C%A8%ED%84%B4%EC%9D%B4%EB%9E%80
https://tecoble.techcourse.co.kr/post/2020-11-07-singleton/
https://onlyfor-me-blog.tistory.com/441
https://jusungpark.tistory.com/22
https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%EC%96%B4%EB%8C%91%ED%84%B0Adaptor-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90
https://refactoring.guru/ko/design-patterns/adapter
https://velog.io/@haero_kim/%EC%9A%B0%EB%A6%AC%EB%8A%94-%EC%9D%B4%EB%AF%B8-%EC%96%B4%EB%8C%91%ED%84%B0-%ED%8C%A8%ED%84%B4%EC%9D%84-%EC%95%8C%EA%B3%A0-%EC%9E%88%EB%8B%A4
https://pjh3749.tistory.com/266
https://ko.wikipedia.org/wiki/%EC%98%B5%EC%84%9C%EB%B2%84_%ED%8C%A8%ED%84%B4
https://velog.io/@hanna2100/%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4-2.-%EC%98%B5%EC%A0%80%EB%B2%84-%ED%8C%A8%ED%84%B4-%EA%B0%9C%EB%85%90%EA%B3%BC-%EC%98%88%EC%A0%9C-observer-pattern
https://refactoring.guru/design-patterns/observer