2021년 4월 26일 월요일

JetPack-Navigation-Component

JetPack-Navigation-Component

Android Navigation Component

Jetpack의 Navigation 기능제공으로 안드로이드에서도 XCode처럼 화면간 이동관계를 그래프형식으로 보면서 작성할수 있게 되었다.

Safe-Args

화면(Activity 또는 Fragment)사이에 데이터를 전달하기 위해서는 Bundle에 담아서 하곤했는데, Safe-Args를 이용하면 데이터를 전달할때 서로다른 타입으로 처리하려고 하는에러를 미리 방지할수 있고 좀더 간단하게 처리할수 있도록 도와준다.

build.gradle(Project: xxx)

의존 class path 를 추가한다.

buildscript {  
  ...
  dependencies {  
  ...
  classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5"  
  
  }  
}

build.gradle(Module: xxx)

Safe-Args는 Gradle에서 Plugin 형태로 동작한다. 이 플러그인은 나중에 safearg로 지정된 데이터타입을 기반으로 자동으로 해당 코드들을 생성해준다.

apply plugin: "androidx.navigation.safeargs.kotlin" // kotlin only
// apply plugin: "androidx.navigation.safeargs" java and kotlin ( will be generated java source code)

또는 

plugins {  
   ...
  id 'androidx.navigation.safeargs.kotlin'  // kotlin only
  // id "androidx.navigation.safeargs" java and kotlin ( will be generated java source code)
}


// navigation 을 사용하기 위해추가    
dependencies {
   ...
  def nav_version = "2.3.5"  
  
  // Kotlin  
  implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"  
  implementation "androidx.navigation:navigation-ui-ktx:$nav_version"  
}

Available data type of Safe Args

기본타입(Integer, Float, Long, Boolean) 부터 String, Parceable, Serializable, Enum, resource reference 등까지 가능하다.

Layout Setting

Navigation 은 특정 Component(주로 Fragment) 에 NavHostFragment 컨테이너를 지정하여 지정된 fragment 가 Host 역활을 하도록 하게 한다.
만일 Fragment 을 이동하는거라면 같은 Activity 내에서 호스트영역에 바꿔가며 보여주는 것이 가능하고, Activity 라면 새로운 화면으로 이동하는것이 가능하다.

  1. 먼저 layout design 화면에서 navigation 그래프를 표시하기 위해서 navigation resource를 만들자.
// your_nav_graph.xml
<!-- res 폴더에서 오른쪽버튼 - New - Android Resource File 을 선택한다음, 대화상자에서 Resource type: Navagation 을 선택하여 navigation graph 파일을 생성한다. -->   
<navigation xmlns:android="http://schemas.android.com/apk/res/android"  
  xmlns:app="http://schemas.android.com/apk/res-auto"  
  xmlns:tools="http://schemas.android.com/tools"  
  android:id="@+id/your_nav_graph"  
  app:startDestination="@id/yourFistFragment"> 
</navigation>
  1. 기본 액티비티 레이아웃파일에 navigation container 를 추가하여 아래처럼 만들자. 편의를 위해서 하단 탭버튼을 자동으로 생성해주는 bottomnavigation 도 추가했다.(탭 아이콘을 추가하면 자동으로 네비게이션의 각 화면으로 이동해준다)
// activity_main.xml
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  xmlns:app="http://schemas.android.com/apk/res-auto"  
  xmlns:tools="http://schemas.android.com/tools"  
  android:layout_width="match_parent"  
  android:layout_height="match_parent"  
  tools:context=".MainActivity"  
  >  
    
