싱글턴 패턴 정의

싱글턴 패턴은 해당 클래스의 인스턴스가 하나만 만들어지고, 어디서든지 그 인스턴스에 접근할 수 있도록 하기 위한 패턴입니다.

싱글턴 패턴 사용방법은 크게 3가지로 나뉘어진다.

  1. getInstance() 메소드에 synchronized 키워드를 사용
  2. 인스턴스를 필요할 때 생성하지 말고, 처음부터 만듦
  3. DCL(Double-Checking Locking) 을 써서 getInstance()에서 동기화 되는 부분을 줄임

첫번째 방법부터 보자.

class Singleton {
    private static Singleton uniqueInstance;

    private Singleton() {//생성자를 외부에서 사용하지 못하도록 private 으로 셋팅
    }

    public static synchronized Singleton getInstance() {
        //synchronized 를 사용해 서로 다른 스레드에서 실행 시 하나의 스레드가 끝난 뒤 다른 스레드가 접근 가능
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }

}

위의 방법처럼 메소드를 동기화 하게 되면 속도가 100배 정도 저하된다고 한다. 속도가 크게 중요하지 않은 부분에서 사용하면 괜춘~!

두번째 방법을 보자.

class Singleton {
    private static Singleton uniqueInstance = new Singleton();//정적 초기화 부분에서 생성
    
    private Singleton() {
    }

    public static Singleton getInstance() {
        return uniqueInstance;
    }

}

위의 방법처럼 사용하면 JVM에서 Singleton 의 유일한 인스턴스를 생성해준다. JVM에서 유일한 인스턴스를 생성하기 전에는 어떤 스레드도 uniqueInstance 에 접근 할 수 없다.

세번째 방법을 보자.

class Singleton {
    private volatile static Singleton uniqueInstance;

    private Singleton() {
    }

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

}

volatile 키워드를 사용하면 멀티 스레딩을 쓰더라도 uniqueinstance 변수가 Singleton 인스턴스로 초기화 되는 과정이 올바르게 진행되도록 할 수 있다.
하지만 자바5 보다 전에 나온 버전의 JVM 을 사용해야 한다면 DCL을 사용할 수 없다.

일반적인 변수의 값은 CPU cache 를 통해 가져 오는데 volatile 키워드를 사용하면 CPU cache가 아닌 MainMemory 에서 가져온다.
그렇기 떄문에 읽고 쓰는 작업을 할 때 제대로 된 값을 읽고 쓸 수 있다.
하지만 멀티 스레드에서 값을 가져올 때 synchronized 를 사용하지 않고 가져오면 완벽하게 보장 되지 않기 때문에 위의 코드를 보면 volatile uniqueInstance 를 초기화 할때 synchronized 를 사용한 것을 볼 수 있다.
이렇게 사용함으로써 안전하게 초기화 하고 instance를 가져와서 사용할 수 있다.
코틀린에서는 아주 간단하게 싱글턴을 만들어 사용한다.

object Singleton {

}

위의 코드가 끝이다.
해당 코틀린 코드는 자바로 Decompile 했을때 1,2,3 방법 중 어떤 방법으로 만들어 질까 궁금해서 한번 해봤다.
Android Studio 에서 Menu > Tools > Kotlin > Show Kotlin Bytecode > Decompile !!

public final class Singleton {
   public static final Singleton INSTANCE;

   private Singleton() {
   }

   static {
      Singleton var0 = new Singleton();
      INSTANCE = var0;
   }
}

두번째 방법으로 만들어지는걸 볼 수 있었다.
이상 싱글턴 패턴에 대해 알아보았다.

참고 URL
http://tutorials.jenkov.com/java-concurrency/volatile.html

팩토리 메소드 패턴과 추상 팩토리 패턴

팩토리 메소드 정의

객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정하게 한다.

class FactoryMethodPatternTest {
    @Test
    fun testCreateModel() {
        val yellowMemo = ClassFactory().create("Yellow")
        yellowMemo.onClick()
        val blueMemo = ClassFactory().create("Blue")
        blueMemo.onClick()
        val empty = ClassFactory().create("Else")
        empty.onClick()
    }

