3 design pattern-а в ruby

Често чувам от люде, които уважавам, че design pattern-ите са начини да се решат ограничения в статичните езици. И че няма нужда от тях в динамичните – ruby, python и perl. Че са напълно излишни “ООП глупости”, от които един истински програмист няма нужда. Силно несъгласен съм.

Шаблоните представляват различни начини да си организираш идеите за да решиш някакъв структурен проблем. Вземете например функциите – тези които дефинирате с def в ruby и python и с function в JavaScript и PHP. Те са нещо като design pattern – начин да групираш последователност от инструкции, за да ги използваш на повече от едно място (още за да създадеш абстракция, да скриеш имплементация, да изградиш кохезия и т.н.). Съвсем аналогично е – те представляват подход към проблема, а не “статична рецепта за решаването му”.

Ще се опитам да илюстрирам горното, като покажа как изглеждат няколко шаблона в java и съответо в ruby.

Фабрика (Factory)

Най-класическият шаблон, с който несъмнено сте запознати, ако сте се зачели в книгата. Той отделя детайлите по създаването на обект от алгоритъма, който го създадава. Например имате библиотека, която прави басейн (pool) от обекти. Разбира се, искате басейните да са генерични – да ги ползвате както за връзки към базата данни, така и за връзки към CVS, например.

Ще направя малко отклонение, в случай че не сте виждали тази концепция. В Java светът много често се говори за database connection pool. Понеже връзките към базата данни са скъпи за създаване, добра идеята е да пуснете десет при стартиране и след това приложението ви да работи с тях. Един басейн ви позволява да вземате обекти от него и да връщате обекти в него. Има редица детайли, като валидация на обекта преди да се вземе (връзката по мрежата може да е загинала), създаване на нов обект ако при поискване всички са заети и т.н. Няма да навлизам в тях, за да задържа примера прост.

Съоветно басейнът ще изглежда така:

public class Pool {

    private final List<?> objects = new LinkedList<Object>()

    public Pool(Factory factory, int limit) {
        this.factory = factory;
        this.limit = limit;

        for (int i = 0; i < limit; i++) {
                 objects.add(factory.create());
        }
    }

    public Object take() {}

    public void putBack(Object object) {}
}

Фабриките така:

interface Factory {
    Object create();
}

class DatabaseConnectionFactory implements Factory {
    public Object create() {
        return DriverManager.getConnection(URL, USER, PASSWORD);
    }
}

А кодът, така:

Pool pool = new Pool(new DatabaseConnectionFactory(), 10);

Ако напишете същия код в руби, той ще има много сходна дефиниция на Pool, но другото ще е излишно.

class Pool
  def initialize(factory, limit)
    @objects = []
    10.times { @objects << factory.call }
  end

  …
end

pool = Pool.new(lambda { Mysql.real_connect($URL, $USER, $PASS) })

Обърнете внимание, че не правя клас Factory – няма нужда. В този случай фабриката може да е просто една ламбда. Шаблонът не е кодът и класовете, а подхода към проблема. Разделяме създаването на обекта от поведението и наричаме това “фабрика”. И не само имаме решение – имаме думичка с която да услесним общуването си.

Посетител (Visitor)

Почти всички отхвърлят този шаблон като маловажен. За мен той е един от най-интересните. Ако не сте съвсем сигурни какво значи double dispatch и какво общо има Посетителя, горещо ви препоръчвам да задълбаете докато не ви стане ясно като бял ден. За мен той е едно от най-ценните неща в книгата.

Като страничен ефект от двойното изпращане, Поселителя ви позволява да прокарате дебела черта между структурите и алгоритмите, които ги обработват. Например имате сложен израз, които се състои от конюнкции, дизюнкции, отрицания и фрази за търсене. Искате да произвеждате различни “изходни” форми на този израз – веднъж като низ със синтаксиса на google, друг път като някаква специална структура и т.н. Посетителят изглежда добро решение. В Java ще започнете със следния код:

public interface TermVisitor {
    void visit(And and);
    void visit(Or or);
    void visit(Not not);
    void visit(Word word);
}

public class And {
    …
    public void accept(TermVisitor visitor) {
        visitor.visit(this);
    }
    …
}

