2012-02-28 2 views
10

Braintree Java 라이브러리 용 Clojure 래퍼를 작성하여보다 간결하고 관용적 인 인터페이스를 제공합니다. 내가 this question 같이 내가 명시 적으로이 작업을 수행 할 수 있습니다 알고지도를 기반으로 Java setter를 호출하기위한 Clojure 매크로?

(transaction-request :amount 10.00 :order-id "user42") 

: 나는 자바처럼, 신속하고 간결 객체 인스턴스화하는 기능을 제공하고 싶습니다

(defn transaction-request [& {:keys [amount order-id]}] 
    (doto (TransactionRequest.) 
    (.amount amount) 
    (.orderId order-id))) 

하지만이에 대한 반복적 인 많은 클래스가 있으며 매개 변수가 선택적 일 때 더 복잡해집니다. 리플렉션을 사용하면 훨씬 더 간결하게 이러한 함수를 정의 할 수 있습니다.

(defn set-obj-from-map [obj m] 
    (doseq [[k v] m] 
    (clojure.lang.Reflector/invokeInstanceMethod 
     obj (name k) (into-array Object [v]))) 
    obj) 

(defn transaction-request [& {:as m}] 
    (set-obj-from-map (TransactionRequest.) m)) 

(defn transaction-options-request [tr & {:as m}] 
    (set-obj-from-map (TransactionOptionsRequest. tr) m)) 

분명히 가능한 경우 모두 반성하고 싶습니다. set-obj-from-map의 매크로 버전을 정의하려고 시도했지만 매크로가 충분하지 않습니다. 설명 된대로 eval이 필요합니다. here.

리플렉션을 사용하지 않고 런타임에 지정된 Java 메소드를 호출하는 방법이 있습니까?

미리 감사드립니다.

업데이트 된 솔루션 :

주스의 조언에 따라, 나는 유사한 기술을 사용하여 문제를 해결할 수 있었다. 매크로는 컴파일시에 리플렉션을 사용하여 클래스가 가지고있는 setter 메소드를 식별 한 다음 폼에서 spam을 검사하여 맵에서 param을 확인하고 그 값으로 메소드를 호출합니다. 후 ~ 필드 이름을 알아낼만큼 당신이 당신에 의해 처리하고있는 클래스를 알고 ~

; Find only setter methods that we care about 
(defn find-methods [class-sym] 
    (let [cls (eval class-sym) 
     methods (.getMethods cls) 
     to-sym #(symbol (.getName %)) 
     setter? #(and (= cls (.getReturnType %)) 
         (= 1 (count (.getParameterTypes %))))] 
    (map to-sym (filter setter? methods)))) 

; Convert a Java camelCase method name into a Clojure :key-word 
(defn meth-to-kw [method-sym] 
    (-> (str method-sym) 
     (str/replace #"([A-Z])" 
        #(str "-" (.toLowerCase (second %)))) 
     (keyword))) 

; Returns a function taking an instance of klass and a map of params 
(defmacro builder [klass] 
    (let [obj (gensym "obj-") 
     m (gensym "map-") 
     methods (find-methods klass)] 
    `(fn [~obj ~m] 
     [email protected](map (fn [meth] 
       `(if-let [v# (get ~m ~(meth-to-kw meth))] (. ~obj ~meth v#))) 
       methods) 
     ~obj))) 

; Example usage 
(defn transaction-request [& {:as params}] 
    (-> (TransactionRequest.) 
    ((builder TransactionRequest) params) 
    ; some further use of the object 
)) 
+0

을 선호하는 것이 아니다 반사? 거의 확실하지 않습니다. –

+1

글쎄, 매크로를 사용하여 맵을 reflection_without없이 메소드 호출로 변환 할 수 있습니다. 매크로가 맵을 포함하는 기호를 사용할 수는 없지만 원시 맵 자체 만 사용할 수 있다는 것을 깨닫고 리플렉션을 사용했습니다. 아마도 @runtime_에서의 반사를 피하고 싶습니다. @ joost-diepenmaat은 아래에서 설명합니다. – bkirkbri

답변

8

당신은 컴파일시에 반사를 사용할 수 있으며, :

다음은 매크로와 사용 예입니다 그것으로부터 "정적"setter를 생성합니다. 꽤 오래 전에 꽤 재미있는 코드를 작성했습니다. https://github.com/joodie/clj-java-fields을 참조하십시오 (특히 def 필드 매크로는 https://github.com/joodie/clj-java-fields/blob/master/src/nl/zeekat/java/fields.clj).

+0

이것은 깔끔한 접근법입니다. – amalloy

+0

고마워, 이것이 정말로 도움이되었다. 내 방식을 거꾸로 뒤집고 런타임에 반영하기 위해지도 매개 변수를 사용하는 대신 컴파일러에서 setter를 열거해야했습니다. 추가 보너스는 유효한 매개 변수 만 검사한다는 것입니다. – bkirkbri

1

매크로는 것처럼 간단 할 수있다 :

(defmacro set-obj-map [a & r] `(doto (~a) [email protected](partition 2 r))) 

그러나 같은이 코드 모양을 만들 것입니다 : 내가 생각

(set-obj-map TransactionRequest. .amount 10.00 .orderId "user42") 

당신이 없으면 :

+0

사실이 구문은 이상적이지 않으며 함수 호출자가 사용할 수 없습니다. 호출자 인수를이 구문에 매핑해야합니다. – bkirkbri

+0

와우. 이것이 왜 내가 배우는 것을 사랑하는 이유입니다. – Core