    abstract class ModelFactory {
        abstract fun create(className: String): Model
    }

    class ClassFactory : ModelFactory() {
        override fun create(className: String): Model {
            return when (className.toLowerCase()) {
                "yellow" -> {
                    YellowMemo()
                }
                "blue" -> {
                    BlueMemo()
                }
                else -> {
                    Empty()
                }
            }
        }
    }

    abstract class Model {
        abstract fun onClick()
    }

    class YellowMemo : Model() {
        override fun onClick() {
            println("YellowMemo 클릭")
        }
    }

    class BlueMemo : Model() {
        override fun onClick() {
            println("BlueMemo 클릭")
        }
    }

    class Empty : Model() {
        override fun onClick() {
            println("Empty 클릭")
        }
    }
}


위의 코드 테스트 결과이미지

추상클래스 ModelFactory 의 create fun 추상 메소드를 통해 모델을 만들고 각각을 클릭 했을 때 print 내용은 달라진다.
모델을 직접 만들지 않고 Factory 를 통해 만들어서 사용하였다.
하지만 이렇게 되면 create 될때 className 에 의해 모두 모델을 ClassFactory에서 만들어 주어야 한다.
모델이 셀수 없게 많아지고 해당 모델들을 관리 해야 한다면??
모든 모델들을 ClassFactory에 추가 해야하기 때문에 비효율적이다.
위의 문제점을 보완한 아래의 내용을 보자.

추상팩토리 정의

추상 팩토리 패턴에서는 인터페이스를 이용하여 서로 연관된 또는 의존하는 객체인 구상 클래스를 지정하지 않고도 생성할 수 있다.
Android Architecture Component 에 있는 ViewModel 을 보자.

val viewModel = ViewModelProviders.of(this).get(MemoViewModel::class.java)

ViewModelProviders.of(this) 는 ViewModelProvider 를 반환 하고 get(MemoViewModel::class.java)을 통해 MemoViewModel 을 반환한다.
ViewModelProvider를 자세히 살펴 보자.

public class ViewModelProvider {

@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
    String canonicalName = modelClass.getCanonicalName();
    if (canonicalName == null) {
        throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
    }
    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}

@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    ViewModel viewModel = mViewModelStore.get(key);

    if (modelClass.isInstance(viewModel)) {
        return (T) viewModel;
    } else {
        if (viewModel != null) {
            }
        }
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
        } else {
            viewModel = (mFactory).create(modelClass);
        }
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }

abstract static class KeyedFactory implements Factory {
    @NonNull
    public abstract <T extends ViewModel> T create(@NonNull String key,
            @NonNull Class<T> modelClass);

    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        throw new UnsupportedOperationException("create(String, Class<?>) must be called on "
                + "implementaions of KeyedFactory");
    }
}

public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory {

    private static AndroidViewModelFactory sInstance;

    @NonNull
    public static AndroidViewModelFactory getInstance(@NonNull Application application) {
        if (sInstance == null) {
            sInstance = new AndroidViewModelFactory(application);
        }
        return sInstance;
    }

    private Application mApplication;

    public AndroidViewModelFactory(@NonNull Application application) {
        mApplication = application;
    }

    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
            try {
                return modelClass.getConstructor(Application.class).newInstance(mApplication);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (InstantiationException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (InvocationTargetException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            }
        }
        return super.create(modelClass);
    }
}	

public static class NewInstanceFactory implements Factory {

    @SuppressWarnings("ClassNewInstance")
    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        try {
            return modelClass.newInstance();
        } catch (InstantiationException e) {
            throw new RuntimeException("Cannot create an instance of " + modelClass, e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Cannot create an instance of " + modelClass, e);
        }
    }
}

public interface Factory {
    @NonNull
    <T extends ViewModel> T create(@NonNull Class<T> modelClass);
}

}

소스가 좀 길지만 필요한 부분만 편집했다.
mFactory 는 AndroidViewModelFactory 이다.
Factory 라는 인터페이스로 인해 구상 클래스를 지정하지 않고도 생성할 수 있다.
mViewModelStore 에 viewModel 이 있으면 해당 viewModel 을 반환하고 아니면 Factory에 의해 생성되게 된다.

