728x90
반응형

싱글턴 패턴이란?

"클래스 인스턴스를 하나만 만들고, 그 인스턴스로의 전역 접근을 제공한다."

 

말 그대로 하나의 클래스로 하나의 객체만 만들겠다는 이야기다.

 

이해를 돕기위해 코드를 살펴보자. 아래는 고전적인 싱글턴 패턴을 구현한 것이다.

 

public class Singleton {
    private static Singleton uniqueInstance;

    private Singleton(){}

    public static Singleton getInstance(){
        if(uniqueInstance == null){
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;

    }
}

자 생성자(Singleton)에 주목해보자. 

private으로 선언함으로써 이 클래스 외에서는 접근이 불가능하다. 즉, new Singleton() 으로 객체 생성이 불가능해진다.

 

그래서 getInstance() 메소드를 통해 static 변수 uniqueInstance에 값이 없으면 객체를 생성해주고, 값이 있으면 그 값을 반환하게 끔 해준다. 이러면 단 Singleton 객체는 단 하나만 존재할 수 밖에 없게 되는 것이다.

 

그런데 위 코드는 멀티 스레딩 시에 문제점이 생긴다. if(uniqueInstance == null) 이 부분에서 각각의 스레드가 true 값을 가지기 때문에 두 개의 스레드에서 각각 객체 생성이 일어난다. 즉, 두 번 이상 객체가 생성되어 싱글턴 패턴에 위반하게 된다. 

 

그러면 어떻게 해결할까? 다양한 방법이 있다. 한 번 살펴보자.


프로그램 시작 시, 인스턴스 생성하기

public class Singleton {
    private static Singleton uniqueInstance = new Singleton();

    private Singleton(){
    }

    public static Singleton getInstance(){
        return uniqueInstance;
    }
}

- 처음부터 Singleton2 인스턴스를 만들어보자. 정적 초기화 부분에서 싱글턴 패턴을 생성하면서 스레드를 사용해도 아무 문제 없이 작동하는 것을 확인할 수 있을 것이다.

 


DCL (Double Checking Locking)

public class Singleton {

    private volatile static Singleton instance = null;
    
    private Singleton() {

    }
    
    public static Singleton getInstance() {
       if (instance == null) {
          synchronized (Singleton.class){
             if(instance == null) {
                instance = new Singleton();
             }
          }
        }
       return instance;
    }
    
}

- 인스턴스가 생성되어 있는지 확인 후, 생성되어 있지 않았을 때만 동기화를 시킴

- volatile

    -> 변수를 CPU cache에 저장하지 않고 메모리에서 읽고 저장한다.

    -> 스레드를 사용할 때 다른 프로세서에 있는 cache에 변수값이 저장되어 서로 다른 값을 사용하는 것을 방지한다.

- synchronized

     -> 여러 스레드에서 사용하려고 할 때 locking 매커니즘을 제공하여 한 번에 한 개 스레드만 사용할 수 있도록 한다.

 

 


inner static class 사용

 

public class Singleton {
    private static class InnerSingleton{
       static final Singleton instance = new Singleton();
    }
    
    private Singleton() {
       System.out.println("Singleton constructor");
    }
    
    public static Singleton getInstance() {
       return InnerSingleton.instance;
    }
    
    public void print() {
       System.out.println("Singleton instance hashCode = " + InnerSingleton.instance.hashCode());
    }
}

- 내부 정적 클래스를 사용하면 JVM에서는 해당 싱글턴 클래스를 메모리에 load할 때, 정적 멤버 변수가 없으므로 싱글턴 인스턴스를 생성하지 않는다.

 


enum

자 이제까지 싱글턴 패턴의 원리를 알아봤다. 근데 사실 enum 키워드로 모든 것이 해결된다.

public enum Singleton3{
    UNIQUE_INSTANCE;
}

... 

728x90
반응형
728x90
반응형

옵저버 패턴 (observer pattern)

정의를 살펴보기 전에 클래스 다이어그램부터 살펴보자!

당연히 이해가 되지 않을 것이다. 

 

옵저버 패턴이란?

 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체에게 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다 의존성을 정의한다.

 

역시 무슨 말인지 모르겠다.

 

유튜브가 가장 좋은 예시인 것 같다.

우리가 유튜브의 쯔양의 먹방 유튜브 채널을 구독과 알림 설정을 했다고 치자. 그러면 우리는 쯔양이 실시간 방송을 하거나 컨텐츠를 올렸을 때 우리는 알림을 받게 된다. 이게 옵저버 패턴이다. 간단하지 않은가?

 

처음 봤던 클래스 다이어그램을 다시 보며 조금 더 들어가보자.

