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

2021년 4월 12일 월요일

Android Kotlin Lint 툴적용 & commit hook

팀으로 작업할때나 혼자서 작업할때나 바쁘게 코딩하다보면 들여쓰기, 괄호 등 엉망인 채로 commit 하는 경우가 있어서 코드리뷰때 발견되면 참 창피한 노릇이다.

그래서 정적분석도구 툴을 사용해서 현재 프로젝트의 소스코드에 컨벤션이 문제가 있나 체크를 하면 좋다. 나아가서는 팀원들이 커밋을 할때 commit hook 로 코드를 검사후 커밋이 가능하도록 하면 컨벤션 때문에 코멘트 달릴 일은 줄어들게 될거다.

팀으 클래스이름의 생성규칙, 별도의 코딩 룰도 체크할수 있다. 아래 내용을 참고하자

https://yunhos.blogspot.com/2021/11/kotlin-ktlint.html 


  1. ktlint 는 gradle에서 task 단위로 실행되도록 해놓는게 편하니까 build.gradle(Module:xxx) 에 다음과 같이 추가하자.
android {
   ...
 
   configurations {  
     ktlint  
   }  
     
   // ktlint 로 검사만 하는 태스크  
   task ktlint(type: JavaExec, group: "verification") {  
     description = "Check Kotlin code style."  
     classpath = configurations.ktlint  
     main = "com.pinterest.ktlint.Main"  
     args "src/**/*.kt", "android", "--reporter=plain", "--reporter=checkstyle,output=${buildDir}/reports/ktlint/ktlint-result.xml"  
      ignoreExitValue true  
   }  
     
   // ktlint 로 검사후 코딩컨베션을 수정까지 해주는 태스크  
   task ktlintFormat(type: JavaExec, group: "formatting") {  
     description = "Fix Kotlin code style deviations."  
     classpath = configurations.ktlint  
     main = "com.pinterest.ktlint.Main"  
     args "-F", "src/**/*.kt", "android", "--reporter=plain", "--reporter=checkstyle,output=${buildDir}/reports/ktlint/ktlint-result.xml"  
     ignoreExitValue true  
   }
}
 
 
dependencies {
   ...
    ktlint "com.pinterest:ktlint:0.36.0"  
}
  1. ktlint, ktlintFormat 태스크는 gradlew 또는 Android Studio 의 오른쪽의 Gradle -> Tasks -> Verification -> ktlint 에서 실행해볼수 있다.
 ./gradlew ktlint
  1. git 에 commit 할때 ktlint 로 코드규칙을 잘 준수했는지 검사하도록 강제해보자.git 에서 commit또는 push할때 뭔가 처리를 하고 싶으면 hook(갈고리) 파일을 이용해서 하고싶은 작업을 적어넣으면 된다. 우린 ./gradlew kotlin 을 실행하고 결과가 실패하면 commit안하도록 하자.
    .git/hooks/pre-commit 파일을 없으면 만들어서 아래처럼 적자.
#!/bin/sh

echo "ktlint start"

./gradlew ktlint

status=$?

if [ "$status" = 0 ] ; then
    echo "No Issue"
    exit 0
else
    echo 1>&2 "Issue Found"
    exit 1
fi
  1. 룰을 어긴 코드를 짜서 커밋 해보자.
class MainActivity : AppCompatActivity() {  
  
    override fun onCreate(savedInstanceState: Bundle?) {  
                 super.onCreate(savedInstanceState)  
        setContentView(R.layout.activity_main2)  
    }  
}

에러가 출력되고 실패가 뜬다.

> Task :app:ktlint FAILED
/Users/hehe/app/src/main/java/com/example/myapplication/MainActivity.kt:9:1: Unexpected indentation (15) (it should be 8) (cannot be auto-corrected)
"checkstyle" report written to /Users/hehe/app/build/reports/ktlint/ktlint-result.xml

Execution failed for task ':app:ktlint'.
> Process 'command '/Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java'' finished with non-zero exit value 1

SourceTree에서 해봐도 에러가 나온다.
enter image description here

에러대로 수정하고 다시 커밋하면 잘된다.

Android Studio 4.1 Template Plugin Example 템플릿 플러그인 작성

Android Studio 4.1 Template Plugin Example 템플릿 플러그인 작성