간단한 로직의 객체 생성이라면 팩토리 메소드를 사용해도 괜찮을것 같고 Android Architecture Component 처럼 구상 클래스를 지정하고 싶지 않을때는 추상 팩토리 패턴을 사용하면 좋을것 같다.

정의

주어진 상황 및 용도에 따라 어떤 객체에 책임을 덧붙이는 패턴으로, 기능 확장이 필요할 때 서브클래싱 대신 쓸 수 있는 유연한 대안이 될 수 있다.

디자인 원칙

확장에는 열려있고 변경에는 닫혀 있어야 한다는 원칙이다.(OCP - Open Close Principle)

카페에 가면 커피를 주문하고 추가적으로 밀크나 모카 휘핑을 추가 하게 되는데 이부분에서 요금이 추가 된다고 가정한다.
HouseBlend,DarkRoast,Espresso 의 기본 클래스에 밀크,모카,휘핑 등을 필요할때 추가 해서 요금을 정산 할 수 있다.
아래의 코드를 보자.

@Test
fun decoratorPatternTest() {
    val houseBlend = HouseBlend()
    println("houseBlend 가격 : ${houseBlend.cost()}원")
    val houseBlendWithWhip = Whip(houseBlend)
    println("HouseBlend + Whip 가격 : ${houseBlendWithWhip.cost()}원")
    val houseBlendWithWhipAndMilk = Milk(houseBlendWithWhip)
    println("HouseBlend + Whip + Milk 가격 : ${houseBlendWithWhipAndMilk.cost()}원")
}

abstract class Beverage {
    abstract fun cost(): Long
}

class HouseBlend : Beverage() {
    override fun cost(): Long {
        return 5000
    }
}

abstract class BeverageDecorator(protected val beverage: Beverage) : Beverage()

class Whip(beverage: Beverage) : BeverageDecorator(beverage) {
    override fun cost(): Long {
        return beverage.cost() + 500
    }
}

class Milk(beverage: Beverage) : BeverageDecorator(beverage) {
    override fun cost(): Long {
        return beverage.cost() + 1000
    }
}

Whip 과 Milk 가 Beverage 를 상속 받아서 만들게 되면 Whip과 Milk 가 Beverage 인지 Decorator 인지 바로 알기 힘들다.
BeverageDecorator 클래스를 만든건 Whip 과 Milk 가 Decorator 를 상속 받아서 장식인걸 바로 알수 있고 HouseBlend 는 Beverage 를 상속 받아 음료 종류 라는걸 바로 알 수 있다.

테스트 결과는 아래 사진과 같다.

밀크가 두번 이상,모카 두번 이상,휘핑 두번 이상이 들어 가더라도 추가적인 객체를 생성하지 않고 기능을 확장해서 사용할 수 있는 장점이 있다.
하지만 데코레이터가 많아지면 해당 객체를 감싸는 클래스들이 많아지고 디버깅하기가 힘들어 질 수 있는 단점이 있다.
그리고 실제 마지막 반환 되는 객체가 원래의 HouseBlend , DarkRoast , Espresso 가 아니라 마지막 Decorator 라는 것을 알고 있어야 한다.
이미지 참조 링크 http://nsnotification.blogspot.com/2013/01/decorator-pattern.html

정의

한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다(one-to-many) 의존성을 정의함.

디자인 원칙

서로 상호작용을 하는 객체 사이에서는 가능하면 느슨하게 결합하는 디자인을 사용해야 한다.

위의 사진은 위키백과에서 가져온 이미지이다.
Subject 에 Observer를 등록 시키고 Subeject 의 데이터가 변경 되었을때 Observer 에게 알려주는 내용이다.

이번에는 옵저버패턴 예시를 위해 TextView를 살펴 보자.
전체 소스는 많기 때문에 필요한 부분만 짤라서 요약함.
Subject 는 TextView 이고 Observer 는 TextWatcher 라고 생각하면 될것 같다.
아래를 보면 TextView(Subject)에서 Observer 를 관리 하기위해 ArrayList 로 가지고 있는걸 볼수 있다.

