Подтвердить что ты не робот

У которых шаблон дизайна GOF имеет совершенно другую реализацию (java vs Scala)

Недавно я прочитал следующий вопрос SO:

Есть ли какие-либо варианты использования шаблона посетителя в Scala? Должен ли я использовать сопоставление образцов в Scala каждый раз, когда я использовал бы шаблон посетителя в Java?

Ссылка на вопрос с заголовком: Шаблон посетителя в Scala. Принимаемый ответ начинается с

Да, вы, вероятно, должны начать с сопоставления с образцом вместо шаблон посетителя. Видеть это http://www.artima.com/scalazine/articles/pattern_matching.html

Мой вопрос (вдохновленный вышеупомянутым вопросом) заключается в том, какие шаблоны дизайна GOF имеют совершенно другую реализацию в Scala? Где я должен быть осторожен и не следовать java-модели программирования шаблонов проектирования (Gang of Four), если я программирую в Scala?

Шаблоны создания

  • Аннотация Factory
  • Builder
  • Factory Метод
  • Прототип
  • Синглтон: Непосредственно создайте объект (scala)

Структурные шаблоны

  • Адаптер
  • Мост
  • Composite
  • декоратор
  • Фасад
  • Наименьший вес
  • Proxy

Поведенческие модели

  • Цепочка ответственности
  • Command
  • Переводчик
  • Итератор
  • Mediator
  • Memento
  • Observer
  • Государство
  • Стратегия
  • Метод шаблона
  • Посетитель: Согласование паттенов (scala)
4b9b3361

Ответ 1

Для почти всех из них существуют альтернативы Scala, которые охватывают некоторые, но не все варианты использования этих шаблонов. Все это ИМО, конечно, но:

Шаблоны создания

Builder

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

trait Status
trait Done extends Status
trait Need extends Status

case class Built(a: Int, b: String) {}
class Builder[A <: Status, B <: Status] private () {
  private var built = Built(0,"")
  def setA(a0: Int) = { built = built.copy(a = a0); this.asInstanceOf[Builder[Done,B]] }
  def setB(b0: String) = { built = built.copy(b = b0); this.asInstanceOf[Builder[A,Done]] }
  def result(implicit ev: Builder[A,B] <:< Builder[Done,Done]) = built
}
object Builder {
  def apply() = new Builder[Need, Need]
}

(Если вы попробуете это в REPL, убедитесь, что класс и объект Builder определены в одном блоке, то есть используйте :paste.) Сочетание типов проверки с <:<, аргументами общего типа и метод копирования классов case делает очень мощную комбинацию.

Factory Метод (и абстрактный метод Factory)

Factory Основное использование методов - держать ваши типы прямыми; в противном случае вы можете использовать конструкторы. При использовании Scala мощной системы типов вам не нужна помощь, позволяющая держать ваши типы прямыми, поэтому вы можете использовать конструктор или метод apply в объекте-компаньоне для вашего класса и создавать вещи таким образом. В частности, в случае сопутствующего объекта не сложнее сохранить согласованный интерфейс, чем поддерживать согласованный интерфейс в объекте Factory. Таким образом, большая часть мотивации для объектов Factory отсутствует.

Аналогично, многие случаи абстрактных методов Factory могут быть заменены наличием сопутствующего объекта, наследуемого от соответствующего признака.

Прототип

Конечно, переопределенные методы и тому подобное имеют место в Scala. Тем не менее, примеры, используемые для шаблона Prototype на веб-сайте Design Patterns, скорее нецелесообразны в Scala (или Java IMO). Однако, если вы хотите, чтобы суперклассы выбирали действия на основе своих подклассов, а не позволяли им самим решать, вы должны использовать match, а не неуклюжие тесты instanceof.

Singleton

Scala охватывает их с помощью object. Это одиночные игры - используйте и наслаждайтесь!

Структурные шаблоны

Адаптер

Scala trait обеспечивает гораздо большую мощность здесь - вместо создания класса, который реализует интерфейс, например, вы можете создать признак, который реализует только часть интерфейса, оставив для вас остальное. Например, java.awt.event.MouseMotionListener требует, чтобы вы заполнили два метода:

def mouseDragged(me: java.awt.event.MouseEvent)
def mouseMoved(me: java.awt.event.MouseEvent)

Возможно, вы хотите игнорировать перетаскивание. Затем вы пишете trait:

trait MouseMoveListener extends java.awt.event.MouseMotionListener {
  def mouseDragged(me: java.awt.event.MouseEvent) {}
}

Теперь вы можете реализовать только mouseMoved, когда вы наследуете это. Итак: аналогичный шаблон, но гораздо больше мощности с Scala.

Мост

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

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