기존의 file template 기능이 Android Studio 4.1 부터 갑자기! 없어져서 템플릿파일들을 생성해서 사용하는것이 불가능하게 되었다.
대신 IntelliJ Plug-In형태로 만들어서 사용하면 되는데, 무려 Plug-in을 만들어야 되기 때문에 귀찮은 면도 있다.

대신 프로그램만들듯이 템플릿 만들수 있기때문에 익숙해진다면 나쁘지도 않을듯하다.

  1. 먼저 intelliJ 에서 Gradle 에서 플러그인 프로젝트를 생성한다.
    enter image description here

enter image description here
Artifact 항목의 GroupId,ArtifactId,Version은 일단 대충입력하자.

  1. Android Studio 가 익숙하니까 Android Studio에서 위의 plugin프로젝트를 열자.
  2. 아래그림 처럼 buildPlugin 태스크를 실행하면 /build/libs 폴더에 xxx-SNAPSHOT.jar 파일이 보인다. 실행해봤자 Android Studio에서는 설치가 안된다.
    enter image description here
build.gradle파일의 

intellij {  
  version '2020.3.3'  
}

이부분에 android와 kotlin을 추가하자
intellij {  
  plugins = ['Kotlin', 'android']  
    localPath '/Applications/Android Studio.app/Contents'  
  version '2020.3.3'  
}
  1. 이제 다시 빌드하고 나서 , AndroidStudio->Preference->Plugins 에서 [톱니바퀴]->install plugin from disk… 로 /build/libs 폴더에 xxx-SNAPSHOT.jar 파일을 선택하면 설치가 되고 목록에도 나타난다.
    enter image description here

  2. 껍데기 뿐인 플러그인이지만 , 이런식으로 플러그인을 추가/변경할수 있다. 프로젝트에서만 사용할것이기 때문에 마켓에는 안올리고 이렇게 팀내부에서 플러그인형태로 공유해서 쓰는것을 전제로 작성할거다

  3. IntellJ Plugin 은 resources/META-INF/plugin.xml 파일에서 설정값들을 수정하거나 추가하여 작성한다.

  4. plugin 은 intelliJ 에디터를 좀더 편하게 쓰기위해서 기능을 확장하는 것이기에 src 폴더에 갖가지 기능을 구현할수도 있지만 우리 목적은 android 템플릿을 만드는것이니까 템플릿구성까지만 해보자

  5. build.gradle 파일에 안드로이드 관련 설정을 추가하고 JDK도 8버젼으로 맞춰준다.

plugins {  
  id 'org.jetbrains.intellij' version '0.7.2'  
  id 'org.jetbrains.kotlin.jvm' version '1.4.10'  
}  
  
group 'com.miserutv'  
version '0.0.1'  
  
repositories {  
  mavenCentral()  
}  
  
dependencies {  
  implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"  
  testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0'  
  testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'  
}  
  
compileKotlin {  
  kotlinOptions.jvmTarget = "1.8"  
}  
  
compileTestKotlin {  
  kotlinOptions.jvmTarget = "1.8"  
}  
  
// See https://github.com/JetBrains/gradle-intellij-plugin/  
intellij {  
  plugins = ['Kotlin', 'android']  
    localPath '/Applications/Android Studio.app/Contents'  
  version '2020.3.3'  
}  
patchPluginXml {  
  changeNotes """  
 Add change notes here.<br> <em>most HTML tags may be used</em>"""}  
test {  
  useJUnitPlatform()  
}
  1. plugin.xml 파일을 수정한다. 여기서 중요한것이 intelliJ 의 Template Wizard기능을 이용할것이기 때문에 List형식의 Template 클래스목록을 돌려주는 클래스를 지정하고 해당 만들어야한다.
