Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Типичные паттерны проектирования на Scala

Anna | 2.06.2014 | нет комментариев

Вступление

В этой статье будут представлены примеры того, как реализуются типичные паттерны проектирования наScala.

Оглавление статьи составляет основу моего выступления на JavaDay конференции (слайды презентации).

Паттерны проектирования

Паттерн проектирования — это всеобщее вторично используемое решение для Зачастую встречающихся задач при разработке программного обеспечения. Паттерн проектирования — это не завершенный код для применения при решении определенной задачи, а лишь образец, тот, что необходимо адаптировать под определенные нужды. Образцы проектирования представляют собой лучшие практики результативного проектирования, которые помогают усовершенствовать читаемость кода и ускорить процесс разработки.

Паттерны проектирования в функциональном мире

Типичные образцы проектирования являются объектно-ориентированными. Они показывают отношения и взаимодействия между классами и объектами. Эти модели являются менее применимы в чисто функциональном программировании (см. Haskell’s Typeclassopedia и Scalaz для «функциональных» образцов проектирования), впрочем, так как Scala является объектно-функциональным языком программирования, то эти модели остаются востребованными даже в функциональном мире Scala кода.

Изредка, паттерны проектирования являются заменой нужных конструкций, отсутствующих в языке программирования. В случаях, когда язык владеет нужными вероятностями, паттерны дозволено упростить либо вообще убрать. В мире Scala множество классических паттернов реализуются с поддержкой колоритных синтаксических конструкций.

В Scala существуют свои добавочные паттерны, впрочем в данной статье будут описаны лишь типичные паттерны проектирования, чай именно они являются ключом к взаимопониманию между разработчиками.

Невзирая на то, что приведенные тут примеры могут быть реализованы больше колоритно, применяя всю мощь языка Scala, я постарался чураться трудных приемов в пользу примитивных и наглядных для того Дабы обеспечить больше ясное осознавание как реализация на Java соотносится с реализацией на Scala.

Обзор паттернов проектирования

Порождающие образцы проектирования (Creational patterns):
Структурные образцы проектирования (Structural patterns):
Поведенческие образцы проектирования (Behavioral patterns):

Дальше будут приводиться реализации паттернов проектирования (каждый код доступен на Github репозитории).

Реализации паттернов проектирования

Фабричный способ (Factory method)

Паттерн фабричный способ применяется для предоставления интерфейса создания объектов, инкапсулируя решение о том, объекты какого класса нужно создавать.

Паттерн разрешает:

  • совместить логику создания трудного объекта,
  • выбирать класс для создания объекта,
  • кэшировать объекты,
  • координировать доступ к разделяемым источникам.

Дальше будут приведены реализации паттерна Статический фабричный способ, тот, что слегка отличается от классической версии паттерна Фабричный способ..

В Java, применяется оператор new для создания экземпляра класса с поддержкой вызова его конструктора. При реализации образца, мы не будем применять конструктор на прямую, а будем применять особый способ для создания объекта.

public interface Animal {}
public class Dog implements Animal {}
public class Cat implements Animal {}
public class AnimalFactory {
    public static Animal createAnimal(String kind) {
        if ("cat".equals(kind)) {
            return new Cat();
        }

        if ("dog".equals(kind)) {
            return new Dog();
        }

        throw new IllegalArgumentException();
    }
}

Пример применения паттерна — создание собаки.

Animal animal = AnimalFactory.createAnimal("dog");

В добавок к конструкторам, Scala предоставляет особую синтаксическую конструкцию, которая схожа на вызова конструктора, но на самом деле комфортный фабричный способ:

trait Animal
private class Dog extends Animal
private class Cat extends Animal

object Animal {
  def apply(kind: String) = kind match {
    case "dog" => new Dog()
    case "cat" => new Cat()
  }
}

Пример применения:

Animal("dog")

Фабричный способ определен в так называемом «объекте-компаньоне” — особый объект синглтон с тем же именем, определенный в том же начальном файле. Такой синтаксис ограничивается «статической» реализацией паттерна, потому как становится немыслимым делегировать создание объекта на подклассы.

Плюсы:
  • Повторное применение имени базового класса.
  • Типовой и лаконичным.
  • Напоминает вызов конструктора.
Минусы:
  • «Статичность» фабричного способа.
Отложеная инициализация (Lazy initialization)

Отложенная инициализация это частный случай отложенных вычислений. Данный паттерн предоставляет механизм инициалvmk!

trait OutputStream {
  def write(b: Byte)
  def write(b: Array[Byte])
}

class FileOutputStream(path: String) extends OutputStream { /* ... */ }

trait Buffering extends OutputStream {
  abstract override def write(b: Byte) {
    // ...
    super.write(buffer)
  }
}

Пример применения:

new FileOutputStream("foo.txt") with Buffering // with Filtering, ...

Делегирование устанавливается статически, во время компиляции, но это, как правило, довольно тех пор, пока мы можем произвольно комбинировать декораторы в момент создания объекта.

В различие от реализации основанной на композиции, подход в Scala сберегает идентичность объекта, следственно дозволено отважно применять equals на декорированных объектах.

В Scala такой подход к декорированию именуется Stackable Trait Pattern.

Плюсы
  • Прозрачная реализация.
  • Суперкомпактный синтаксис.
  • Распознавание объектов сохраняется.
  • Неимение очевидного делегирования.
  • Неимение промежуточного класса декоратора.
Минусы
  • Статическое связывание.
  • Конструкторы без параметров.
Объект-значение (Value object)

