2014-04-22 178 views
2

想,我有我的域對象命名「辦公室」如何避免使用null?

case class Office(
    id: Long, 
    name: String, 
    phone: String, 
    address: String 
) { 
    def this(name: String, phone: String, address: String) = this(
     null.asInstanceOf[Long], name, phone, address 
    ) 
} 

當我創建新辦公室

new Office("officeName","00000000000", "officeAddress") 

我不指定ID場becouse我不不知道。當我節省辦公(由ANORM)我現在ID並做到這一點:

office.id = officeId 

所以。我知道使用null是非斯卡拉方式。如何避免在我的情況下使用null

更新#1

使用選項

想,這樣的事情:

case class Office(
    id: Option[Long], 
    name: String, 
    phone: String, 
    address: String 
) { 
    def this(name: String, phone: String, address: String) = this(
     None, name, phone, address 
    ) 
} 

而且,在保存後:

office.id = Option(officeId) 

但是如果我需要找到辦公ID的東西嗎?

SomeService.findSomethingByOfficeId(office.id.get) 

是否清楚? office.id.get看起來不那麼好)

更新#2

大家的感謝!我從你的答案中得到了新的想法!衷心感謝!

+0

是findSomethingByOfficeId(null)一件好事嗎?在沒有officeId的情況下,如果你想通過辦公室ID找到某些東西,你會做什麼?期權會迫使你決定如何處理這種情況(這是件好事)。 – stew

回答

1

只需使id字段爲Option[Long];一旦你有,你可以使用它像這樣:

office.id.map(SomeService.findSomethingByOfficeId) 

這會做你想要什麼,並返回Option[Something]。如果office.idNone,則map()甚至不會調用取景器方法,並且將立即返回None,這是您通常想要的。

如果findSomethingByOfficeId回報Option[Something](應該如此),而不是僅僅Somethingnull /異常,用途:

office.id.flatMap(SomeService.findSomethingByOfficeId) 

這樣,如果office.idNone,它會再次立即返回None;但是,如果它是Some(123),它會將該123轉換爲findSomethingByOfficeId;現在如果取景器返回Some(something)它將返回Some(Something),但如果取景器返回None,它將再次返回None

如果findSomethingByOfficeId可以返回null,你不能改變它的源代碼,任何包裝調用它Option(...) - 將要null秒值進行轉換,以NoneSome(...)包裝任何其他值;如果它在找不到東西時可能會拋出異常,請使用Try(...).toOption換行以獲得相同效果(雖然這也會將任何不相關的異常轉換爲None,這可能是不受歡迎的,但您可以使用recoverWith修復這些異常)。

一般指導方針總是避免null和Scala代碼中的異常(如您所述);總是優先選擇Option[T]mapflatMap鏈接,或者使用一元for語法糖隱藏使用mapflatMap

Runnable的例子:

object OptionDemo extends App { 
    case class Something(name: String) 
    case class Office(id: Option[Long]) 

    def findSomethingByOfficeId(officeId: Long) = { 
    if (officeId == 123) 
     Some(Something("London")) 
    else 
     None 
    } 

    val office1 = Office(id = None) 
    val office2 = Office(id = Some(456)) 
    val office3 = Office(id = Some(123)) 

    println(office1.id.flatMap(findSomethingByOfficeId)) 
    println(office2.id.flatMap(findSomethingByOfficeId)) 
    println(office3.id.flatMap(findSomethingByOfficeId)) 
} 

輸出:

None 
None 
Some(Something(London)) 

對於一個偉大的介紹Scala的相當有用Option[T]類型,請參閱http://danielwestheide.com/blog/2012/12/19/the-neophytes-guide-to-scala-part-5-the-option-type.html

+0

爲什麼downvote ?? –

3

爲什麼不聲明id字段爲Option?你應該避免在Scala中使用null。 Option是更可取的,因爲它是類型安全的,並且與功能範例中的其他構造很好地搭配。

喜歡的東西(我沒有測試此代碼):

case class Office(
    id: Option[Long], 
    name: String, 
    phone: String, 
    address: String 
) { 
    def this(name: String, phone: String, address: String) = this(
     None, name, phone, address 
    ) 
} 
+0

請分享描述你想法的例子。 –

+0

非常感謝!我更新了我的問題。 –

0

當使用id: Option[Long],與

if (office.id.isDefined) { 
    val Some(id) = office.id 
    SomeService.findSomethingByOfficeId(id) 
} 

或也許提取例如選項值例如

office.id match { 
    case None => Array() 
    case Some(id) => SomeService.findSomethingByOfficeId(id) 
} 

此外,您可以定義案例cla小規模企業和對象如下,

trait OId 
case object NoId extends OId 
case class Id(value: Long) extends OId 

case class Office (
    id: OId = NoId, 
    name: String, 
    phone: String, 
    address: String 
) 

注意,由違約id例如以NoId,就沒有必要將呼叫申報this。然後

val office = Office (Id(123), "name","phone","addr") 
val officeNoId = Office (name = "name",phone="phone",address="addr") 

如果id成員定義過去,那麼就沒有必要名稱的成員名稱,

由於調用(整齊)的方法,

office.id match { 
    case NoId => Array() 
    case Id(value) => SomeService.findSomethingByOfficeId(value) 
} 
+0

你似乎剛剛重新設計了'Option [T]'。 –

-1

我更喜歡對象Id屬性更強的限制屬性:

trait Id[+T] { 
    class ObjectHasNoIdExcepthion extends Throwable 
    def id : T = throw new ObjectHasNoIdExcepthion 
} 

case class Office(
    name: String, 
    phone: String, 
    address: String 
) extends Id[Long] 

object Office { 
    def apply(_id : Long, name: String, phone: String, address: String) = 
    new Office(name, phone, address) { 
     override def id : Long = _id 
    } 
} 

如果我試圖讓對象沒有存儲在數據庫中,我會得到異常,這意味着程序行爲有問題。

val officeWithoutId = 
    Office("officeName","00000000000", "officeAddress") 

officeWithoutId.id // Throw exception 

// for-comprehension and Try monad can be used 
// update office: 
for { 
    id <- Try { officeWithoutId.id } 
    newOffice = officeWithoutId.copy(name = "newOffice") 
} yield OfficeDAL.update(id, newOffice) 

// find office by id: 
for { 
    id <- Try { officeWithoutId.id } 
} yield OfficeDAL.findById(id) 


val officeWithId = 
    Office(1000L, "officeName","00000000000", "officeAddress") 

officeWithId.id // Ok 

優點:

1)方法應用於與ID參數可在DAL邏輯incapsulated

private[dal] def apply (_id : Long, name: String, ... 

2)複製方法總是創建新的對象,具有空ID (如果更改數據,則安全)

3)更新的方法是安全(對象不會被默認覆蓋,ID總是需要被指定)

缺點:需要存儲ID屬性

1)特殊serealization/deserealization邏輯(json for webservices等)

PS 如果您擁有不可變對象(ADT)並將其存儲到帶有id +對象版本的數據庫而不是對象替換,則此方法很好。

+0

爲什麼是一個異常而不是'Option' /'None'? –

+0

有例外的解決方案不是改變Id屬性的類型,它包括瞭如果你沒有id但試圖獲得它(例外情況,因爲你期望id被設置但不是)。如果有人設置id = null(Option是AnyRef),那麼使用Option可以獲得異常。 – Yuriy

+0

選項是很好的成語,但不是編譯級別,它是運行時功能和不安全。 – Yuriy