воскресенье, 10 марта 2019 г.

Шаблон проектирования в Scala

Порождающие шаблоны (Creational)

абстрагирование процесса создание объектов

Строитель (Builder)

шаблон создания сложного объекта, у которого куча параметров, которые устанавливаются через сеттеры:
* Pizza - класс с множеством переменных, который нужно инициализировать
* HawaiianPizzaBuilder / SpicyPizzaBuilder - классы, которые инициализируют нужные сеттеры
* Waiter - класс вызова нужного билдера (тот кто заказывает пиццу)
* BuilderTest - вызов всего вместе
package object builder {

  class Pizza {

    private var sauce: String = ""
    private var topping: String = ""

    def setSauce(sauce: String) : Unit = {
      this.sauce = sauce
    }

    def setTopping(topping: String) : Unit = {
      this.topping = topping
    }

    def outputReceipt() : Unit = {
      println(s"Sauce: $sauce\nTopping: $topping")
    }
  }

  abstract class AbstractPizzaBuilder {

    protected var pizza : Option[Pizza] = None

    def getPizza : Option[Pizza] = pizza

    def createPizza() : Unit = {
      pizza = Some(new Pizza)
    }

    def buildSauce() : Unit
    def buildTopping() : Unit
  }

  class HawaiianPizzaBuilder extends AbstractPizzaBuilder {

    override def buildSauce() : Unit = {
      pizza.get.setSauce("mild")
    }

    override def buildTopping() : Unit = {
      pizza.get.setTopping("ham+pineapple")
    }
  }

  class SpicyPizzaBuilder extends AbstractPizzaBuilder {

    override def buildSauce() : Unit = {
      pizza.get.setSauce("hot")
    }

    override def buildTopping() : Unit = {
      pizza.get.setTopping("pepperoni+salami")
    }
  }

  class Waiter {
    
    private var pizzaBuilder : Option[AbstractPizzaBuilder] = None

    def setPizzaBuilder(pizzaBuilder: AbstractPizzaBuilder) : Unit = {
      this.pizzaBuilder = Some(pizzaBuilder)
      println(s"Builder ${pizzaBuilder.getClass.getName} is set as default")
    }

    def createPizza() : Pizza = {
      pizzaBuilder.get.createPizza()
      pizzaBuilder.get.buildSauce()
      pizzaBuilder.get.buildTopping()
      pizzaBuilder.get.getPizza.get
    }
  }


  object BuilderTest {

    def main(args: Array[String]) : Unit = {
      val waiter = new Waiter
      println("Output:")
      waiter.setPizzaBuilder(new HawaiianPizzaBuilder)
      waiter.createPizza().outputReceipt()
      waiter.setPizzaBuilder(new SpicyPizzaBuilder)
      waiter.createPizza().outputReceipt()
    }
  }
}

Фабричный метод ( Factory )

метод для создания нужного подкласса, в зависимости от параметра
реализуется просто через case
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)

func вызовется при первом обращении к x
lazy val x = func()
Пример однопоточной реализации на java:
private volatile Helper helper = null;
public Helper getHelper() {
 if (helper == null) helper = new Helper();
 return helper;
}

Прототип ( Prototype )

паттерн создания объекта через клонирование другого объекта вместо создания через конструктор.
class Waffle(
    protected var name: String,
    protected var primaryFilling: String,
    protected var specialFilling: Option[String] = None
    )
 extends Cloneable {

 override def clone(): Waffle = {
   super.clone().asInstanceOf[Waffle]
 }

 def output() : Unit = {
   println(s"Waffle $name with primary filling $primaryFilling"
  + (if (specialFilling != None) specialFilling.get else ""))
 }
}

object PrototypeTest {

 def main(args: Array[String]) : Unit = {
   println("Output:")
   val chocolateWaffle = new Waffle("ChocolateWaffle", "Chocolate")
   chocolateWaffle.output()
   chocolateWaffle.clone().output()
   val coconutWaffle = new Waffle("CoconutWaffle","Condensed milk", Some("Coconut"))
   coconutWaffle.output()
   coconutWaffle.clone().output()
 }
}

Одиночка (Singleton)

гарантирующий, что в однопроцессном приложении будет единственный экземпляр некоторого класса, и предоставляющий глобальную точку доступа к этому экземпляру
В java реализуется через статичную переменную
object Singleton {}

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

Адаптер ( Adapter )

