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
반응형

일단 옵저버 패턴을 알아야 한다. 잘 정리해놓은 사이트가 있으니 참고하길 바란다.

https://codingjobrice.tistory.com/153

 

[디자인 패턴 기초] 옵저버 패턴(observer pattern)

옵저버 패턴 (observer pattern) 정의를 살펴보기 전에 클래스 다이어그램부터 살펴보자! 당연히 이해가 되지 않을 것이다. 옵저버 패턴이란? 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체

codingjobrice.tistory.com

 

setOnClickerListener는 옵저버 패턴

우리가 안드로이드 프로그래밍을 하면서 버튼 클릭 수행 시 

binding.button.setOnClickListener{
            // 버튼 클릭 시 수행할 로직 구현
}

위와 같은 코드를 사용한다.

이 곳에는 옵저버 패턴이 사용된다. 어떻게 사용되는지 뜯어보자.

 

저 위의 코드는 너무 축약되었다. 이해하기 좋게 원시적으로 돌아가보자.

 

val button = binding.button

button.setOnClickListener(View.OnClickListener { 
            // 로직 구현
})

 

우선 Subject를 찾아보자. 

Subject는 이벤트를 발생시키는 UI 요소 즉, 버튼 UI이다.  여기선 button 이겠지.

btn은 클릭 이벤트를 감지하고, 등록된 Observer에게 클릭 이벤트가 발생했다고 알려준다.

 

그럼 여기서 Observer는 무엇인가?

OnClickListener가 Observer 역할을 한다. button에서 클릭 이벤트를 감지했음을 OnClickListener에게 알려주고 OnClickListener는 구현한 로직을 수행하게 된다.

 

그냥 생각없이 써왔는데 옵저버 패턴이였던 것이었다... 소름

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
반응형
728x90
반응형

문제

https://www.acmicpc.net/problem/1789

 

1789번: 수들의 합

첫째 줄에 자연수 S(1 ≤ S ≤ 4,294,967,295)가 주어진다.

www.acmicpc.net

 

문제 접근

일단 그리디로 접근했다. 

1부터 순서대로 더해가면서 입력값보다 큰 수가 나오는 경우에서 count -1 을 출력하면 된다.

즉, 11을 입력했을 때 

1+2+3+4+5 = 15 이고 15에서 4라는 자연수를 빼면 11이 된다.

 

코드

s= int(input())

count = 0
total = 0
for i in range(1,s):
    count+=1
    total += i
    if total > s:
        count -= 1
        break
if s == 1 or s== 2:
    count = 1

print(count)

 

배운점

예외 찾는 부분에서 애먹었다. 입력값을 200으로 잡고 total을 다 출력을 해봤는데 틀린게 없었다.

1이나 소수인 경우에 좀 예외인 경우가 종종 있으니 앞으로 주의하자! 이 경우에는 1과 2일 때 예외여서 처리를 해주었다!

 

728x90
반응형
728x90
반응형

문제

https://www.acmicpc.net/problem/13305

 

13305번: 주유소

표준 입력으로 다음 정보가 주어진다. 첫 번째 줄에는 도시의 개수를 나타내는 정수 N(2 ≤ N ≤ 100,000)이 주어진다. 다음 줄에는 인접한 두 도시를 연결하는 도로의 길이가 제일 왼쪽 도로부터 N-1

www.acmicpc.net

 

문제 접근

그리디 문제이지만 정렬하여 최솟값 부터 접근하는 문제는 아니다.

반복문을 돌면서 거리의 리스트인 distances는 하나하나 모두 방문하고,

리터당 가격의 리스트인 costs는 이전의 지역보다 작은 경우에만 바꿔주고 다른 경우에는 그냥 진행하면 된다고 생각하여 풀었다.

 

코드

n = int(input())

distances = list(map(int,input().split()))
costs = list(map(int,input().split()))

total = 0
j=0

for i in range(0,len(distances)):
    total += costs[j] * distances[i]
    if costs[j] > costs[i+1]:
        j = i+1
           
print(total)

 

배운점

i, j 부분에서 조금 실수해서 17점이 나오던데 모든 경우에 대해 print(total)를 해서 어느 부분에서 틀렸는지 잘 찾아서 해결했다.

나는 문제 겨우 풀고 다른 코드들 봤는데 당연하다는 듯이 쉽게 풀던데... 아직 멀었다... 공부 더 하자!!!

 

728x90
반응형

+ Recent posts