<idea-plugin>  
    <id>com.miserutv.templates</id>  
    <name>Plugin display name here</name>  
    <vendor email="support@yourcompany.com"  
  url="http://www.yourcompany.com">  
        YourCompany  
    </vendor>  
  
    <description><![CDATA[  
    Enter short description for your plugin here.<br>  
    <em>most HTML tags may be used</em>  
    ]]></description>  
  
    <!-- please see https://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/plugin_compatibility.html  
 on how to target different products -->  <depends>com.intellij.modules.platform</depends>  
    <depends>org.jetbrains.android</depends>  
    <depends>com.intellij.modules.androidstudio</depends>  
  
    <extensions defaultExtensionNs="com.android.tools.idea.wizard.template"> 
        <!-- Add your extensions here -->  
  <tools.idea.wizard.template.wizardTemplateProvider  
  implementation="com.miserutv.templates.recipes.TemplateWizardProvider"/>  
    </extensions>  
  
    <actions>  
        <!-- Add your actions here -->  
  </actions>  
</idea-plugin>
  1. 자,이제 src/main/kotlin 밑에com.miserutv.templates.recipe 라는 패키지경로를 만들고 TemplateWizardProvider.kt 파일을 만들어서 템플릿목록을 반환하도록 하자.
//com.miserutv.templates.recipe  폴더에 TemplateWizardProvider.kt
import com.android.tools.idea.wizard.template.WizardTemplateProvider  
  
class TemplateWizardProvider: WizardTemplateProvider() {  
    override fun getTemplates() = listOf(HelloWorldTemplate)  
}
  1. listOf(HelloWorldTemplate) 에는 여러개의 템플릿을 지정할수도있는데, 즉 한번에 여러군데의 템플릿을 지정할수 있어서 프로젝트 전반에 걸친 다양한 템플릿을 등록할수도 있는거다.
    일단 FeatureScreenTemplate 파일이 없으니 com.miserutv.templates.recipe 폴더에(아니면 template 별로 폴더를 만들어서 지정해도 좋다.) HelloWorldTemplate object 파일을 만들어서 Template interface를 구현해보자.
object HelloWorldTemplate : Template {  
    override val category: Category  
        get() = Category.Activity // New 할때, Activity목록,Fragment목록,Other 목록등 나타날 카테고리지정한다.  
  override val constraints: Collection<TemplateConstraint>  
        get() = emptyList()     //AndroidX, kotlin 사용제약을 할지 결정  
  override val description: String  
        get() = "My description" // 설명  
  override val documentationUrl: String?  
        get() = null // 설명주소  
  override val formFactor: FormFactor  
        get() = FormFactor.Mobile //어떤 종류 Mobile,Wear등  
  override val minCompileSdk: Int  
        get() = MIN_API // 컴파일 최소 API버젼  
  override val minSdk: Int  
        get() = MIN_API // 컴파일 최소 API버젼  
  override val name: String  
        get() = "Hello Android Template" // 선택목록에 표시될 템플릿이름  
  override val recipe: Recipe  
        get() = { // 레시피에는 생성하고자할 Acvitiy,Fragment,Layout,Value XMl등을 지정하여 프로젝트폴더에 복사되도록한다.  
  helloWorldActivityRecipe(  
                    it as ModuleTemplateData,  
                    activityClassInputParameter.value,  
                    activityTitleInputParameter.value,  
                    layoutNameInputParameter.value,  
                    packageName.value  
  ) // .value 는 화면에서 받은 입력값이다.  helloWorldActivityRecipe파일은 밑에서 만들꺼다
  }  
  override val revision: Int  
        get() = 1 // 템플릿 버젼  
  override val uiContexts: Collection<WizardUiContext>  
        get() = listOf(WizardUiContext.ActivityGallery, WizardUiContext.MenuEntry, WizardUiContext.NewProject, WizardUiContext.NewModule) // 어떤 메뉴등에서 템필릿이 표시될지 지정  
  override val widgets: Collection<Widget<*>>  
        get() = listOf(  
                  TextFieldWidget(activityTitleInputParameter),  
                  TextFieldWidget(activityClassInputParameter),  
                  TextFieldWidget(layoutNameInputParameter),  
                  PackageNameWidget(packageName),  
                  LanguageWidget()  
            ) // 지금 템플릿을 New로 대화상자가 떴을때 , 필요한 위젯(액티비티이름, 레이아웃이름등)들을 입력할수있도록한다.  
  
  override fun thumb(): Thumb {  
        return Thumb { findResource(this.javaClass, File("template_.png"))}  
  }  
  
