我想在scala中实现类似于策略模式的某种功能,而无需诉诸与一长串case语句匹配的模式。这大致就是我的想法:
trait HandlerTrait {
def handlerA(...): Unit
def handlerB(...): Unit
}
SomeClass1 extends HandlerTrait {
override def handlerA(...) {...}
override def handlerB(...) {...}
}
SomeClass2 extends HandlerTrait {
override def handlerA(...) {...}
override def handlerB(...) {...}
}
object MyApp extends App {
// 1. define bindings for these implementations
val myBindings = Map(x -> someClass1, y -> someClass2)
// 2. Such that implementation of someMethod targeting handlerA implementations could look like this:
def someMethod(object: ObjectType): Unit = {
myBindings.get(object.x) match {
case Some(entry) => entry.handlerA(object)
case None => ()
}
}
}
还有几件事:
在scala中,有没有更好,更简洁的方法来实现这一目标?
答案 0 :(得分:0)
我认为减少样板的一种方法是使用类型系统而不是继承。例如,如果您的处理程序的类型为<nav class="navbar">
<div id="ABT-Container">
<ul id="nav">
<li class="w3-animate-right" id="menuElement">
<a href="#" onclick="toggle(this)">
<p class="menu_line"></p>
<p class="menu_line"></p>
<p class="menu_line"></p>
</a>
<ul>
<li>
<a href="#" onclick="console.log('about us clicked')">About US</a>
<ul>
<li></li>
</ul>
</li>
<li>
<a href="#" onclick="toggle(this)">Multi-Levels</a>
<ul>
<li>
<a href="#" onclick="toggle(this)">Sales</a>
<ul>
<li>
<a href="#" onclick="console.log('support clicked')">Support</a>
</li>
</ul>
</li>
<li>
<a href="#" onclick="console.log('another link clicked')">Another Link</a>
</li>
<li>
<a href="#" onclick="console.log('department clicked')">Department</a>
</li>
</ul>
</li>
<li>
<a href="#" onclick="toggle(this)">Multi-Levels2</a>
<ul>
<li>
<a href="#" onclick="toggle(this)">Sales2</a>
<ul>
<li>
<a href="#" onclick="console.log('support2 clicked')">Support2</a>
</li>
</ul>
</li>
<li>
<a href="#" onclick="toggle(this)">Sales3</a>
<ul>
<li>
<a href="#" onclick="console.log('support clicked3')">Support3</a>
</li>
</ul>
</li>
<li>
<a href="#" onclick="console.log('another link2 clicked')">Another Link2</a>
</li>
<li>
<a href="#" onclick="console.log('department clicked2')">Department2</a>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
</nav>
,那么满足此类型的任何函数都可以是处理程序,则无需正式声明T => Unit
甚至HandlerTrait
。
是否使用someMethod
或Map
从键到处理程序的映射取决于您。两者都可以扩展以处理新案件。
以下是一个总结我提议的例子:
case
和用法:
val currentlyDefinedStrategies: PartialFunction[String, Unit] = {
case "1" => println(1)
case "2" => println(2)
}
val newStrategies: PartialFunction[String, Unit] = {
case "3" => println(3)
}
val defaultStrategy: PartialFunction[String, Unit] = {
case _ => println("default")
}
您可以使用scala> currentlyDefinedStrategies("1")
1
scala> currentlyDefinedStrategies("3")
scala.MatchError: 3 (of class java.lang.String) ...
scala> currentlyDefinedStrategies.orElse(newStrategies)("3")
3
scala> currentlyDefinedStrategies.orElse(newStrategies)("4")
scala.MatchError: 4 (of class java.lang.String)
scala> currentlyDefinedStrategies.orElse(newStrategies).orElse(defaultStrategy)("4")
default
或使用其他FP技术来实现类似的模式。重点是保留最相关的代码并摆脱样板。当然,Map
对于您构造代码和根据类而不是函数进行思考可能会很有用,但是思想是相同的。
另请参阅:https://pavelfatin.com/design-patterns-in-scala/#strategy
上面的示例有点简化,您实际上想将参数传递给处理程序(在我们的例子中为HandlerTrait
)。方法如下:
println
您可以在不选择策略的情况下解决问题:
val currentlyDefinedStrategies: Int => PartialFunction[String, Unit] = (x) => {
case "1" => println("1: " + x)
case "2" => println("2: " + x)
case _ => println("default: " + x)
}
...并随后提供策略:
scala> val noStrategy = currentlyDefinedStrategies(1)
noStrategy: PartialFunction[String,Unit] = <function1>
或立即应用该策略:
scala> noStrategy("1")
1: 1
您还可以先决定策略,然后再传递参数:
scala> currentlyDefinedStrategies(1)("1")
1: 1
我认为关键是FP是如此丰富和灵活,以至于策略模式真的不是问题。基本上,val currentlyDefinedStrategies: PartialFunction[String, Int => Unit] = {
case "1" => x => println("1: " + x)
case "2" => x => println("2: " + x)
case _ => x => println("default: " + x)
}
scala> val handlerWithChosenStrategy = currentlyDefinedStrategies("1")
handlerWithChosenStrategy: Int => Unit = $anonfun$1$$Lambda$1374/666224848@59a9f3eb
scala> handlerWithChosenStrategy(1)
1: 1
就是一些适合您使用的函数类型。示例:
type Strategy[T, -A, +B] = PartialFunction[T, A => B]
一个更高级的概念是表达式问题(here),您需要在其中扩展您可以对类型执行的操作以及添加新类型。