함수형 프로그래밍에 비추어 디자인 패턴을 구현하는 방법

패턴을 디자인하는 이유는 무엇입니까?



소프트웨어 개발자로서 우리는 바퀴를 재발명하고 싶지 않습니다. 그것이 제가 Desing Patterns를 좋아하는 이유 중 하나입니다. 하지만 저는 우리가 한 단계 더 나아가 간단한 솔루션을 제공하기 위해 함수형 프로그래밍을 수용할 수 있다고 생각합니다.

훌륭한 것을 만드는 것을 좋아하고 업계에서 모범 사례를 구현하는 소프트웨어 개발자라면 아마도 디자인 패턴에 대해 들어봤을 것입니다.

디자인 패턴은 디자인 문제에 대한 공유 어휘와 일반적인 솔루션을 제공했습니다. 특정 응용 프로그램에 적용합니다.

소프트웨어의 현재 상태와 세상을 지배하는 객체 지향 패러다임에서 몇 년 전 Head First Design Patterns을 읽으면서 여정을 시작했지만, 그 후 함수형 프로그래밍과 Java와 같은 언어의 새로운 기능에 대해 배우기 시작했습니다. 1.8 Release- 및 Kotlin을 사용하여 다음을 깨달았습니다. 함수형 프로그래밍을 사용하면 디자인 패턴을 더 좋고 간결한 방식으로 구현할 수 있습니다.

전략



예를 들어 OOP 방식의 전략 패턴을 사용하여 네 가지 기본 수학 연산을 구현할 수 있습니다.

package com.jetprogramming.designpatterns.headfirst.strategy

interface Strategy {
    fun doOperation(num1: Int, num2: Int): Int
}

class OperationAdd : Strategy {
    override fun doOperation(num1: Int, num2: Int): Int {
        return num1 + num2
    }
}

class OperationMultiply : Strategy {
    override fun doOperation(num1: Int, num2: Int): Int {
        return num1 * num2
    }
}

class Context(private val strategy: Strategy) {

    fun execute(num1: Int, num2: Int): Int {
        return strategy.doOperation(num1, num2)
    }
}


이 설계를 사용하면 현재 코드를 확장하는 것만으로 새 작업을 쉽게 추가할 수 있습니다.

package com.jetprogramming.designpatterns.headfirst.strategy

fun main() {
    val contextAdd = Context(OperationAdd())
    val contextMultiply = Context(OperationMultiply())

    println(contextAdd.execute(1, 2))
    println(contextMultiply.execute(3, 2))
}


하지만... 새로운 작전마다 새로운 클래스가 필요합니다. 더 잘할 수 있을까요?. 아마도 함수를 사용하고 있을 것입니다. 한번 해보자.

package com.jetprogramming.designpatterns.headfirst.strategy

class ContextFn(private val fn: (a: Int, b: Int) -> Int) {

    fun execute(n1: Int, n2: Int) = fn.invoke(n1, n2)
}


Kotlin은 함수형 스타일 또는 OOP로 코드를 작성할 수 있게 해주는 강력한 언어입니다. 상황에 가장 적합한 솔루션을 결정하는 것은 사용자의 몫입니다.

Functional 세계에서는 현재 코드를 확장하기 위해 subtraction function만 추가하면 되고 새 클래스는 필요하지 않습니다! 더 이상 의식이 없습니다.

package com.jetprogramming.designpatterns.headfirst.strategy

fun main() {
    val contextFnSum = ContextFn { x: Int, y: Int -> x + y }
    val contextFnMultiply = ContextFn { x: Int, y: Int -> x * y }
    val contextFnSubtraction = ContextFn { x: Int, y: Int -> x - y }

    println(contextFnSum.execute(1, 2))
    println(contextFnMultiply.execute(3, 2))
    println(contextFnSubtraction.execute(3, 2))
}


와우, 이것은 순수한 기능으로 작업하는 열린 눈이었습니다. 구성은 솔루션을 디자인하는 깔끔한 방법입니다! 그러나 Clojure와 같은 완전한 기능적 프로그래밍 언어는 어떻습니까? 클래스와 객체 없이 그런 솔루션을 구현할 수 있습니까?. 물론.

(defn context [f n1 n2]
      (f n1 n2))

(defn -main
      "I don't do a whole lot ... yet."
      [& args]
      (println (context + 1 2))
      (println (context * 1 2))
      (println (context - 1 2))
      )


당신은 바퀴를 재발명하고 싶지 않다는 것을 알고 있습니다. 디자인 패턴의 공유된 어휘를 가지고 핵심에서 비즈니스 로직으로 솔루션을 만들면 순수한 기능과 불변 데이터 구조를 통해 변경 사항을 명확하게 분리할 수 있습니다. 그대로 유지하고 간단한 솔루션으로 더 나은 결과를 제공합니다.

명령:



이는 메서드 호출을 캡슐화할 수 있는 OOP의 또 다른 중요한 패턴이므로 계산을 호출하는 개체가 작업 수행 방법에 대해 걱정할 필요가 없습니다.

interface Command {
    fun execute()
}

class LoginCommand(private val user: String, private val password: String) : Command {
    override fun execute() {
        DB.login(user, password)
    }
}

class LogoutCommand(private val user: String) : Command {
    override fun execute() {
        DB.logout(user)
    }
}

fun main() {
    val loginCommand = LoginCommand("clojure", "kotlin").execute()
    val logoutCommand = LogoutCommand("clojure").execute()

}


그러나 결국 우리는 함수로 더 잘 할 수 있다는 것을 압니다.

(defn execute [command & args]
      (apply command args))

(execute login "clojure" "kotlin")
(execute login "clojure")



결론: 함수 및 디자인 패턴으로 작성된 코드는 매우 모듈화되고 구성 가능하며 재사용 가능하고 추론하기 쉽습니다.

좋은 웹페이지 즐겨찾기