 쯔양 유튜브 채널은 Subject가 된다. 즉, 구독(registerObserver)을 하거나 구독 취소(unregisterObserver)을 하는 역할을 하고 영상이 올라왔다고 알려준다.(notifyObservers)

 구독자들(Observer)은 그저 구독을 하면 알림이 온다.(update)

ConcreteSubject, ConcreteObserver는 각각 Subject 인터페이스, Observer 인터페이스의 구현이라고 보면 된다.

그래서 저 클래스 다이어그램의 구조가 옵저버 패턴이라고 볼 수 있지.

 

---

(심화) 구현 예제

 이제는 다른 예제로 코드로 살펴보자. 이 예제는 헤드퍼스트 디자인 패턴의 예제를 가져왔다.

 

기상 데이터 중 습도, 온도, 기압의 정보를 가져와서 스마트폰 디스플레이에 띄워본다고 생각하자. 데이터 정보는 WeatherData라는 객체에 담길 것이다.

 

그럼 여기서 Subject는 WeatherData가 될 것이고, Observer는 각각의 디스플레이가 될 것이다. 클래스 다이어그램을 살펴보자.

 모든 클래스를 구현할 것은 아니다. 일단 Subject, Observer 인터페이스 부터 살펴보자.

public interface Subject {
    public void registerObserver(Observer o);
    public void removeObserver(Observer o);
    public void notifyObserver();
}

 

public interface Observer {
    public void update(float temperature, float humidity, float pressure);
}

간단하다. 

Subject의 역할은 Observer의 등록을 받아주거나 삭제하거나 Observer에게 알려주는 것이다.

Observer은 Subject에서 알려준 정보를 update하면 된다.

여기까지만 이해해도 옵저버 패턴에 대해서는 이해한 것이다. Subject, Observer를 구현한 것을 살펴보자.

 

public class WeatherData implements Subject{
    private List<Observer> observers;
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData(){
        observers = new ArrayList<Observer>();
    }
    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        observers.remove(o);
    }

    @Override
    public void notifyObserver() {
        for(Observer observer:observers){
            observer.update(temperature,humidity,pressure);
        }
    }

    public void measurementsChanged(){
        notifyObserver();
    }

    public void setMeasurements(float temperature,float humidity, float pressure){
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }
}

위는 WeatherData는 Subject의 구현부 즉, ConcreteSubject이다. 쯔양 유튜브 채널이라고 생각하면 된다.

온도, 습도, 기압 데이터를 입력 받아 이를 Observer에게 알려준다.

 

 

public class CurrentConditionsDisplay implements Observer,DisplayElement{
    private float temperature;
    private float humidity;
    private WeatherData weatherData;

    public CurrentConditionsDisplay(WeatherData weatherData){
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }
    @Override
    public void update(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        display();
    }

    @Override
    public void display() {
        System.out.println("현재 상태: 온도 "+temperature +"F, 습도 "+humidity+"%");
    }
}

위 코드는 Observer의 구현부 즉, ConcreteObserver로 여러 구독자 중 한 명이라고 생각하면 된다.

그저 Subject에서 제공된 데이터를 update하면 된다.

클래스 다이어그램의 모든 것을 구현한 것은 아니다. 옵저버 패턴에 대해 이해할 정도로만 알아두자!

 

