Scala Объекты и рост одиночных чисел
Общий стиль.
Как только я стал лучше писать функциональный код, больше моих методов становятся чистыми функциями. Я нахожу, что многие мои "классы" (в свободном смысле контейнера кода) становятся свободными от государства. Поэтому я создаю им объекты вместо классов, так как нет необходимости их создавать.
Теперь в мире Java наличие класса с "статическими" методами показалось бы довольно странным и обычно используется только для "вспомогательных" классов, как вы видите с Guava и Commons- * и так далее.
Итак, мой вопрос заключается в том, что в мире Scala есть много логики внутри "объектов", а не "классы", вполне нормальные, или есть другая предпочтительная идиома.
Ответы
Ответ 1
Как вы упоминаете в своем названии, объекты - это одноэлементные классы, а не классы со статическими методами, как вы упомянули в тексте своего вопроса.
И есть несколько вещей, которые делают объекты scala лучше, чем статические и одиночные в java-мире, поэтому вполне "нормально" использовать их в scala.
В первую очередь, в отличие от статических методов, объектные методы являются полиморфными, поэтому вы можете легко вставлять объекты в зависимости от них:
scala> trait Quack {def quack="quack"}
defined trait Quack
scala> class Duck extends Quack
defined class Duck
scala> object Quacker extends Quack {override def quack="QUAACK"}
defined module Quacker
// MakeItQuack expects something implementing Quack
scala> def MakeItQuack(q: Quack) = q.quack
MakeItQuack: (q: Quack)java.lang.String
// ...it can be a class
scala> MakeItQuack(new Duck)
res0: java.lang.String = quack
// ...or it can be an object
scala> MakeItQuack(Quacker)
res1: java.lang.String = QUAACK
Это делает их пригодными для использования без жесткой связи и без содействия глобальному состоянию (это две проблемы, которые обычно связаны как с статическими методами, так и с одиночными точками).
Тогда есть тот факт, что они устраняют весь шаблон, который делает одиночные игры такими уродливыми и униимоматичными в java. Это, на мой взгляд, часто упускается из виду, и часть того, что делает одиночные игры настолько хмурыми в java, даже когда они без гражданства и не используются в качестве глобального государства.
Кроме того, шаблон, который вы должны повторить во всех java-синглонах, дает две обязанности класса: обеспечить только один экземпляр себя и делать то, что он должен делать. Тот факт, что scala имеет декларативный способ указать, что что-то является синглоном, освобождает класс и программиста от нарушения принципа единой ответственности. В scala вы знаете, что объект является синглом, и вы можете просто рассуждать о том, что он делает.
Ответ 2
Вы также можете использовать объекты пакета, например. взгляните на объект пакета scala.math здесь
https://lampsvn.epfl.ch/trac/scala/browser/scala/tags/R_2_9_1_final/src//library/scala/math/package.scala
Ответ 3
Да, я бы сказал, что это нормально.
Для большинства моих классов я создаю объект-компаньон для обработки некоторой логики инициализации/проверки. Например, вместо того, чтобы бросать исключение, если валидация параметров не выполняется в конструкторе, можно возвратить Option
или Either
в методе-компаньоне apply-method:
class X(val x: Int) {
require(x >= 0)
}
// ==>
object X {
def apply(x: Int): Option[X] =
if (x < 0) None else Some(new X(x))
}
class X private (val x: Int)
В сопутствующем объекте можно добавить много дополнительной логики, например кеша для неизменяемых объектов.
также хороши для отправки сигналов между экземплярами, если нет необходимости также отправлять сообщения:
object X {
def doSomething(s: String) = ???
}
case class C(s: String)
class A extends Actor {
var calculateLater: String = ""
def receive = {
case X => X.doSomething(s)
case C(s) => calculateLater = s
}
}
Другим вариантом использования объектов является уменьшение объема элементов:
// traits with lots of members
trait A
trait B
trait C
trait Trait {
def validate(s: String) = {
import validator._
// use logic of validator
}
private object validator extends A with B with C {
// all members of A, B and C are visible here
}
}
class Class extends Trait {
// no unnecessary members and name conflicts here
}