2013-08-08 4 views
3

Я сейчас играю с Scala и попытался выяснить некоторые рекомендации по дизайну классов. (Попытка Scala с недели или около того.)Занятия на основе актеров с интерфейсами или без них

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

object Foo 
object Bar 
class MyActor extends Actor { 
    def receive = { 
    case Foo => ... 
    case Bar => ... 
    case _ => ... 
    } 
} 

Но то, что я узнал от моего объектно-ориентированного (интерфейсов и полиморфизма) носитель говорит мне, что это понятие не очень гибкое.

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

Когда я думаю о написании актеров в Scala, я склонен писать черту, которая указывает некоторые методы. Реализация MyActor должна реализовать эти методы, в которых они могут отправлять свои собственные личные сообщения. При таком подходе мы имеем указанный интерфейс и можем заменить реализацию MyActor безопасным типом.

В мое время чтения учебников и примеров scala я не сталкивался с таким дизайном класса. Разве это не здравый смысл или есть ли лучшие способы сделать это в Scala? Или эти учебные пособия просто маленькие, чтобы охватить такую ​​тему?

ответ

5

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

sealed trait MyActorMessages 
object Foo extends MyActorMessages 
object Bar extends MyActorMessages 

Но этот вид договора не соблюдаются компилятором. Вы можете использовать использовать Typed Channels для принудительного исполнения настоящего договора:

class MyActor extends Actor with Channels[TNil, (MyActorMessages, MyActorReply) :+: TNil] { 
    channel[MyActorMessages] { (req, snd) ⇒ 
    req match { 
     case Foo ⇒ ... 
     case Bar ⇒ ... // You'll get a warning if you forget about `Bar` 
    } 
    } 
} 

компилятор заставит вас (с предупреждением), чтобы обработать все возможные типы сообщений (в данном случае все подтипы MyActorMessages), и отправители будут вынуждены отправить действительны только сообщения с использованием метода <-!- (с ошибкой компиляции).

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

+0

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

+0

@ r2p2: безопасность типов важна, поэтому существуют такие вещи, как типизированные каналы и [типизированные персонажи] (http://doc.akka.io/docs/akka/2.2.0/scala/typed-actors.html). , но сложно использовать актеров только в безопасном типе. Есть такие вещи, как «стать», и у вас должен быть способ ответить любому отправителю и так далее. Существует много дискуссий по этой теме, например: [Почему сообщения для аккских актеров непечатаны?] (Http://stackoverflow.com/q/5547947/406435). – senia

2

Мне очень нравится решение от @senia. Это эффективное использование новой функции Typed Channels от Akka. Но если это решение вам не подходит, я могу предложить нечто более традиционное для мира OO. В этом решении вы определяете фактическое поведение обработки сообщений для актера посредством реализации стратегии, с которой построен актер. Код будет выглядеть примерно так:

//Strategy definition 
trait FooStrategy{ 
    def foo1(s:String):String 
    def foo2(i:Int):Int 
} 

//Regular impl 
class RegularFoo extends FooStrategy{ 
    def foo1(s:String) = ... 
    def foo2(i:Int) = ... 
} 

//Other impl 
class AdvancedFoo extends FooStrategy{ 
    def foo1(s:String) = ... 
    def foo2(i:Int) = ... 
} 

//Message classes for the actor 
case class Foo1(s:String) 
case class Foo2(i:Int) 

//Actor class taking the strategy in the constructor 
class FooActor(strategy:FooStrategy) extends Actor{  
    def receive = { 
    case Foo1(s) => sender ! strategy.foo1(s)   
    case Foo2(i) => sender ! strategy.foo2(i) 
    } 
} 

Тогда для создания экземпляров этого актера:

val regFooRef = system.actorOf(Props(classOf[FooActor], new RegularFoo)) 
val advFooRef = system.actorOf(Props(classOf[FooActor], new AdvancedFoo)) 

Одним из преимуществ является то, что вы развязку бизнес-логику актера от его нормального обработки сообщений поведение , Вы позволяете актерскому классу просто актерский материал (получать из почтового ящика, отвечать отправителю и т. Д.), А затем реальная бизнес-логика инкапсулируется в черту. Это также упрощает тестирование бизнес-логики в отдельности с модульными тестами для признаков признаков. Если вам нужен материал типа актера в чертах признаков, вы всегда можете указать неявный ActorContext методам на FooStrategy, но тогда вы потеряете полное развязывание актера и бизнес-логики.

Как я уже говорил, мне нравится решение от @senia; Я просто хотел дать вам еще один вариант, который может быть более традиционным OO.

+0

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

 Смежные вопросы

  • Нет связанных вопросов^_^