Antes de mais nada rs:
Disclaimer - eu sou um programador Ruby e Clojure. Não sou de Scala nem de linguagens estáticamente tipadas.
Sabendo disso, essa é minha opinião que, por não ser de (não programar, nem me identificar muito com) linguagens estáticas, talvez eu esteja perdendo algum ponto crucial na forma de abstração, ou algum ponto importante nesses conceitos. Mas vamos lá:
Eu, particularmente não gosto muito do conceito do sealed. Eu acho que limita coisas que o programador final pode fazer. Eu prefiro deixar as coisas sempre possíveis de serem extensíveis ao extremo, mesmo que isso possa quebrar minha abstração - acho que esse tipo de decisão não é do desenvolvedor de APIs, e sim do programador final. Eu também sei que os programadores normalmente não tomam decisões corretas - inclusive, estou meio que saindo do mundo de Ruby por esse motivo - muitos dos códigos que eu peguei pra dar manutenção estão simplesmente ilegíveis e funcionam provavelmente porque a fé do programador original é muito boa (sério, nada me fez acreditar tanto em poder da fé como certos códigos Ruby que eu peguei hahahaha). Mas, chega de alfinetadas rs.
Sealed traits só podem ser extendidas no arquivo que foram definidas. Sealed classes, não podem ser extendidas nunca. Isso significa que se você tem uma classe selada, você pode garantir que ninguém vai extendê-la. Isso garante, por exemplo, que ninguém vai passar uma subclasse da sua classe pra algum método bizarro, que vai fazer sua classe se comportar de forma inesperada:
sealed class Ratio(val num: Int, val den: Int) {
require( den != 0)
}
// Em algum lugar no espaço...
def resolve(r: Ratio) = r.num / BigDecimal(r.den)
Por exemplo:
scala> class Foo extends BigDecimal
<console>:7: error: illegal inheritance from final class BigDecimal
class Foo extends BigDecimal
Agora, se não fosse selada, olha o que pode acontecer:
class Ratio(val num: int, val den: int) {
require( den != 0)
}
// Em algum lugar no espaço...
def resolve(r: Ratio) = r.num / BigDecimal(r.den)
// Em outro lugar...
class WrongRatio(num: Int, _den: Int) extends Ratio(num, -1) {
override val den = _den
}
resolve(new WrongRatio(10, 0)) // !BANG!
Dahora a vida, consegui montar um código inseguro, falho, e com uma feature que eu não concordo hahahaha. Mas enfim :D
E Sealed Traits é mais quando você quer fazer um pattern match, e não quer que ele falhe nunca. Por exemplo, quero escrever uma árvore binária (que só tem duas possibilidade: Empty e Node) e não quero que ninguém venha com idéia de colocar um "AlmostEmpty", ou "OnlyOneChild" hahahhaa. Aí você cria sua trait e suas classes assim:
sealed trait Tree
case object Empty extends Tree
case class Node(value: Int, left: Tree, right: Tree) extends Tree
def insert(tree: Tree, value: Int): Tree = tree match {
case Empty => Node(value, Empty, Empty)
case Node(oldValue, left, right) if(value < oldValue) => Node(oldValue, insert(left, value), right)
case Node(oldValue, left, right) => Node(oldValue, left, insert(right, value))
}
def toList(tree: Tree): List[Int] = tree match {
case Empty => Nil
case Node(value, left, right) => toList(left) ++ List(value) ++ toList(right)
}
val tree = List(10, 2, 5, 60, 4, 9, 7).foldLeft(Empty: Tree)(insert)
toList(tree) // => List[Int] = List(2, 4, 5, 7, 9, 10, 60)
Assim, você garante que só tem duas implementações da trait "Tree".
(nota mental: espero que você não precise entregar um trabalho pra semana que vem sobre "árvores binárias", senão acabei de ter dar a resposta sem nem pensar rs)