정리

 옵저버 패턴은   "한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체에게 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다 의존성을 정의한다." 이제는 이 문장이 조금 이해되었을 것이라고 생각한다.

 

 

728x90
반응형
728x90
반응형

람다식(Lambda Expression)

 

람다식은 함수의 이름과 return 값이 없는 익명 함수이다.

무슨 소리냐? 코드로 한 번 보자.

두 숫자를 파라미터로 받아 더한 값을 return 하는 함수를 만들어보자.

 

fun sum1(x:Int,y:Int):Int = x+y

fun main(){
	sum1(1,2)
}

 

보통 이런식이다. 함수를 만든다 → 함수를 호출하여 객체에 담는다. 이 과정을 줄일 수 없을까하는 생각에서 람다식이 등장한다. 한 번 살펴보자.

 

fun main(){
	val sum2 = {x:Int,y:Int -> x+y}
	sum2(1,2)
}

 

x:Int, y:Int 는 파라미터와 타입, x+y은 본문이다. x, y가 각각 1, 2 라면 x+y 3이고 sum2 는 3이 되는 거지!

가독성 면에서나 함수를 따로 만들지 않아도 된다는 점에서 장점이 있다.

더하기 함수 말고 다른 함수로 람다식이 변할 수 있는지 살펴보자!

 

val people = listOf<Person>(Person("김철수",22),Person("박철수",33))

 

Person이라는 이름과 나이를 저장하는 데이터 클래스를 만들고 person 이라는 리스트에 각각 담았다. 데이터 클래스는 생략이다… 이 람다식 배울 때면 알아서 할 수 있으리라 믿는다.

자 이 리스트에서 가장 나이가 많은 사람을 골라내보자. 반복문을 돌려도 되지만 람다식을 활용하여 라이브러리 함수를 사용하겠다!

 

people.maxBy ({ p:Person -> p.age })

 

자 앞에서 봤던 철저하게 원칙을 지킨 람다 함수이다. 조금 더 줄여볼까?

 

people.maxBy (){ p:Person -> p.age}

 

줄였다기보다는 () 괄호에서 빼냈네… 관례 상 맨 뒤의 파라미터가 람다식이면 괄호 밖으로 뺄 수 있다.

조금만 더 줄여보자.

 

people.maxBy { p:Person -> p.age}

 

줄긴 줄었는데…? 별로 티도 나지 않네… 정답은 ()가 없어졌다.

maxBy() 처럼 파라미터가 하나이고 그 하나가 람다식이다? 그럼 생략 가능하다.

아 뭔가 아쉽다. 줄긴 줄었는데 뭐를 싸다가 끊긴 느낌…? 조금만 더 해보자.

 

people.maxBy{ p-> p.age}

 

자 자바와 달리 코틀린의 장점 중에서 컴파일러가 타입을 유추할 수 있으면 생략 가능하다고 했었다.

people이 Person 타입의 객체가 들어있는 컬렉션인건 너도 알고 있잖아? 근데 나보다 똑똑한 컴파일러가 모르겠냐구…

자 진짜 마지막이다. 한 번 더 줄여보자.

 

people.maxBy{ it.age}

 

?? 급발진인가?? ㅋㅋ 어쨌던 짧아졌다…

람다식의 파라미터가 하나이고(여기선 p) 컴파일러가 타입을 유추할 수 있을 때 “it” 이라는 키워드를 사용할 수 있다.

자 과정이 좀 길었다… 이렇게까지 길게 할 부분인가 싶긴 한데 내가 안드로이드 처음 할 때 저게 어떻게 저렇게 되는지 이해를 못했거든.. ㅎ

 

최종 정리

 

정통 람다식

people.maxBy ({ p:Person -> p.age })

야매? 줄인 람다식

people.maxBy{ it.age}

둘은 같은 표현이다!!

코틀린을 사용하면 밑에 표현을 많이 접하게 될 것이다! 잘 봐둬라!!

728x90
반응형

+ Recent posts