Объект-значение представляет собой малое неизменяемое значение. Объекты-значения равны, если все их поля равны. Объекты-значения обширно применяются для представления чисел, дат, цветов и т.д. В корпоративных приложениях такие объекты применяются в качестве DTO объектов для взаимодействия между процессами, — из-за неизменности, объекты-значения комфортны в многопоточном программировании.

В Java нет особого синтаксиса для создания объектов-значений, взамен этого создается класс с конструктором, getter-способами и дополнительными способами (equals, hashCode, toString).

public class Point {
    private final int x, y;

    public Point(int x, int y) { this.x = x; this.y = y; }

    public int getX() { return x; }

    public int getY() { return y; }

    public boolean equals(Object o) {
        // ...
        return x == that.x && y == that.y;
    }

    public int hashCode() {
        return 31 * x   y;
    }

    public String toString() {
        return String.format("Point(%d, %d)", x, y);
    }
}

Пример применения:

Point point = new Point(1, 2)

В Scala, дозволено применять кортежи либо case-классы для объявления объектов-значений. Когда обособленный case-класс не необходим, дозволено применять кортежи:

val point = (1, 2) // new Tuple2(1, 2)

Кортежи это предопределенные постоянные «коллекции», которые могут содержать фиксированное число элементов разных типов. Кортежи предоставляют конструктор, getter-способы, и все вспомогательные способы.

type Point = (Int, Int) // Tuple2[Int, Int]

val point: Point = (1, 2)

В тех случаях, когда всё же выделенный класс нужен, либо когда требуются больше описательные имена для элементов данных, дозволено определить case-класс:

case class Point(x: Int, y: Int)

val point = Point(1, 2)

Case-классы делают параметры конструктора класса свойствами. По-умолчанию, case-классы неизменяемы. Как и кортежи, они предоставляют все нужные способы механически. В добавок, case-классы являются валидными классами, а значит с ними дозволено трудиться как с обыкновенными классами (скажем, наследоваться от них).

Плюсы
  • Суперкомпактный синтаксис.
  • Предопределенные конструкции языка — кортежи.
  • Встроенные нужные метоrmark! private final String s; PrintCommand(String s) { this.s = s; } public void run() { System.out.println(s); } } public class Invoker { private final List<Runnable> history = new ArrayList<>(); void invoke(Runnable command) { command.run(); history.add(command); } } Invoker invoker = new Invoker(); invoker.invoke(new PrintCommand(“foo”)); invoker.invoke(new PrintCommand(“bar”));

    В Scala существует особый механизм для отложенных вычислений:

    object Invoker {
      private var history: Seq[() => Unit] = Seq.empty
    
      def invoke(command: => Unit) { // by-name parameter
        command
        history : = command _
      }
    }
    
    Invoker.invoke(println("foo"))
    
    Invoker.invoke {
      println("bar 1")
      println("bar 2")
    }
    

    Вот так дозволено конвертировать всякое выражение либо блок кода в функцию-объект. Вызовы способа println выполняются внутри вызовов invoke способа, и после этого сохраняются в последовательности history.

    Плюсы
    • Суперкомпактный синтаксис.
    Минусы
    • Обобщенное предназначение.
    Цепочка отвественности (Chain of responsibility)

    Паттерн цепочка ответственности отделяет отправителя запроса от его получателя, предоставляя вероятность нескольким объектам обработать запрос. Запрос обрабатывается цепочке, пока какой-то объект не обрабатывает его.

    В нормальной реализации паттерна всякий объект в цепочке наследует базовый интерфейс и содержит добровольный ссылку на дальнейший объект обработки в цепи. Всякому объекту дается вероятность либо обработать запрос (и прекратить обработку), либо передать запрос дальнейшему обработчику в цепочке.

    public abstract class EventHandler {
        private EventHandler next;
    
        void setNext(EventHandler handler) { next = handler; }
    
        public void handle(Event event) {
            if (canHandle(event)) doHandle(event);
            else if (next != null) next.handle(event);
        }
    
        abstract protected boolean canHandle(Event event);
        abstract protected void doHandle(Event event);
    }
    
    public class KeyboardHandler extends EventHandler { // MouseHandler...
        protected boolean canHandle(Event event) {
            return "keyboard".equals(event.getSource());
        }
    
        protected void doHandle(Event event) { /* ... */ }
    }
    

    Пример применения:

    KeyboardHandler handler = new KeyboardHandler();
    handler.setNext(new MouseHandler());
    

    В Scala предусмотрен больше изящный механизм решения сходственных задач, а именно — частичные функции (partial functions). Частичная функция, — это функция определенная на подмножестве допустимых значений своих доводов.

    Правда для построения цепочки дозволено применять комбинацию способов isDefinedAt и apply, больше верным будет применение способа getOrElse

    case class Event(source: String)
    
    type EventHandler = PartialFunction[Event, Unit]
    
    val defaultHandler: EventHandler = PartialFunction(_ => ())
    
    val keyboardHandler: EventHandler = {
      case Event("keyboard") => /* ... */
    }
    
    def mouseHandler(delay: Int): EventHandler = {
      case Event("mouse") => /* ... */
    }
    
    keyboardHandler.orElse(mouseHandler(100)).orElse(defaultHandler)
    

    Значимо подметить, что тут применяется defaultHandler для избежания ошибки на «неопределенных» событиях.

    Плюсы
    • Суперкомпактный синтаксис.
    • Встроенные в язык конструкции.
    Минусы
    • Обобщенное предназначение.
    Внедрение зависимостей (Dependency injection)

    Паттерн внедрения зависимостей разрешает избежать жестко заданные зависимости и подставить зависи

Источник: programmingmaster.ru

 

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB