пятница, 4 февраля 2011 г.

За что мне не нравится Java. Паттерны проектирования.


Вообще, если брать отдельно от Java и прочих таких же ограниченных языков, паттерны – хороший инструмент. Они позволяют не изобретать велосипед, а реализовывать проверенные временем фрагменты архитектуры. Проблема Java (а также C#, C++ и т. д.) в том, что все эти паттерны приходится писать богомерзким copy-paste-ом, т. к. ни реализовать их в виде библиотеки, ни ввести в синтаксис языка — невозможно.
Другое дело – Clojure. Здесь я буду приводить пример декоратора с одним методом для простоты демонстрации. Вот как я себе представляю DSL и использование его для реализации этого паттерна.

(ns jv
(:import (deco Decorable)))

(gen-deco "jv.My2" (println "Hello from jv.My2"))
(gen-deco "jv.My3" (println "Hello from jv.My3"))

Здесь генерируются два декоратора в пространстве имен jv. Скомпилировать их можно вызовом (compile 'jv), и clojure создаст два класса My2 и My3.

user=> (compile 'jv)
jv

Использовать декораторы можно так. Откроем скомпилированное пространство имен jv.

user=> (in-ns 'jv)
#<Namespace jv>

Создадим декорируемый элемент.

jv=> (def a (decorable))
#'jv/a

Его метод run выводит строку:

jv=> (.run a)
Hello from Decorable!
nil

Аналогично для классов My2 и My3.

jv=> (def b (jv.My2.))
#'jv/b
jv=> (.run b)
Hello from jv.My2
nil
jv=> (def c (jv.My3.))
#'jv/c
jv=> (.run c)
Hello from jv.My3
nil 

Теперь используем динамическую композицию объектов и декорируем объект а функциональностью объекта b.

jv=> (decorate b a)
nil
jv=> (.run b)
Hello from jv.My2
Hello from Decorable!
nil

Теперь динамически добавим функциональность объекта c.

jv=> (decorate c b)
nil
jv=> (.run c)
Hello from jv.My3
Hello from jv.My2
Hello from Decorable!

Как видите, создание и использование паттерна декоратор ограничилось всего тремя синтаксическими конструкциями: gen-deco, decorable, decorate.
Базовый класс deco.Decorable выглядит так.

package deco;
public class Decorable {
private Decorable deco;

public void run() {
System.out.println("Hello from Decorable!");
}

public Decorable getDeco() {
return deco;
}

public void setDeco(Decorable deco) {
this.deco = deco;
}
}

Макросы, реализующие паттерн «декоратор» – ниже.

(defmacro deco-class [new-name old-name method body & params]
  (let [this (symbol "this")
         prefix (gensym)
         new-method (symbol (str prefix method))
         call-method (symbol (str "." method))]
  `(do
     (defn ~new-method [~this ~@params]
      ~body
        (let [deco# (.getDeco ~this)]
            (if (not (nil? deco#))
                (~call-method deco# ~@params))))

        (gen-class
          :name ~new-name
          :prefix ~prefix
          :extends ~old-name))))

(defn decorate [new-obj old-obj]
   (.setDeco new-obj old-obj))

(defn decorable []
   (deco.Decorable.))

(defmacro gen-deco [name body & params]
  (let [run (symbol "run") ]
      (if (nil? params)
       `(deco-class ~name deco.Decorable ~run ~body)
       `(deco-class ~name deco.Decorable ~run ~body ~params))))

Таким образом, Clojure – язык, который позволяет не плодит ужасающий copy-paste для реализации паттернов, а разработать высокоуровневую абстракцию и автоматизировать все повторяющиеся фрагменты.

Комментариев нет:

Отправить комментарий