trait MouseMotioner extends java.awt.event.MouseMotionListener {
  def mouseMotion(me: java.awt.event.MouseEvent, drag: Boolean): Unit
  def mouseMoved(me: java.awt.event.MouseEvent) { mouseMotion(me, false) }
  def mouseDragged(me: java.awt.event.MouseEvent) { mouseMotion(me, true) }
}

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

Composite

Сложный шаблон особенно легко получить с помощью классов case, хотя внесение обновлений довольно сложно. Это одинаково ценно в Scala и Java.

декоратор

Декораторы неудобны. Обычно вы не хотите использовать одни и те же методы в другом классе в случае, когда наследование не совсем то, что вы хотите; то, что вы действительно хотите, это другой метод в том же классе, который делает то, что вы хотите, а не по умолчанию. рисунок обогащения-my-library часто является превосходной заменой.

Фасад

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

Наименьший вес

Несмотря на то, что идея мухи столь же важна в Scala как Java, у вас есть еще несколько инструментов для ее реализации: lazy val, где переменная не создается, если она фактически не нужна (и после этого используется повторно), и by-name parameters, где вы выполняете только работу, необходимую для создания аргумента функции, если функция действительно использует это значение. Тем не менее, в некоторых случаях шаблон Java остается неизменным.

Proxy

Работает аналогично в Scala как Java.

Поведенческие шаблоны

Цепочка ответственности

В тех случаях, когда вы можете указать ответственные стороны в порядке, вы можете

xs.find(_.handleMessage(m))

предполагая, что каждый имеет метод handleMessage, который возвращает true, если сообщение было обработано. Если вы хотите мутировать сообщение, как оно есть, используйте вместо этого клавишу fold.

Так как легко отбрасывать ответственные стороны в Buffer какого-то рода, сложная структура, используемая в Java-решениях, редко имеет место в Scala.

Command

Этот шаблон почти полностью заменяется функциями. Например, вместо всех

public interface ChangeListener extends EventListener {
  void stateChanged(ChangeEvent e)
}
...
void addChangeListener(ChangeListener listener) { ... }

вы просто

def onChange(f: ChangeEvent => Unit)

Переводчик

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

Итератор

Scala имеет Iterator встроенный в свою стандартную библиотеку. Практически тривиально сделать свой собственный класс Iterator или Iterable; последний обычно лучше, поскольку он делает повторное использование тривиальным. Определенно хорошая идея, но так просто, я бы не назвал ее шаблоном.

Mediator

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

case class Entry(s: String, d: Double, notes: Option[String]) {}

def parse(s0: String, old: Entry) = {
  try { old.copy(s = s0, d = s0.toDouble) }
  catch { case e: Exception => old }
}

Сохраните шаблон посредника, когда вам нужно обрабатывать несколько разных отношений (по одному медиатору для каждого) или когда у вас есть изменяемые данные.

Memento

lazy val почти идеально подходит для многих простейших приложений шаблона памяти, например

class OneRandom {
  lazy val value = scala.util.Random.nextInt
}
val r = new OneRandom
r.value  // Evaluated here
r.value  // Same value returned again

Вы можете создать небольшой класс специально для ленивой оценки:

class Lazily[A](a: => A) {
  lazy val value = a
}
val r = Lazily(scala.util.Random.nextInt)
// not actually called until/unless we ask for r.value

Observer

В лучшем случае это хрупкий шаблон. Поощряйте, когда это возможно, сохранение неизменяемого состояния (см. "Посредник" ) или использование актеров, в которых один актер отправляет сообщения всем остальным о смене штата, но где каждый актер может справиться с устаревшим.

Государственные

Это одинаково полезно в Scala и на самом деле является предпочтительным способом создания перечислений при применении к методам без меток:

sealed trait DayOfWeek
final trait Sunday extends DayOfWeek
...
final trait Saturday extends DayOfWeek

(часто вы хотите, чтобы будни делали что-то, чтобы оправдать это количество шаблонов).

Стратегия

Это почти полностью заменено тем, что методы выполняют функции, реализующие стратегию, и предоставляют функции для выбора.

def printElapsedTime(t: Long, rounding: Double => Long = math.round) {
  println(rounding(t*0.001))
}
printElapsedTime(1700, math.floor)  // Change strategy

Метод шаблона

Черты предлагают гораздо больше возможностей, поэтому лучше всего рассмотреть их еще один образец. Вы можете заполнить столько кода, сколько сможете, из той же информации, что и на уровне абстракции. Я бы не хотел называть это одним и тем же.

Visitor

Между структурная типизация и неявное преобразование, Scala обладает потрясающе большим количеством возможностей, чем типичный шаблон посетителя Java. Нет смысла использовать оригинальный шаблон; вы просто отвлекитесь от правильного пути, чтобы сделать это. Многие из примеров действительно просто хотят, чтобы была функция, определенная на посещаемой вещи, которую Scala может сделать для вас тривиально (т.е. Преобразовать произвольный метод в функцию).