<!-- 아래의 fragment 를 직접 입력해도 되고, Palete - Containers - NavHostFragment 를 activity화면에 드래그 하면 자동으로 아래의 태그가 추가된다.( 단 , Resources 의 navigation/your_nav_graph xml 파일은 생성되어 있어야 된다. -->  
  <fragment  
  android:id="@+id/nav_host"  
  android:name="androidx.navigation.fragment.NavHostFragment"  
  android:layout_width="0dp"  
  android:layout_height="0dp"  
  
  
  app:layout_constraintBottom_toBottomOf="parent"  
  app:layout_constraintEnd_toEndOf="parent"  
  app:layout_constraintStart_toStartOf="parent"  
  app:layout_constraintTop_toTopOf="parent"  
  
  app:defaultNavHost="true"  
  app:navGraph="@navigation/your_nav_graph"  
  />  
  
  
  <com.google.android.material.bottomnavigation.BottomNavigationView  
  android:id="@+id/main_bottom_navigation"  
  android:layout_width="match_parent"  
  android:layout_height="wrap_content"  
  app:layout_constraintBottom_toBottomOf="parent"  
  app:layout_constraintEnd_toEndOf="parent"  
  app:layout_constraintStart_toStartOf="parent"  
  app:menu="@menu/menu_bottom_navigation"  
  />  
  
</androidx.constraintlayout.widget.ConstraintLayout>
  1. Navigation Graph에서 사용할 Fragmement를 여러개 만들어준다. 에를 들어 fragment_blank1.xml , fragment_blank2.xml…
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  xmlns:tools="http://schemas.android.com/tools"  
  android:layout_width="match_parent"  
  android:layout_height="match_parent"  
  tools:context=".BlankFragment"  
  >  
  
  <!-- TODO: Update blank fragment layout -->  
  <TextView  
  android:layout_width="match_parent"  
  android:layout_height="match_parent"  
  android:text="@string/hello_blank_fragment"  
  />  
  
</FrameLayout>
  1. 이제 앞서 비어있던 navigation 파일(your_nav_graph.xml) 을 열어서 위에서 만든 fragment를 요소로서 추가해보자.
    디자인 화면(중앙에디터)에서 +기호가 달린 버튼을 누르면 화면에 추가가능한 fragment, activity가 나온다.
    원하는 fragment 를 지정하고, 화면상에서 각각 연결해주면된다.
    app:startDestination="@id/blankFragment2"는 시작시 보여줄 fragment를 지정한다.
 <navigation xmlns:android="http://schemas.android.com/apk/res/android"  
  xmlns:app="http://schemas.android.com/apk/res-auto"  
  xmlns:tools="http://schemas.android.com/tools"  
  android:id="@+id/your_nav_graph"  
  app:startDestination="@id/blankFragment2">  
  <fragment  
  android:id="@+id/blankFragment2"  
  android:name="com.sugoigroup.nav11.BlankFragment"  
  android:label="fragment_blank"  
  tools:layout="@layout/fragment_blank" >  
    <action  
  android:id="@+id/action_blankFragment2_to_loginFragment"  
  app:destination="@id/loginFragment" /> 
    
	<argument  
	  android:name="param_count"  
	  app:argType="integer"  
	  android:defaultValue="0" /> 
  </fragment>  
  <fragment  
  android:id="@+id/loginFragment"  
  android:name="com.sugoigroup.nav11.ui.login.LoginFragment"  
  android:label="fragment_login"  
  tools:layout="@layout/fragment_login" >  
    <action  
  android:id="@+id/action_loginFragment_to_mainActivity22"  
  app:destination="@id/otherActivity" />  
  </fragment>  
  <navigation android:id="@+id/otherActivity"  
  app:startDestination="@id/mainActivity22">  
    <activity  
  android:id="@+id/mainActivity22"  
  android:name="com.sugoigroup.nav11.MainActivity2"  
  android:label="activity_main2"  
  tools:layout="@layout/activity_main2" />  
  </navigation>  
</navigation>
  1. activity_main에서 bottomnavigation 에 탭메뉴가 될 것들을 지정안했기 때문에 res 폴더에서 오른쪽버튼 - New - Android Resource File 선택한다음, 대화상자에서 Resource type: Menu 부분을 선택하여 res/menu/your_menu.xml 이 생성되도록 하고 탭아이템이 될 메뉴를 지정한다.
    여기서 중요한것이 android:id="@id/blankFragment2" 와 your_nav_graph.xml 에서 fragment에 지정한ID android:id="@+id/blankFragment2" 가 동일해야 하단탭버튼을 클릭할때 fragment또는 activity가 자동으로 매칭되어 화면이동이 가능해진다.
<menu xmlns:android="http://schemas.android.com/apk/res/android">  
  
  <item  
  android:id="@id/blankFragment2"    
  android:icon="@drawable/ic_android"
  android:title="@string/title_menu_first" />  
  
  <item  
  android:id="@id/loginFragment"  
  android:title="@string/title_menu_second" />  
  
  <item  
  android:id="@id/otherActivity"  
  android:title="@string/title_menu_three" />  
  
</menu>
  1. your_nav_host 를 지정한 레이아웃을 가진 액티비티(여기선 MainActivity) 에서 아래와 같이 네이베이션 사용을 선언하다.
class MainActivity : AppCompatActivity() {  
    override fun onCreate(savedInstanceState: Bundle?) {  
        super.onCreate(savedInstanceState)  
        setContentView(R.layout.activity_main)  
        
        val your_bottom_navigation = findViewById<BottomNavigationView>(R.id.your_bottom_navigation)  
  
        NavigationUI.setupWithNavController(your_bottom_navigation, findNavController(R.id.your_nav_host))  
    }  
}
  1. 과정4에서 argument 를 지정해주었다. 따라서 BlankFragment 에서 LoginFragment로 화면전이가 될때 Safe Arg 클래스가 자동을 성성되어 바로 사용할수 있다.
<navigation ...>  
/*
  <fragment  
  ...  
  android:name="com.sugoigroup.nav11.BlankFragment"  
  ... >  
    <action  
	  ...
	  app:destination="@id/loginFragment" /> 
	<argument  
	  android:name="param_count"  
	  app:argType="integer"  
	  android:defaultValue="0" /> 
  </fragment>  
  <fragment  
  ...  
  android:name="com.sugoigroup.nav11.ui.login.LoginFragment"  
  ... >  
  </fragment> 
*/

// LoginFragment.kt
  
class LoginFragment : Fragment() {  
  
    val myFromBlankParam by navArgs<BlankFragmentArgs>()
  
	override fun onViewCreated(view: View, savedInstanceState: Bundle?) {  
	    super.onViewCreated(view, savedInstanceState)  
	    val myFutureAge = myFromBlankParam.paramCount * 2
    }
}

수동으로 화면이동

버튼을 눌렀을때등 화면이동을 수동으로 할수 있다.
Navigation.findNavController(view).navigate(R.id.액션id또는목적지id);

 // 액션id또는목적지id 는 loginFragment 또는 action_loginFragment_to_mainActivity22 가 될수 있다.
 // action id 를 지정하는게 애니메이션효과등을 출수 있기 때문에 더 좋다.
  <fragment  
  android:id="@+id/loginFragment"  
  android:name="com.sugoigroup.nav11.ui.login.LoginFragment"  
  android:label="fragment_login"  
  tools:layout="@layout/fragment_login" >  
    <action  
  android:id="@+id/action_loginFragment_to_mainActivity22"  
  app:destination="@id/otherActivity" />  
  </fragment> 

기타 옵션

Pop Behavior - popUpToIncursive 옵션을 true 로 하면 현재 화면에서 다음화면으로 넘어갈경우 스택이 쌓지않고 이동하도록 한다.
Pop Behavior - popUpTo 는 쌓여있는 스택을 모두 popup 시키고 마지막 화면만 남아있도록 한다.
Deep link - 외부에서 URI를 통해서 현재 화면에 바로 들어 접속할수 있도록 딥링크주소를 만들수 있다.

2021년 4월 20일 화요일

Kotlin_designpattern_ebook_kth

Kotlin_designpattern_ebook_kth

Kotlin 디자인 패턴

생성자 패턴 ( Creational Patterns )

생성자 패턴은 Object, 즉 객체를 만들때 사용하는 패턴들 이다. java 에서는 객체를 생성할때 무조건 new 를 사용하여 직접 객체를 지정하여 생성하는데, 이렇게 하면 나중에 유지보수 할때 해당객체에 변경사항이 생겼거나 추가사항이 생겼을때 일일이 찾아가며 수정해야한다.
생성자 패턴은 생성은 이러한 변화에 대해 조금이라도 덜 소스코드를 수정하면서 원하는 결과가 될수 있도록 해주는 패턴이다.

Singleton

객체가 프로그램 전반에 걸쳐 사용되거나, 복잡한 로직을 포함하지 않는 간단한 유틸리티기능의 객체라면 싱글톤 패턴을 이용하여 메모리상에서 하나만 생성해두어 그것을 활용할수 있도록 하는게 좋다.
예를 들어 , 프로그램내에서 데이터베이스 접속은 쿼리를 할때마다 접속과정을 다시 거칠필요없이 프로그램초기에 접속을 하고, 해당 객체를 이용하여 접속풀을 통해서 메모리도 절약하고 접속도 빠르게 할수 있다.
또한 환율계산 로직의 유틸리티 클래스같은 경우도 싱글톤으로 생성하여, 프로그램어디서나 간편하게 환율계산값을 알아낼수 있도록 할수있다.

Kotlin에서는 object 클래스를 이용하여 싱글톤 패턴을 너무나도 쉽게 구현할수 있다.

object MySingelton

가장 심플한 싱글톤 형태이다. 로직을 넣어보자.

object MySingelton {
    init {
        //init 안에 디비접속정보등을 초기화 하는 로직을 넣는다.
        println("init은 MySingletone이 처음 호출될때만 실행됨")
    }
    fun repositry(isDev: Boolean):MyRepositry {
        return if (isDev) MyLocalDb else MyRemoteDb
    }
}

사용은

MySingelton.repositry(DEV_TRUE) // init 내용 출력 
MySingelton.repositry(DEV_TRUE)   
MySingelton.repositry(DEV_TRUE)

Factory

팩토리 메서드 패턴은 말그대로 공장시스템을 만드는 패턴이다. 공장에서는 주문서에 따라서 객체를 생산해서 납품하면 되는데 팩토리 패턴이 하는일이 딱 그거다.
단 하나의 공장들은 생산하는 물품은 비슷한 품목이어야하고 팩토리 패턴사용자도 객체의 공통된 인터페이스에 정의되어 이쓴ㄴ 사용법을 통해 사용해야한다.
자동차공장에서 비슷한 라인에서 생산하지만, 최종사용자가 차의 문을 열고, 시동걸고, 운전하는건 같아야 하는 원리이다.
따라서 전혀 다른 품목들은 각각 팩토리 메서드를 생성하면된다.

자동차 공장을 예로 만들어 보자.

class FactoryMethod {  
    fun makeCar(maker: String) : Car {  
        return when(maker) {  
            "bmw" -> Bmw()  
            "audi" -> Audi()  
            else -> throw RuntimeException("그런차 없어!")  
        }  
    }  
}  
  
class Bmw : Car {  
    override val name ="bmw"  
}  
  
class Audi : Car {  
    override val name ="audi"  
}  
  
interface Car {  
    val name: String  
}


사용은

val myBmw = FactoryMethod().makeCar("bmw")

makeCar 함수에 자동차의 제원등을 interface 인자로 넘겨주도록 한다면 자동차 생성과 함께 마력, 편의사항등 제원을 포함한 자동차를 생성할수 있도록 할수 있다.
팩토리 패턴을 사용하면 객체의 생성에 직접적으로 관여하지않고, 원하는 객체의 이름이나 형식만 넣어주면 자동으로 해당객체를 생성하여 주기 때문에 확장에 용이하다. ( 새 모델의 자동차를 만들어도 추가라인만 세우면 되는것과 같다)

  • Static Factory 라는것도 있는데 기존의 Factory 클래스에서 생성자를 private 시켜 직접적인 팩토리 객체를 생성할수 없도록 하고, 정적타입의 팩토리 객체를 리턴하도록 함으로서 캐싱과 좀더 명시적인 객체생성을 함수 있도록 해준다.

FactoryMethod만 Static Factory로 변경해보자.

  
class FactoryMethod private constructor(){  
    companion object {  
        fun create(): FactoryMethod {  
            return FactoryMethod()  
        }  
    }  
    fun makeCar(maker: String) : Car {  
        return when(maker) {  
            "bmw" -> Bmw()  
            "audi" -> Audi()  
            else -> throw RuntimeException("그런차 없어!")  
        }  
    }  
}
  
  
val factory = FactoryMethod.create()  
var bmw = factory.makeCar("bmw")  
var audi = factory.makeCar("audi")

Abstract Factory

추상 팩토리 메서드 패턴은 기존의 팩토리 메서드 에서 좀더 진화된 개념이라고 생각하면된다.
Factory 에서 설명한 자동차공장이 공장을 확장하게 되어 자회사를 별도로 만들어 각기다른 공장에서 자동차, 오토바이, 수륙양용, 탱크. 비행기까지 만들게 되었다. Abstract Factory 회사는 주문서에 적힌 (추상화된) 품목들 즉 바퀴, 날개, 물에뜨는기능 등을 하위 공장에 적절하게 생산하도록 하고 최종결과물은 부품조립공장에서 만들어 납품한다.

하위공장은 Factory 메서드패턴으로 만든다.

class AbstractFactoryMethod (val vehicleFactory: VehicleFactory){  
    fun spec() : String {  
        val engine = vehicleFactory.getEngines();  
        val wheels = vehicleFactory.getWheels();  
        val wings = vehicleFactory.getFly();  
        return "이번 상품은 파워:${engine.getPower()}, 바퀴:${wheels.getCount()}, 날개:${wings.getWing()}"  
  }  
}  
  
// 추상팩토리 공장 인터페이스  
interface VehicleFactory {  
    fun getEngines(): EngineFactory  
  fun getWheels(): WheelFactory  
  fun getFly(): WingFactory  
}  
  
// 자동차 주문서  
class CarProduct : VehicleFactory {  
    override fun getEngines() = CarPower()  
    override fun getWheels() = CarWheel()  
    override fun getFly() = CarWing()  
  
}  
  
// 비행기 주문서  
class AirplaneProduct : VehicleFactory {  
    override fun getEngines() = AirPlanePower()  
    override fun getWheels() = AirPlaneWheel()  
    override fun getFly() = AirPlaneWing()  
  
}  
  
// 엔진사양 추상화  
interface EngineFactory {  
    fun getPower(): String  
}  
//자동차 엔진 구체화  
class CarPower: EngineFactory {  
    override fun getPower() = "200 MP"  
}  
//비행기 엔진 구체화  
class AirPlanePower: EngineFactory {  
    override fun getPower() = "3000 MP"  
}  
  
// 바퀴 사양 추상화  
interface WheelFactory {  
    fun getCount(): String  
}  
//자동차 바퀴 구체화  
class CarWheel: WheelFactory {  
    override fun getCount() = "4WP"  
}  
//비행기 바퀴 구체화  
class AirPlaneWheel: WheelFactory {  
    override fun getCount() = "3WP"  
}  
  
// 날개 사양 추상화  
interface WingFactory {  
    fun getWing(): String  
}  
//자동차 날개 구체화  
class CarWing: WingFactory {  
    override fun getWing() = "날개없음"  
}  
//비행기 날개 구체화  
class AirPlaneWing: WingFactory {  
    override fun getWing() = "날개두짝"  
}

추상팩토리로 만들면 관련 구현-구체화 클래스가 다소 복잡해지게된다.

Generic을 이용하면 생성하고자 하는 객체타입을 통해서 생성을 쉽게 할수 있는 방법도 있다.
참고:https://medium.com/@dbottillo/patterns-in-kotlin-abstract-factory-a0ff99a0d177

Builder

빌더패턴은 객체생성시 다양한 부가 옵션을 추가하고자 할때 사용하는 패턴이다.
커피머신에서 커피를 뽑을때 추가로 설탕, 크림등 다양한 옵션을 생성시에 추가할수 있다.
전통적인 Builder 패턴은 클래스내의 정적서브 클래스를 만들어 객체를 리턴하는 방식으로 작성한다.
Kotlin에서는 Data class 를 이용하여 간단히 생성할수 있다.

data class BuilderPattern(val coffee:String, val sugar:Int = 0, val cream:Int = 0)

  
  
val americano = BuilderPattern("java coffee")  
println("coffee type : ${americano.coffee}")  
  
val creamCoffee = BuilderPattern("cream coffee", cream = 3)  
println("coffee type : ${creamCoffee.coffee}, suger : ${creamCoffee.sugar}, cream: ${creamCoffee.cream}")  
  
val mixCoffee = BuilderPattern("mix coffee", sugar = 2, cream = 2)  
println("coffee type : ${mixCoffee.coffee}, suger : ${mixCoffee.sugar}, cream: ${mixCoffee.cream}")

/*
    coffee type : java coffee
    coffee type : cream coffee, suger : 0, cream: 3
    coffee type : mix coffee, suger : 2, cream: 2
*/

정말 간단하다.
java 라면

Class BuilderPattern {
  String coffetype = ""; 
  Int cream = 0; 
  Int sugar = 0; 
   static class BuilderCoffee{
      String coffetype = ""; 
      Int cream = 0; 
      Int sugar = 0; 
     BuilderCoffee(String coffetype) {
        this.coffetype = coffetype;
     }
       BuilderCoffee setCoffeeType(String coffeType) { this.coffetype=coffetype; return this; }
       BuilderCoffee setCream(Int cream) { this.cream=cream; return this; }
       BuilderCoffee setSugar(Int sugar) {  this.sugar=sugar; return this;  }
       BuilderPattern build() {
         BuilderPattern coffeB = new BuilderPattern();   
         coffeB.coffetype = this.coffetype;
         coffeB.cream = this.cream;
         coffeB.sugar = this.sugar;
         return coffeB;
    }
   }
}

BuilderPattern coffeB = new BuilderPattern.BuilderCoffee("americano").setSugar(2).build()

이처럼 길어진다.

구조체 패턴 ( Structural Patterns )

생성자 패턴은 Object, 즉 객체를 만들때 사용하는 패턴들 이다. java 에서는 객체를 생성할때 무조건 new 를 사용하여 직접 객체를 지정하여 생성하는데, 이렇게 하면 나중에 유지보수 할때 해당객체에 변경사항이 생겼거나 추가사항이 생겼을때 일일이 찾아가며 수정해야한다.
생성자 패턴은 생성은 이러한 변화에 대해 조금이라도 덜 소스코드를 수정하면서 원하는 결과가 될수 있도록 해주는 패턴이다.

Decorator pattern

데코레이터패턴은 객체생성시 부가적인 꾸밈(?) 을 쉽게 하기 위해서 확장가능하도록 설계하는 패턴이다.
Builder 패턴의 경우 품목이 추가될때마다 Builder 클래스를 수정해주어야 하기 때문에 확장이 용이하지않는데, 데코레이터패턴은 기본이 되는 객체에 확장하는 패턴이라 객체에 옵션을 추가하기가 비교적 쉽다.

class DecoratorPattern : Hambuger{  
    override fun getBread() {  
        println("빵 두개 준비!")  
    }  
  
}  
  
interface Hambuger {  
    fun getBread()  
}  
  
open class BuggerDecorator(protected var hambuger: Hambuger) : Hambuger {  
    override fun getBread() {  
        this.hambuger.getBread()  
    }  
}  
  
open class MeetBuggerDecorator(bugger: Hambuger) : BuggerDecorator(bugger) {  
    override fun getBread() {  
        super.getBread();  
        this.addMeet();  
    }  
    private fun addMeet() {  
        println("고기패티 추가")  
    }  
}  
  
open class EggBuggerDecorator(bugger: Hambuger) : MeetBuggerDecorator(bugger) {  
    override fun getBread() {  
        super.getBread();  
        this.addEgg();  
    }  
    private fun addEgg() {  
        println("계란후라이 추가")  
    }  
}

val eggBuger = EggBuggerDecorator(DecoratorPattern())  
println("달걀추가된 버거입니다. : ${eggBuger.getBread()}")  
  
val meetBuger = MeetBuggerDecorator(DecoratorPattern())  
println("미트 버거입니다. : ${meetBuger.getBread()}")
/*
  
  

빵 두개 준비!
고기패티 추가
계란후라이 추가
달걀추가된 버거입니다. 

빵 두개 준비!
고기패티 추가
미트 버거입니다. 
*/

Adapter Pattern

아답타 패턴은 서로다른 두개의 객체를 공통된 인터페이스로 공용화 시키는 패턴이다. 실생활에서 보이는 usb-typec 아답터, 멀티포트 usb충전 아답터 , 시중에 파는 리모콘 등과 같은 역활을 한다고 생각하면된다.
아답터는 주로 어느 한쪽이 기존의 사양이고, 나중에 추가된 기능이 해당 사양에 맞게 연결점(인터페이스)를 통해 연결할때 사용한다.
기존의 객체가 요구하는 최소한의 인터페이스만 같으면 어떤 다른 객체도 기존객체에 추가하고 적용할수 있다.

class AdaptorPattern(private val usbConnect: ToTypeC) {  
   fun runConnect() {  
       usbConnect.connect()  
   }  
}  
  
interface ToTypeC {  
   fun connect()  
}  
  
  
class Usb2 : ToTypeC{  
    override fun connect() {  
        println("usb2 연결됬어유")  
    }  
  
}  
  
class Usb1 : ToTypeC{  
    override fun connect() {  
        println("usb1 연결됬어유")  
    }  
  
}  
  
  
class Thunderbird : ToTypeC{  
    override fun connect() {  
        println("썬더버드 연결됬어유")  
    }  
  
}

  
var usbAdaptor = AdaptorPattern(Usb1())  
usbAdaptor.runConnect()  
  
usbAdaptor = AdaptorPattern(Usb2())  
usbAdaptor.runConnect()  
  
usbAdaptor = AdaptorPattern(Thunderbird())  
usbAdaptor.runConnect()

/*
usb1 연결됬어유
usb2 연결됬어유
썬더버드 연결됬어유
*/

Bridge Pattern

Bridge 패턴은 상속관계있는 하위객체에 대해 브릿지 인터페이스를 제공하여 하위객체의 변화를 브릿지인터페이스를 통해서 하도록 하는 패턴이다.

abstract class Icon(var color:Color) {  
    abstract fun applyColor()  
}  
  
class Triangle(val c:Color) : Icon(c) {  
    override fun applyColor() {  
        color.applyColor()  
    }  
  
}  
  
class Rectangle(val c:Color) : Icon(c) {  
    override fun applyColor() {  
        color.applyColor()  
    }  
  
}  
  
interface Color {  
    fun applyColor();  
}  
  
class GreenColor : Color {  
    override fun applyColor() {  
        println("푸른색")  
    }  
}


  
val iconRed = Triangle(RedColor())  
iconRed.applyColor()  
  
  
val iconGreen = Rectangle(RedColor())  
iconGreen.color = GreenColor()  
iconGreen.applyColor()

Composite Pattern

콤포지트패턴을 가장 쉽게 이해할수 있는 것은 OS내의 folter-file구조이다. 폴더안에는 폴더와 파일이 있고 다시 폴더안에는 폴더와 파일이 구성되어있다.

class CompositePattern(override val name: String) : FileFolderComponent {  
    val allFiles:ArrayList<FileFolderComponent> = ArrayList()  
    init {  
        allFiles.add(Folder(name))  
    }  
  
    override fun display()  = allFiles.forEach(FileFolderComponent::display)  
  
    fun add(component: FileFolderComponent) {  
        allFiles.add(component)  
    }  
    fun remove(component: FileFolderComponent) {  
        allFiles.remove(component)  
    }  
  
}  
  
interface FileFolderComponent {  
    val name: String  
  fun display()  
}  
  
class Folder(override val name: String) : FileFolderComponent {  
    val contents:ArrayList<FileFolderComponent> = ArrayList()  
    fun addComponent(folder: FileFolderComponent) {  
        this.contents.add(folder)  
    }  
    override fun display() {  
        println(name)  
        this.contents.forEach(FileFolderComponent::display)  
    }  
  
}  
  
  
class File(override val name: String)  : FileFolderComponent {  
    override fun display() {  
        println(name)  
    }  
  
}
val folders = CompositePattern("/")  
folders.add(File("first.txt"))  
folders.add(File("second.txt"))  
  
val subfolders = Folder("/mysecret")  
subfolders.addComponent(File("secret1.txt"))  
subfolders.addComponent(File("secret1.txt"))  
  
folders.add(subfolders)  
folders.display()

/*
    /
    first.txt
    second.txt
    /mysecret
    secret1.txt
    secret1.txt
    */

Flyweight Pattern

같은 객체를 프로그램에서 자주 생성하여야 하거나, 가능한 무거운 처리로직등을 캐싱을 해두어 빠르게 사용하도록 하는 패턴이다.

class FlyweightPattern {  
    fun getOrNewEnermy(name:String):Enermy {  
        return EnermyFactory.getEnermy(name)  
    }  
    fun printAll() = EnermyFactory.allPrint()  
}  
  
class Enermy(val name: String) {  
    fun attack() {  
        println("$name 공격!")  
    }  
}  
  
class EnermyFactory {  
    companion object {  
        val enermies = mutableMapOf<String, Enermy>()  
        fun getEnermy(name: String) = enermies.computeIfAbsent(name) {  
  Enermy(name)  
        }  
  fun allPrint() {  
            enermies.forEach({  
  println(it.key)  
            })  
        }  
    }  
}

  
val flyweightPattern = FlyweightPattern()  
val enermy = flyweightPattern.getOrNewEnermy("고블린")  
enermy.attack()  
  
val enermy2 = flyweightPattern.getOrNewEnermy("악마")  
enermy2.attack()  
  
val enermyReuse = flyweightPattern.getOrNewEnermy("고블린")  
enermyReuse.attack()  
  
FlyweightPattern().printAll()

Facade Pattern

복잡한 로직을 하나의 클래스 또는 함수형식으로 만들어, 사용하는 쪽에서는 단순하하게 사용할수 있도록 하는 패턴이다. 겉으로는 즉, 간단해 보이도록 하는 패턴이다.

개발중에 자주 사용하는 패턴으로서 로직을 클래스로 만들어 사용하는 것이 Facade 패턴이다.

class FacadePattern {  
   fun complexiLogic() {  
       val fromReturn = VeryComplexLogic().idontknow("blablabla" == "albalbalb")  
       println("result is $fromReturn")  
   }  
}  
  
class VeryComplexLogic {  
    fun idontknow(isEqual:Boolean): Boolean {  
        return isEqual  
    }  
}
  
FacadePattern().complexiLogic()

Proxy Pattern

인터넷 사용시 프록시서버를 통하면 해당사이트에 직접연결하지않고 프록시서버를 통해서 연결된다. 프록시패턴도 사용자의 요청을 다른 곳으로 전달하고 처리된 결과를 반환받아 사용에게 전달하는 패턴이다
라이브러리를 참조할때 프록시패턴을 사용하면 라이브러리의 교체나 수정시 포록시 클래스만 수정하게 되므로 기존 서비스에는 영향이 적게 할수 있다.

class ProxyPattern(val proxy:IProxyService)  : IProxyService {  
      
    override fun connectSite(): String {  
        return proxy.connectSite()      
    }  
}  
  
interface IProxyService {  
    fun connectSite():String  
}  
  
class ProxyService : IProxyService {  
    override fun connectSite():String {  
        return "connected"  
  }  
}
  
val proxyPattern = ProxyPattern(ProxyService())  
proxyPattern.connectSite()

행동 패턴 ( Behavior Patterns )

행동패턴은 어떤처리에 대해 어떻게 행동할건지를 판단하여 적절하게 처리객체에 분배하는 패턴이다.
상태,전략,명령,중재 등과 같이 행동의 변화에 대해 적절한 판단을 하여 객체에 처리를 전달한다.

Strategy pattern

전략패턴은 처리로직을 전략에 맞게 변경하는패턴이다.
게임에서 돌진할때 군인으로 할까 전차로 할까를 전략적으로 선택해야될때 사용된다.

  
class StrategyPattern(val basePrice:Int) {  
    fun getPrice(whos: Discount):Int {  
        return basePrice - whos.discount()  
    }  
}  
  
interface Discount {  
    fun discount() : Int  
}  
  
class DiscChild : Discount{  
    override fun discount(): Int {  
        return - 1000;  
    }  
}  
  
class DiscAdult : Discount{  
    override fun discount(): Int {  
        return 0;  
    }  
}
  
val StrategyPattern = StrategyPattern(1500)  
println("어린이는 ${StrategyPattern.getPrice(DiscChild())}")  
println("어른은 ${StrategyPattern.getPrice(DiscAdult())}")

Iterator pattern

반복자패턴은 객체의 형태와 상관없이 객체의 요소들을 반복해서 처리해야 할필요가 있을때 사용한다.

State pattern

상태패턴은 객체의 상태를 관리하는 패턴이다.

class StatePattern {  
    var nowState:States = Born()  
    fun nextLife() {  
        nowState = nowState.nextState()  
    }  
    fun currentLife() = nowState.getState()  
}  
  
interface States {  
    fun getState()  
    fun nextState() : States  
}  
  
class Born : States {  
    override fun getState() = println("태어남")  
    override fun nextState(): States {  
        return Infant()  
    }  
  
}  
  
class Infant : States {  
    override fun getState() = println("아기때")  
    override fun nextState(): States {  
        return Adult()  
    }  
  
}  
  
class Adult : States {  
    override fun getState() = println("성인")  
    override fun nextState(): States {  
        return Die()  
    }  
  
}  
class Die : States {  
    override fun getState() = println("죽음")  
    override fun nextState(): States {  
        return Born()  
    }  
  
}

  
var statePattern = StatePattern()  
statePattern.currentLife()  
  
statePattern.nextLife()  
statePattern.nextLife()  
statePattern.currentLife()

Command pattern

커맨드 패턴은 만능리모콘 또는 일반프로그램의 상단의 풀다운 메뉴에 비유해서 이해하면 된다.

class CommandPattern {  
    val allParts = mutableListOf<ComputerParts>()  
    fun start(parts: ComputerParts) {  
        allParts.add(parts)  
        OnCommand(parts).execute()
    }  
  
    fun stop(parts: ComputerParts) { 
        OffCommand(parts).execute() 
    } 
  
}  
  
interface Command {  
    fun execute()  
}  
  
class OnCommand(private val pcEtc: ComputerParts) : Command {  
    override fun execute() {  
        pcEtc.on()  
    }  
}  

class OffCommand(private val pcEtc: ComputerParts) : Command {  
    override fun execute() {  
        pcEtc.off()  
    }  
}  

interface ComputerParts {  
    fun on()  
    fun off()  
}  
  
class Printer : ComputerParts {  
    override fun on() = println("print 시작")  
    override fun off()  =  println("print 끔")  
}  
  
class Monitor : ComputerParts {  
    override fun on()  =  println("monitor 전원켬")  
    override fun off()  =  println("monitor 끔")  
}  
  
class LedLight : ComputerParts {  
    override fun on()  =   println("led 켬")  
    override fun off()  =  println("led 끔")  
}
  
val commandPattern = CommandPattern();  
val print = Printer()
commandPattern.start(print)  
commandPattern.start(Monitor())  
commandPattern.start(LedLight())  
commandPattern.stop(print)  

Chain of responsibility pattern

책임연쇄패턴은 어떤 요청에 대해 처리를 할수 있는 개체를 만날때까지 처리를 계속 위임하는패턴이다.
온라인송금을 할때 처리 가능한 서버를 찾아서 처리를 하는것과 같다.

class ChainOfResponsibilityPattern(val wantPay:Int) {  
    val banks = mutableListOf<PayMoney>()  
  
    init {  
        banks.add(PayMoney(wantPay, BankArea1()))  
        banks.add(PayMoney(wantPay, BankArea2()))  
        banks.add(PayMoney(wantPay, BankArea3()))  
    }  
  
    fun payMe() {  
        banks.forEach {  
  if (it.payProcess()) return  
  }  
  }  
}  
  
class PayMoney(val billMoney:Int = 0, var next:Process? = null) {  
    fun payProcess():Boolean {  
        if(next?.canBill(billMoney) == true) {  
            next?.runBill(billMoney)  
            return true  
  }  
        return false  
  }  
}  
  
interface Process{  
    fun canBill(amount: Int): Boolean  
  fun runBill(amount: Int)  
}  
  
class BankArea1: Process {  
    var remain = 1000  
  override fun canBill(amount: Int): Boolean {  
        remain = remain - amount  
        return remain > 0  
  }  
    override fun runBill(amount: Int) {  
        remain = remain - amount  
        println("1번은행")  
    }  
}  
  
class BankArea2: Process {  
    var remain = 3000  
  override fun canBill(amount: Int): Boolean {  
        remain = remain - amount  
        return remain > 0  
  }  
    override fun runBill(amount: Int) {  
        remain = remain - amount  
        println("2번은행")  
    }  
  
}  
  
class BankArea3: Process {  
    var remain = 5000  
  override fun canBill(amount: Int): Boolean {  
        remain = remain - amount  
        return remain > 0  
  }  
    override fun runBill(amount: Int) {  
        remain = remain - amount  
        println("3번은행")  
    }  
  
}
  
  
val chainOfResponsibilityPattern = ChainOfResponsibilityPattern(2600)  
chainOfResponsibilityPattern.payMe()

Mediator pattern

중재자 패턴은 서로다른 클래스 또는 컴포넌트간에 직접통신하지 않고 중재자를 통해서 통신할도록 하는 패턴이다.
가장 적합한 예로 활주로가 하나있는 비행장을 생각하면된다. 관제소에서 비행기가 뜨고 내릴 타임을 중재해주어 사고가 안나도록 해야한다. 중재자패턴을 사용안한다면 각 비행기마다 서로 통신을 하다가 결국은 사고가 나게된다.

class MediatorPattern() {  
   companion object {  
       fun requestFromAirplane(airplane: Airplane) {  
           airplane.requestLanding()  
       }  
       fun confirmLanded() {  
           AirplaneMediator.isLanding = false  
 }  
   }  
 
}  
 
class Airplane(val name: String, var mediator: Mediator) : Commander {  
   var state = FLY.WATING  
 override fun requestLanding() {  
       state = mediator.canLand()  
   }  
}  
 
interface Commander {  
   fun requestLanding()  
}  
 
interface Mediator {  
   fun canLand(): FLY  
 fun endLand()  
}  
 
class AirplaneMediator : Mediator {  
   companion object {  
       var isLanding:Boolean = false  
 }  
   override fun canLand(): FLY {  
       if(!isLanding) {  
           isLanding = true  
return FLY.LANDING  
 }  
       return FLY.FLYING  
 }  
 
   override fun endLand() {  
       isLanding = false  
 }  
 
}  
 
enum class FLY {  
   FLYING,  
   WATING,  
   LANDING,  
}
 
 
val mediator1 = AirplaneMediator()  
 
val airplane717 = Airplane("b717", mediator1)  
airplane717.state = FLY.FLYING  
 
val airplane718 = Airplane("b718", mediator1)  
airplane718.state = FLY.FLYING  
 
val airplane719 = Airplane("b719", mediator1)  
airplane719.state = FLY.FLYING  
 
MediatorPattern.requestFromAirplane(airplane717)  
MediatorPattern.requestFromAirplane(airplane718)  
MediatorPattern.requestFromAirplane(airplane719)  
 
println("b717 ${ airplane717.state}")  
println("b718 ${ airplane718.state}")  
println("b719 ${ airplane719.state}")  
 
MediatorPattern.confirmLanded()  
 
MediatorPattern.requestFromAirplane(airplane719)  
MediatorPattern.requestFromAirplane(airplane718)  
 
println("b719 ${ airplane719.state}")  
println("b718 ${ airplane718.state}")

Memento pattern

Memento 패턴은 특정 상황에 대해 기록을 하고 나중에 꺼내서 다시 복구할수 있도록 하는 패턴이다. 마치 특정 시기에 기념비(?)를 세워두는 것과 같다고 해서 memento 라는 단어를 사용한것 같다.
기념비를 세우기 위해서는, 해당 기념비가 왜세워졌는지에 대한 기록 Caretaker 가 있ㅇ어야 하고, 기념비에 새길 설명 Originator, 그리고 실제 단순 돌덩이리(순수객체) 기념비 Memento , 이렇게 3개의 조합으로 모멘토 패턴은 완성된다.
프로그램에서 Undo기능을 구현할때 사용된다.

  
class Originator {  
   private var state:String = ""  
 fun setState(state: String?) {  
       this.state = state!!  
   }  
 
   fun getState(): String? {  
       return state  
 }  
 
   fun saveStateToMemento(): Memento? {  
       return Memento(state)  
   }  
 
   fun getStateFromMemento(Memento: Memento) {  
       state = Memento.getState()!!  
   }  
}  
 
class Memento(private var stat: String?) {  
   fun getState():String? {  
       return stat  
 }  
}  
 
class Caretaker {  
 
   private val mementoList: MutableList<Memento> = ArrayList()  
 
   fun add(state: Memento) {  
       mementoList.add(state)  
   }  
 
   fun get(index: Int): Memento? {  
       return mementoList[index]  
   }  
 
   fun countHistory() : Int {  
       return mementoList.size  
 }  
 
}
// 객체 생성  
val originator = Originator() // 기념비에 새길 내용  
val careTaker = Caretaker() // 기념비 관리자  
 
// 기념비 #1 을 세우고 기념비 관리에 추가  
originator.let {  
it.setState("기념비 #1");  
   it.saveStateToMemento()?.let { mt ->  
 careTaker.add(mt)  
   };  
}  
// 기념비 #2 을 세우고 기념비 관리에 추가  
originator.let {  
it.setState("기념비 #2");  
   it.saveStateToMemento()?.let { mt ->  
 careTaker.add(mt)  
   };  
}  
 
var historyReverse:Int = careTaker.countHistory()  
careTaker.get(--historyReverse)?.let { originator.getStateFromMemento(it) };  
println("이전에 세워진 기념비: " + originator.getState());  
 
careTaker.get(--historyReverse)?.let { originator.getStateFromMemento(it) };  
println("이전에 세워진 기념비: " + originator.getState());

Visitor pattern

방문자패턴은 기존의 객체에 방문자로서 참여한 객체가 그곳에서 자신의 행동을 할수 있는 패턴이다.
다수의 보안카메라가 작동하고 있고, 감시자 또는 외부관리자는 각 카메라에 접속해서 각자의 체크사항들을 체크하는 것과 같은 패턴이다.

class Room1 : Camera {  
   override fun display() {  
      println("1번방 카메라로 방보기")  
   }  
 
 
   override fun cleanCheck() {  
       println("1번방 방청소확인")  
   }  
 
   override fun visit(visitor: Visitor) {  
       visitor.visit(this)  
   }  
 
}  
class Room2 : Camera {  
   override fun display() {  
       println("2번방 카메라로 방보기")  
   }  
 
   override fun cleanCheck() {  
       println("2번방 카메라로 방보기")  
   }  
 
   override fun visit(visitor: Visitor) {  
       visitor.visit(this)  
   }  
 
}  
 
interface Camera {  
   fun display()  
   fun cleanCheck()  
 
   fun visit(visitor:Visitor)  
}  
 
interface Visitor {  
   fun visit(room1: Room1)  
   fun visit(room2: Room2)  
}  
 
class Visitors(val visitorName:String) : Visitor {  
   override fun visit(room1: Room1) {  
       println("$visitorName 방확인 : ${room1.display()}")  
       println("$visitorName 청소확인 : ${room1.cleanCheck()}")  
   }  
 
   override fun visit(room2: Room2) {  
       println("$visitorName 방확인 : ${room2.cleanCheck()}")  
   }  
 
}
 
val room1 = Room1()  
val room2 = Room2()  
 
room1.visit(Visitors("책임운영자"))  
room2.visit(Visitors("경비운영자"))

Template Pattern

객체들간의 공통되고 반복되는 루틴을 템플릿화 시켜서 사용하기 간편하게 만드는 패턴이다.

  
class TemplatePattern {  
    fun instruceCompany() {  
        val hrTeam = HrTeam()  
        println("인사부의 하루: ")  
        hrTeam.ourDaily()  
  
        val devTeam = DevTram()  
        println("개발부의 하루: ")  
        devTeam.ourDaily()  
    }  
  
}  
abstract class DailyRoutine {  
    private fun commute() {  
        print("hurry!hurry!hurry!")  
    }  
    abstract fun toWork()  
  
    private fun eatLunch() {  
        print("delicious!!!")  
    }  
  
    private fun goHome() {  
        print("home!home!home!")  
    }  
  
    fun ourDaily() {  
        print("${commute()} -> ${toWork()} -> ${eatLunch()} -> ${goHome()}")  
    }  
  
}  
  
class HrTeam : DailyRoutine() {  
    override fun toWork() {  
        print("seek!seek!seek!")  
    }  
  
}  
  
class DevTram : DailyRoutine() {  
    override fun toWork() {  
        print("coding!coding!coding!")  
    }  
  
} 

  
TemplatePattern().instruceCompany()

Observer Pattern

최근 유행이 되고 있는 옵서버 패턴은 발행자/구독자 로 구성되어 있고 발행자가 이벤트를 빌행하면 구독자가 내용을 받아서 적절한 처리를 하는 패턴이다.

class ObserverPattern {  
    val newsPublisher = NewsPublisher()  
    init {  
        newsPublisher.add(PeopleSubscriber(false))  
        newsPublisher.add(PeopleSubscriber(true))  
        newsPublisher.add(PeopleSubscriber(false))  
    }  
    fun publish(){  
        newsPublisher.update("고급뉴우우우스", onlyPayed = true)  
        newsPublisher.update("일반뉴우우우스", onlyPayed = false)  
    }  
  
}  
  
// 발행 인터페이스를 정의한다.  
interface Publisher {  
    fun update(SubscribeStatus: String, onlyPayed:Boolean): Boolean  
  fun add(subscriber: Subscriber): Boolean  
  fun delete(subscriber: Subscriber): Boolean  
  
}  
  
class NewsPublisher : Publisher {  
  
    private val observerList = mutableListOf<Subscriber>()  
  
    override fun update(newsText: String, onlyPayed:Boolean): Boolean {  
        observerList.forEach { it.onUpdate(newsText, onlyPayed) }  
  return true  
  }  
  
    // 구독자를 추가한다  
  override fun add(subscriber: Subscriber) = observerList.add(subscriber)  
  
    // 구독자를 제거한다.  
  override fun delete(subscriber: Subscriber) = observerList.remove(subscriber)  
}  
  
  
  
interface Subscriber {  
    fun onUpdate(subscribeStatus: String, onlyPayed:Boolean): Boolean  
}  
  
class PeopleSubscriber(private val isPayedUser:Boolean = false) : Subscriber {  
  
    override fun onUpdate(newsText: String, onlyPayed:Boolean): Boolean {  
        if (onlyPayed == true && onlyPayed == isPayedUser) {  
            println("프리미엄 뉴스: $newsText")  
            return true  
  }  
        if (onlyPayed != true) println("새로운 뉴스: $newsText")  
        return false  
  }  
}  
ObserverPattern().publish()

Github : https://github.com/sugoigroup/kotlin_design_pattern_example