public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
    private ArrayList<TextWatcher> mListeners;

    public void addTextChangedListener(TextWatcher watcher) {
        if (mListeners == null) {
            mListeners = new ArrayList<TextWatcher>();
        }
        mListeners.add(watcher);
    }

    public void removeTextChangedListener(TextWatcher watcher) {
        if (mListeners != null) {
            int i = mListeners.indexOf(watcher);
            if (i >= 0) {
                mListeners.remove(i);
            }
        }
    }

    void sendOnTextChanged(CharSequence text, int start, int before, int after) {
        if (mListeners != null) {
            final ArrayList<TextWatcher> list = mListeners;
            final int count = list.size();
            for (int i = 0; i < count; i++) {
                list.get(i).onTextChanged(text, start, before, after);
            }
        }
        if (mEditor != null) mEditor.sendOnTextChanged(start, before, after);
    }

    ...
}

아래는 LogTextWatcher(옵저버) 와 PrintTextWatcher(옵저버) 이다.

class LogTextWacher : TextWatcher {
    override fun afterTextChanged(s: Editable?) {
        Log.d("kyh", "텍스트 변경후")
    }

    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
        Log.d("kyh", "텍스트 변경전")
    }

    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
        Log.d("kyh", "텍스트 변경할때")
    }
}

class PrintTextWatcher : TextWatcher {
    override fun afterTextChanged(s: Editable?) {
        System.out.println("텍스트 변경후")
    }

    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
        System.out.println("텍스트 변경전")
    }

    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
        System.out.println("텍스트 변경할때")
    }

}

아래는 LogTextWatcher(옵저버) 와 PrintTextWatcher(옵저버) 를 TextView(Subject)에 등록

fun initTextView(context: Context) {
    val textView = TextView(context)
    textView.addTextChangedListener(LogTextWacher())
    textView.addTextChangedListener(PrintTextWatcher())
}

TextView 의 text 가 변경 될 때마다 Log 와 Print 가 찍히게 된다.
하지만 유의해야하는것은 같은 옵저버를 두번 등록 하지않게 해야한다.
두번 등록 하게 될 경우 Log 와 Print 를 한번씩만 호출되길 원하는데 두번씩 호출 될 수 있다.

Strategy Pattern 에 대해 알아보자.

알고리즘군을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있도록 만든다.
스트래티지를 활용하면 알고리즘을 사용하는 클라이언트와는 독릭적으로 알고리즘을 변경할 수 있다.

1장에서는 다음과 같은 3가지 디자인 원칙이 나온다.

  1. 애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분으로부터 분리시킨다.
  2. 구현이 아닌 인터페이스에 맞춰서 프로그래밍한다.
  3. 상속보다는 구성을 활용한다.

디자인 패턴책에서는 오리의 예제를 들었는데 안드로이드 부분에서 찾아 보았다.
얼마전 AppCenter 를 사용하면서 사용된 스트래티지패턴의 예제를 보려고 한다.

class InAppUpdateListener : DistributeListener {
    override fun onReleaseAvailable(
        activity: Activity?,
        releaseDetails: ReleaseDetails
    ): Boolean {
        activity?.apply {
            val dialogBuilder = AlertDialog.Builder(activity)
            dialogBuilder.setTitle(R.string.in_app_update)
            dialogBuilder.setMessage(R.string.msg_download_latest_version)
            dialogBuilder.setPositiveButton(R.string.common_confirm) { _, _ ->
                Distribute.notifyUpdateAction(UpdateAction.UPDATE)
            }
            dialogBuilder.setCancelable(false)
            dialogBuilder.create()
                .show()
            return true
        }
        return false
    }
}

Distribute.setListener(InAppUpdateListener())

위의 코드는 업데이트할 버전이 있을때 onReleaseAvailable 가 불려서 다이얼로그를 띄우고 확인을 누르게 되면 업데이트를 진행한다.
Distribute 에는 DistributeListener 인터페이스를 구성했다.

다이얼로그를 띄우고 확인을 누르게 되면 업데이트를 진행한다. 라는 알고리즘을 캡슐화해서 사용하였고 InAppUpdateListener를 다른 DistributeListener로 교환해서 사용할 수 있다.
그렇기 때문에 이 코드도 strategy pattern 으로 볼 수 있다.