Ответ 2

Хорошо, давайте кратко рассмотрим эти шаблоны. Я рассматриваю все эти шаблоны исключительно с точки зрения функционального программирования и оставляю много вещей, которые Scala может улучшить с точки зрения OO. Ответ Рекса Керра дает интересную контр-точку для моих собственных ответов (я только прочитал его ответ после написания моего собственного).

Имея это в виду, я хотел бы сказать, что важно изучать постоянные структуры данных (функционально чистые структуры данных) и монады. Если вы хотите углубляться, я думаю, что основы теории теорий важны - теория категорий может формально описать все программные структуры, в том числе императивные.

Шаблоны создания

Конструктор - это не что иное, как функция. Например, конструктор без параметров для типа T является не более чем функцией () => T. Фактически, Scala синтаксический сахар для функций используется для классов case:

case class T(x: Int)

Это эквивалентно:

class T(val x: Int) { /* bunch of methods */ }
object T {
  def apply(x: Int) = new T(x)
  /* other stuff */
}

Чтобы вы могли создать T с T(n) вместо new T(n). Вы могли бы даже написать это следующим образом:

object T extends Int => T {
  def apply(x: Int) = new T(x)
  /* other stuff */
}

Который превращает T в формальную функцию, не меняя никакого кода.

Это важный момент, чтобы иметь в виду, думая о творческих узорах. Поэтому давайте посмотрим на них:

Аннотация Factory

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

Builder

Шаблоны Builder могут быть заменены встроенными функциями или приложениями с частичной функцией.

def makeCar: Size => Engine => Luxuries => Car = ???
def makeLargeCars = makeCar(Size.Large) _

def makeCar: (Size, Engine, Luxuries) => Car = ???
def makeLargeCars = makeCar(Size.Large, _: Engine, _: Luxuries)

Factory Метод

Становится устаревшим, если вы отбрасываете подклассы.

Прототип

Не меняется - на самом деле это обычный способ создания данных в функциональных структурах данных. См. Метод case copy или все не изменяемые методы в коллекциях, возвращающие коллекции.

Singleton

Синглеты не особенно полезны, когда ваши данные неизменяемы, но Scala object реализует этот шаблон безопасным образом.

Структурные шаблоны

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

Не то, чтобы некоторые шаблоны здесь не актуальны. Я просто говорю, что, как правило, вы должны смотреть на вещи выше, а не пытаться перевести структурные шаблоны в функциональные эквиваленты.

Adapter

Этот шаблон связан с классами (номинальная типизация), поэтому он остается важным, если у вас есть это, и не имеет значения, если вы этого не сделаете.

Мост

Связано с архитектурой OO, так же, как указано выше.

Composite

Лот в объективах и молниях.

декоратор

A Decorator - это просто функциональная композиция. Если вы украшаете целый класс, это может не применяться. Но если вы предоставляете свою функциональность в качестве функций, то составление функции при сохранении ее типа является декоратором.

Фасад

Тот же комментарий, что и для Bridge.

наилегчайшем

Если вы думаете о конструкторах как о функциях, подумайте о мухи в качестве функции memoization. Кроме того, Flyweight является неотъемлемой частью того, как строятся постоянные структуры данных, и многому выигрывает от неизменности.

Proxy

Тот же комментарий, что и для адаптера.

Поведенческие шаблоны

Это повсюду. Некоторые из них совершенно бесполезны, в то время как другие так же актуальны, как и всегда в функциональной обстановке.

Цепочка ответственности

Подобно Decorator, это функциональная композиция.

Command

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

Переводчик

Это монада.

Итератор

Он может быть устаревшим, просто передав функцию в коллекцию. То, что Traversable делает с foreach, на самом деле. Также см. Iteratee.

Mediator

Все еще актуально.

Memento

Бесполезно с неизменяемыми объектами. Кроме того, его точкой является инкапсуляция, которая не является главной проблемой в FP.

Обратите внимание, что этот шаблон не является сериализацией, которая по-прежнему актуальна.

Observer

Релевантно, но см. Функциональное реактивное программирование.

State

Это монада.

Стратегия

Стратегия - это функция.

Метод шаблона

Это шаблон проектирования OO, поэтому он имеет отношение к проектам OO.

для посетителей

Посетитель - это всего лишь метод, получающий функцию. На самом деле, что делает Traversable foreach.

В Scala он также может быть заменен экстракторами.

Ответ 3

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

Flyweight является только кешем и имеет реализацию по умолчанию на большинстве функциональных языков (memoize в clojure)

Даже Template method, Strategy и State могут быть реализованы с просто передачей соответствующей функции в методе.

Итак, я рекомендую не углубляться в шаблоны проектирования, когда вы пробуете себя в функциональном стиле, но читаете некоторые книги о функциональных концепциях (функции высокого порядка, лень, карри и т.д.)