public class Or {
    …
    public void accept(TermVisitor visitor) {
        visitor.visit(this);
    }
    …
}

public class MyVisitor implements TermVisitor {
    public void accept(And and) {
        …
    }

    public void accept(Or or) {
        …
    }

    …
}

И прочее. Виждате как бързо-бързо се вдига доста излишен шум, характерен за Java. Е, в ruby може да сме по-секси:

def accept(term, visitor)
  method_name = :”visit_#{term.class.to_s.downcase}”
  if visitor.respond_to? method_name
    visitor.send(method_name, term)
  else
    visitor.send(:visit_other, term) if visitor.respond_to? :visit_other
  end
end

class MyVisitor
  def visit_and ; … ; end
  def visit_or ; … ; end
  def visit_other ; … ; end
end

Самец (Singleton)

(да не се бърка със Simpleton – простак)

В Java имплементацията е непрятно дълга:

public class DatabaseHandler {

    private static DatabaseHandler self;

    private DatabaseHandler() {}

    public static DatabaseHandler getInstance() {
        if (self == null) {
            self = new DatabaseHandler();
        }

        return self;
    }
}

Докато в Ruby:

def database_handler
  @DB_HANDLER ||= DatabaseHandler.new
end

Чувствителна разлика, нали?

Идеята на шаблоните

Нека повторя. Целта на шаблоните е да идентифицират определен проблем и подхода за решаването му. Искате да отделите специфичното в създаването на обект от алгоритъма, който го създава. Искате да разделите структурите от алгоритмите, които ги обработват. Искате да имате една-единствена инстанция на клас в паметта. Все проблеми, които срещате независимо дали пишете на Lisp или C++. Шаблоните не са статична рецепта как да си напишете кода. Те представляват често срещан проблем и една стратегия за неговото решаване –предвидливо пакетирани в няколко страници.

Това е и идеята на книгата, както аз я виждам. Тя не ви дава “рецепти”. Тя ви поставя няколко проблема, дава решения и показва как може да сглобите тези решения заедно. Създава интересна умствена заигравка, която не цели да ви “научи” на Х шаблона, а да ви покаже как други хора разсъждават за тези проблеми и да ви даде идеи, които могат да бъдат много, много полезни в работата ви.

За шаблоните, обектно-ориентираното програмиране и предразсъдъците

Пиша това понеже имам няколко познати, които много уважавам като програмисти, но никак не харесвам как реагират като пълни пънове когато заговоря за обектно-ориентирано програмиране, шаблони за дизайн и UML. Убеден съм, че на всичките им е писнало от тази крайност, стереотипния Java програмист, който като види динамичен език прави всякакви глупости с него. Но уверявам ви, отричайки фанатично ООП-то и сходните практики, вие попадата в другата досадна крайност. И подобно да омразните от вас архитипни Java програмисти, вие сте слепи за огромна част от софтуерния свят.

Така че, моля ви, не проповядвайте излишни предразсъдъци. Не ми гот да деля дружките си на “Java зеалоти” и “Ruby зеалоти” – два еднакво заблудени лагера.

5 thoughts on “3 design pattern-а в ruby

  1. Здравей, Стефане!

    Много ми харесва твоя блог и начина, по който пишеш. Радвам се, че има хора като теб, които пишат професионално за технологията на програмирането, а не просто за поредната джаджа от света на компютрите. Аз поддържам блог, който се казва PM Stories и е повече ориентиран към процеса на създаване на софтуер и по-специално към управлението на проекти. Вече те включих в списъка на хората, които чета 🙂 Разгледай го – може да ти бъде интересен.

    Поздрави, Майк

  2. Мисля, че можем спокойно да открием и стратегия (политика) в примера за Pool – при създаването на обекта Pool можем да променяме начина, по който се създават обектите в него, като подаваме различна конкретна стратегия/политика по създаването им, т.е. различна конкретна фабрика.

  3. Поредният много добър пост. Но нещо не е наред с markup-a – в Internet Explorer страницата учудващо свършва след „Съоветно басейнът ще изглежда така:“. W3 валидаторът пък намира множество „Opening and ending tag mismatch“.

Вашият коментар

Вашият имейл адрес няма да бъде публикуван.