본문 바로가기

프로그래밍 기술 노트/Functional Study

[Expression Problem] Open classes , Protocol , 그리고 확장 메서드

 

 

Open Classes , 일명 Monkey patching, 은 확장에 열려있는 Class 를 의미한다.

Ruby 에서 주로 땜빵할때 많이 사용하는 기술이다. OOP 식 접근법에서 발전?된 솔루션으로 볼 수 있다.
많이 다르지만, 대충보면 비슷한 extension method 나 implicit 까지 해당 포스트에서 설명

 

Open Class

일반적으로, 한번 정의된 Class 를 확장하려면, Class code 자체를 수정해야한다.
나만 쓰는 내 코드라면 상관없지만, 이곳저곳 다 사용되거나, 아니면 에초에 내가짠 코드가 아닌경우는 class 를 까서 수정하기 쉽지않다.

Ruby 와 같은 일부 언어는, 정의가 이미 끝난 클래스를 외부에서 수정할수있다.
필자는 Ruby 를 잘 모르기 때문에 간단하게 설명하면,

  1. 이미 있는 클래스를
  2. 열어서 (Open)
  3. 내코드를 쓰까 섞고 다시 닫는다

이다.

#이전에 Healer 는 미리 정의 되어 있음
class Healer
  def defence
      # blah blah ~~
  end
end

진짜 돌아가는 코드인지는 모름

Ruby 에서는 위와 같이 이미 있는 Healer 라는 class 에 외부에서 defence 라는 함수를 추가 할수도, 기존 함수를 수정할수도 있다.

딱봐도 어마어마한 자유도를 가져다 줄 것 같은 강력한 기능이다.

Class 를 열 수 있다

다만,  너무나도 강력하기 때문에 위험하다, 그리고 이를 "몽키 패치" 라고 부르는 이유가 있다.

namespace 를 넘어 당기면서 험난한 길을 거쳐온 Class 내에서의 함수의 충돌이 있다면?
분명 defence 만 했는데 채력이 마구 찬다면?
이거 어떤 새끼가 바꾼거야?

바로 의도치 않은 동작을 할 가능성이 매우 높기 때문이다.

알아도 쓸데없는 잡 지식) 문제 발생시 게릴라(임시로)로 쏙 고쳐서 게릴라 패치가 고릴라 패치가 되고 몽키패칭이 된것

 

일반적으로, 정말로 잘 설계되지 않는한, 몽키패칭은 지양하는것이 좋다.

 

Protocol

프로토콜은 clojure 진영에서 주로 사용하는 2가지 표현문제의 해결법중에 하나이며, Clojure 판 Open class 라고 보면 된다. 다만, protocol 은 함수의 범위가 namespace 로 규제되므로, 실 사용시에는 크게 다르다.
원래 짜던 함수에 다형성이 지원 될 뿐이다. 위 링크에서가져온 예시를 보자

# ruby 에서는 실제로 class 가 확장된다 (open 되어 수정된다)
"string".methods.count # => 170
require 'rails'
"string".methods.count # => 225

 

 

(def foo bar)
(in-ns 'monkeypatching)

(defprotocol Monkey
  (patch [this]))
            
;; 프로토콜로 일반 Core 확장
(extend-protocol Monkey
  nil
  (patch [this] "Check out this cool nil!")
  String
  (patch [this] "I'm an awesome string!"))

(patch nil) ; => "Check out this cool nil!"
(patch "this is some string") ; => "I'm an awesome string!"

;; Name space 변경
(in-ns 'workin-hard)

(patch nil) ; => CompilerException 

;; 프로토콜로 확장된 함수는 사용가능함 (namespace 접근)
(monkeypatching/patch "a string") ; => "I'm an awesome string!"

즉.. 원래 (defn patch-string []) / (defn patch-nill [] ) 로 정의 될 함수가 다형성이 지원된 (patch) 로 사용할 뿐이다.

clojure 에 관심있는 사람을 별로 없을테니까 여기까지...

 

Extension Method (Extension Function)

확장 메서드 (C# 에서는 확장메서드, 코틀린에서는 확장 함수) 는 얼핏보면 오픈클래스와 아주 유사하다. 이미 정의된 클래스를 가져다가, 새로운 메서드 를 추가할 수 있으며, 추가된 기능을 가진 namespace  를 가져와서 확장한다.

다만 그렇게 보인다는거지 실제는 구현은 아주 다르다, 개인적인 의견으로는 Protocol 에 더 가깝고, 사실 문법적 설탕에 불과하다.

import oop.Healer

//기존 Healer 에는 defence 함수가 없음
fun Healer.defence() {
    println("Healer defence")
}

fun main(args: Array<String>) {
    val h = Healer()
    h.defence() // Healer defence
}

엥 이거 완전 Open class 아니냐?

Open class 가 아닌 이유는 확장메서드는 눈속임이기 때문이다.

Healer.defence() 와 defence(Healer) 는 동시에 정의 할 수 없다

위 에러를 살펴보자, JVM 에서 동일한 시그니쳐라고 에러를 뿜뿜한다.

즉 확장 메서드는, 그냥 첫번째 파라미터로 확장한 클래스를 받는 함수를 "쓰기 쉽게" 원래 있던 함수인거처럼 보여주는것 뿐이다. 쓰기쉽게, 첫번째 파라미터의 명칭이 "this" 인것 뿐이다.

그렇기 때문에 당연하게, private 변수를 사용하지 못한다.

확장메서드는 유틸성 함수(C# 의 LINQ 같은..)를 추가하는데 아주 유용하다.

 

implicit

암시적 (implicit) 은 scala 에 있는 키워드로, scala 진영에서 가장 좋아하는 (그러면서도 가장 주의하는) 기능이다.
암시적 클래스와 암시적 파라미터가 있는데, 구분은 넘어가고..


대충 말하자면, 원하는 클래스로 컴파일러가 뚝딱 바꿔준다는 개념이다. 


이로써 가능한 트릭들이 어마어마한데, implicit 를 확장 메서드를 구현하는데 활용도 할수있다. (다양한 기술중 하나)

(작성 하면서 알았는데, scala3 에서는 implicit 키워드 없이도 확장메서드가 된다.)

(Scala 의 implicit 는 정확하지 않음 )

 

728x90