    //액티비티 생성이름  
  val activityClassInputParameter = stringParameter {  
  name = "Activity Name"  
  default = "MainActivity"  
  help = "The name of the activity class to create"  
  constraints = listOf(Constraint.CLASS, Constraint.UNIQUE, Constraint.NONEMPTY)  
        suggest = { layoutToActivity(layoutNameInputParameter.value) }  
 }  //레이아웃 생성이름  
  var layoutNameInputParameter : StringParameter = stringParameter {  
  name = "Layout Name"  
  default = "activity_main"  
  help = "The name of the layout to create for the activity"  
  constraints = listOf(Constraint.LAYOUT, Constraint.UNIQUE, Constraint.NONEMPTY)  
        suggest = { activityToLayout(activityClassInputParameter.value) }  
 }  
  //액티비티 생성이름  
  val activityTitleInputParameter = stringParameter {  
  name = "Title"  
  default = "MainActivity"  
  help = "The name of the activity. For launcher activities, the application title"  
  visible = { false }  
  constraints = listOf(Constraint.NONEMPTY)  
        suggest = { activityClassInputParameter.value }  
 }  
  //패키지이름지정  
  val packageName = defaultPackageNameParameter  
}  
  
val defaultPackageNameParameter get() = stringParameter {  
  name = "Package name"  
  default = "com.mycompany.myapp"  
  constraints = listOf(Constraint.UNIQUE, Constraint.NONEMPTY)  
    suggest = { packageName }  
}
  1. 이제 대화상자를 띄우는 것까지 했으니 입력받은 값으로 실제 화일들을 생성하는 부분을 구현하자. RecipeExcutor의 확장함수 형태로 만들면된다.
//HelloWorldRecipe.kt
//대화상자에서 Finish 버튼을 누루면 helloWorldActivityRecipe 함수가 기동된다.  
fun RecipeExecutor.helloWorldActivityRecipe(  
        moduleData: ModuleTemplateData,  
        activityClass: String,  
        activityTitle: String,  
        layoutName: String,  
        packageName: String  
) {  
    val (projectData, srcOut, resOut) = moduleData  
  
  
    generateManifest(  
            moduleData = moduleData,  
            activityClass = activityClass,  
            activityTitle = activityTitle,  
            packageName = packageName,  
            isLauncher = false,  
            hasNoActionBar = false,  
            generateActivityTitle = true,  
    )  
  
    save(helloWorldActivity(packageName, activityClass, layoutName), srcOut.resolve("${activityClass}.${projectData.language.extension}"))  
    save(activityHelloWorldXml(activityClass), resOut.resolve("layout/${layoutName}.xml"))  
  
    open(resOut.resolve("layout/${layoutName}.xml"))  
  
}
  1. Recipe에서는 generateManifest 함수로 Manifest에 새로운 액티비티 추가도 해주고, save명령으로 현재 플러그인안에 있는 원본소스파일과 리소스등을 작업프로젝트에 복사해준다. kotlin소스파일과 레이아웃소스파일을 만들자.
//recipes/myresources/res/src 단,폴더이름은 상관없다.
fun helloWorldActivity(  
  packageName: String,  
  activityClass: String,  
  layoutName: String,  
) = """  
package ${escapeKotlinIdentifier(packageName)}  
  
import android.os.Bundle  
import androidx.appcompat.app.AppCompatActivity  
import ${escapeKotlinIdentifier(packageName)}.R  
  
class ${activityClass} : AppCompatActivity() {  
 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.${layoutName})  
 }}  
 
//recipes/myresources/res/layout 단,폴더이름은 상관없다.
fun activityHelloWorldXml(  
  activityClass: String,  
) = """  
<?xml version="1.0" encoding="utf-8"?>  
<${getMaterialComponentName("android.support.constraint.ConstraintLayout", true)}  
 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=".${activityClass}">  
  
 <TextView android:id="@+id/tv_hello" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" />  
</${getMaterialComponentName("android.support.constraint.ConstraintLayout", true)}>  
"""
"""
  1. 다시 buildPlugin 태스크를 실행하고, plugin을 재설치한다.
  2. 샘플프로젝트를 하나 만들어서 액티비티를 추가해보자. 액티비티 목록에도 보이고, 액티비티 선택리스트에도 Hello Android Template가 추가되었다.
    enter image description here

github : https://github.com/schoolhompy/androidStudio_template_plugin_sample