позволяет объектам с несовместимыми интерфейсами работать вместе.

В Java нужен явный класс LoggerToLogAdapter который инициализируеся классом, который нужно преобразовать и наследуюется от класса к которому нужно привести
В Scala преобразование делается автоматически через подмену "implicit"
trait Log {
  def warning(message: String)
  def error(message: String)
}

final class Logger {
  def log(level: Level, message: String) { /* ... */ }
}

implicit class LoggerToLogAdapter(logger: Logger) extends Log {
  def warning(message: String) { logger.log(WARNING, message) }
  def error(message: String) { logger.log(ERROR, message) }
}

val log: Log = new Logger() 
В java:
Log log = new LoggerToLogAdapter(new Logger());

Мост (Bridge)

используемый в проектировании программного обеспечения чтобы «разделять абстракцию и реализацию так, чтобы они могли изменяться независимо»
В объект передается класс API с конкретной реализацией:
trait DrawingAPI {
  def drawCircle(x: Double, y: Double, radius: Double)
}

class DrawingAPI1 extends DrawingAPI {
  def drawCircle(x: Double, y: Double, radius: Double) = println(s"API #1 $x $y $radius")
}

class DrawingAPI2 extends DrawingAPI {
  def drawCircle(x: Double, y: Double, radius: Double) = println(s"API #2 $x $y $radius")
}

abstract class Shape(drawingAPI: DrawingAPI) {
  def draw()
  def resizePercentage(pct: Double)
}

class CircleShape(x: Double, y: Double, var radius: Double, drawingAPI: DrawingAPI)
    extends Shape(drawingAPI: DrawingAPI) {

  def draw() = drawingAPI.drawCircle(x, y, radius)

  def resizePercentage(pct: Double) { radius *= pct }
}

object BridgePattern {
  def main(args: Array[String]) {
    Seq (
 new CircleShape(1, 3, 5, new DrawingAPI1),
 new CircleShape(4, 5, 6, new DrawingAPI2)
    ) foreach { x =>
        x.resizePercentage(3)
        x.draw()   
      } 
  }
}

Декоратор (Decorator)

для динамического подключения дополнительного поведения к объекту.
Шаблон Декоратор предоставляет гибкую альтернативу практике создания подклассов с целью расширения функциональности.

В Java нужно было бы передавать класс параметром и вызывать функции из класса-параметра.
В Scala нужные функции можно подмешать через with нужного trait (см. патерн внедрение зависимостей)
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

Фасад (Facade)

позволяющий скрыть сложность системы путём сведения всех возможных внешних вызовов к одному объекту, делегирующему их соответствующим объектам системы
Просто внедрение нескольких классов переменными и создание функций под методы каждого класса.
trait SubSystemA {
 def methodA1()
 def methodA2()
}

trait SubSystemB {
 def methodB()
}

class ConcreteSubSystemA extends SubSystemA {
 override def methodA1() {
  println("System A")
 }

 override def methodA2() {
  println("System A")
 }
}

class ConcreteSubSystemB extends SubSystemB {
 override def methodB() {
  println("System B")
 }
}

class Facade {
 val subsystemA = new ConcreteSubSystemA()
 val subsystemB = new ConcreteSubSystemB()
 override def methodA1() {
  subsystemA.methodA1()
 }
 override def methodA2() {
  subsystemA.methodA2()
 }
 override def methodB() {
  subsystemB.methodB()
 }
}

// Client
object FacadeClient extends Application {
 var facade = new Facade()
 facade.methodA1()
 facade.methodA2()
 facade.methodB()
}

Заместитель (Proxy)

предоставляющий объект, который контролирует доступ к другому объекту, перехватывая все вызовы (выполняет функцию контейнера).
trait IMath {
  def add(x: Double, y: Double): Double
}

class Math extends IMath {
  def add(x: Double, y: Double) = x + y
}

class MathProxy extends IMath {
  private lazy val math = new Math

  def add(x: Double, y: Double) = math.add(x, y)

}

object Main extends App {
  val p: IMath = new MathProxy
  System.out.println("4 + 2 = " + p.add(4, 2))
}

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

Цепочка обязанностей (Chain of responsibility)

предназначенный для организации в системе уровней ответственности.
Каждый последующий класс добавляет или изменяет поведение.

Итератор (Cursor Iterator)

Объект, позволяющий получить последовательный доступ к элементам объекта-агрегата без использования описаний каждого из агрегированных объектов.
import scala.collection.mutable.ListBuffer
class AlertIterator(alertAgg: AlertAggregator) extends CustomIterator[Alert] {
 var list: ListBuffer[Alert] = alertAgg.list
 //запоминаем позицию
 var position: Int = 0
 
 //и движемся, постепенно увеличивая счетчик
 def next(): Alert = {
  val alert = list(position)
  position += 1
  alert
 }
}

Посредник (Mediator)

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

Null object

инкапсулирование отсутствия объекта путём замещения его другим объектом, который ничего не делает
Синглтон SoundSource возвращает Option class = Option[Sound] , который может возвращаться Some, так и None, которые оба (для Option класса можно использовать getOrElse )
В Java пришлось бы создать спецаильный пустой объект NullSound , наследника Sound
trait Sound {
  def play()
} 
  
class Music extends Sound {
    def play() { /* ... */ }
}

object SoundSource {
  def getSound: Option[Sound] = 
    if (available) Some(music) else None
}
  
for (sound <- SoundSource.getSound) {
  sound.play()
}

Наблюдатель или Издатель-подписчик (Observer)

Реализует у класса механизм, который позволяет объекту этого класса получать оповещения об изменении состояния других объектов, и тем самым наблюдать за ними
* Набиваем массив с классами, которые будет оповещать.
* Реализуем метод, который обходит массив и выполняет publish
* Оповещаемые классы наследуем от базового (AlertObservable и AlertObserver) и делаем частную релаизацию publish
import scala.collection.mutable.ArrayBuffer
class PublishAlert private extends AlertObservable {
 val observerList: ArrayBuffer[AlertObserver] = ArrayBuffer[AlertObserver]();
 def register(observer: AlertObserver): Unit = {
  observerList += observer;
 }
 def unregister(observer: AlertObserver): Unit = {
  val index: Int = observerList.indexOf(observer)
  observerList.remove(index)
 }
 def notifyObservers(alert: Alert): Unit = {
  observerList.foreach {
   observer =>
    observer.publish(alert);
  }
 }
}

class FileAlertSubscriber(observable: AlertObservable) extends AlertObserver {
 observable.register(this);
 def publish(alert: Alert): Unit = {
  println("FileAlertSubscriber called")
 }
}

Состояние (State)

объект должен менять своё поведение в зависимости от своего состояния.
* Создаем несколько классов State с конкретной реализацией handle
* В основной класс Context передаем указатель на нужный State
class Context {
  var state:State = _
  state = new NullState()

  def handle() {
    state.handle()
  }
}

// Concrete Implementation
class NullState extends State {
  override def handle() {
    println("blank algorithm, maybe throw an exception of unitilized context")
  }
}

class StateA extends State {
  override def handle() {
    println("algorithm for state A")
  }
}

// Client
object StateClient extends Application {
  var context = new Context()
  context.state = new StateA()
  context.handle()
  context.state = new NullState()
  context.handle()
}

Стратегия (Strategy)

Выбор алгоритма путём определения соответствующего класса.
В основной класс передаем ссылку на case class с конкретной реализацией
type Strategy = (Int, Int) => Int 

class Context(computer: Strategy) {
  def use(a: Int, b: Int)  { computer(a, b) }
}

val add: Strategy = _ + _
val multiply: Strategy = _ * _

new Context(multiply).use(2, 3)

Шаблонный метод (Template method)

определяющий основу алгоритма и позволяющий наследникам переопределять некоторые шаги алгоритма, не изменяя его структуру в целом.
Просто наследуем класс и переопределяем часть функций:
abstract class SomeFramework {
 def templateMethod() {
  // some implementation here
  someAbstractMethod()
  // some implementation here
 }

 def someAbstractMethod()
}

// Concrete Implementation
class MyFramework extends SomeFramework {
 override def someAbstractMethod() {
  println("partial implementation of the framework")
 }
}

// Client
object TemplateMethodClient extends Application {
 var framework = new MyFramework()
 framework.templateMethod()
}

Внедрение зависимостей (Dependency injection)

Внедрение зависимостей используется для выбора из множества реализаций конкретного компонента в приложении
В scala реализуется через подмешивание trait
trait Repository {
  def save(user: User)
}

trait DatabaseRepository extends Repository { /* ... */ }

trait UserService { self: Repository => // requires Repository
  def create(user: User) {
    // ...
    save(user)
  }
}

new UserService with DatabaseRepository

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

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