[Scala] - day 10 : Packaging

Sau khi tạo ra class, method, và field bây giờ chúng ta sẽ xem xét cách quản lý chúng trong những namespace.
Một Scala package được hiểu như 1 tổ chức Scala code. Chúng tạo điều kiện cho việc nhóm những phần code Scala theo thư mục bằng cách sử dụng những đường dẫn. Sử dụng từ khoá package ở đầu file Scala sẽ nói lên toàn bộ class trong file đó nằm trong 1 package cụ thể.

Define Scala Package

Syntax: định nghĩa Package cho 1 file Scala.
package <identifier>
Scala sử dụng tiêu chuẩn như Java cho việc đặt tên cho package, ví dụ, những class có nhiệm vụ cung cấp các method utility và được phát triển ở nhà máy của HoaiPT có thể đóng gói trong “com.hoaipt.utilities.”
Những file source code Scala nên được đặt trong các thư mục tương ứng với đường dẫn package, ví dụ, class “DateUtilities” trong package “com. hoaipt.utilities” nên được lưu trong thư mục tương ứng sau com/hoaipt/utilities/DateUtilities.scala.
Ok, thử tạo 1 vài file với package và compiling nó. Chúng ta sẽ sử dụng scalac để compile source file và sinh class file theo đường dẫn định nghĩa ở package :
Đầu tiên tôi tạo ra 1 class nằm trong thư mục com/hoaipt/utilities/source nhưng package lại đinh nghĩa 1 đường dẫn đến com/hoaipt/utilities.
Hoais-Mac:Workspace HoaiPT$ mkdir -p com/hoaipt/utilities/source 
Hoais-Mac:Workspace HoaiPT$ cat > com/hoaipt/utilities/source/Config.scala
package com.hoaipt.utilities
class Config(val baseUrl: String = "https://hoaiptvn.blogspot.com") 
Hoais-Mac:Workspace HoaiPT$ scalac com/hoaipt/utilities/source/Config.scala 
Hoais-Mac:Workspace HoaiPT$ cd com/hoaipt/utilities 
Hoais-Mac:utilities HoaiPT$ ls
Config.class source

Accessing Packaged Classes

Một class trong package có thể truy cập thông qua đường dẫn đinh nghĩa ở package và tên class.Trong ví dụ trên, class “Config” sẽ được truy cập thông qua đường dẫn “com.hoaipt.utilities.Config.”
Đến đây bạn sẽ thấy những phần trước ở những ví dụ cần import thư viện ngoài chúng ta đã làm những điều tương tự.Hãy thử 1 ví dụ mới, lần này tôi sẽ dùng JDK class Date của java, nó nằm ở trong package java.util package:
scala> val d = new java.util.Date
d: java.util.Date = Sat May 07 08:11:13 WEST 2016
Để thuận tiện hơn trong những trường hợp phải gọi nhiều lần đến những class nằm trong package khác bạn nên import package đó vào trong space mà bạn cần, sau đó class có thể được gọi trực tiếp mà không cần đường dẫn package.
Syntax: Import Package Class
import <package>.<class>
Thử lại ví dụ trên với cách này :
scala> import java.util.Date
import java.util.Date 
scala> val d = new Date
d: java.util.Date = Sat May 07 08:23:38 WEST 2016
Quay lại day-6-Mutable Collections chúng ta tìm hiểu về mutable collection bao gồm Buffer, Map và Set. Tất cả chúng đều nằm trong package collection.mutable, với trường hợp bạn cần cả 3 mutable này bạn không cần import lần lượt cả 3 mà có thể sử dụng placeholder (_) :

scala> import collection.mutable._
import collection.mutable._ 
scala> val b = new ArrayBuffer[String]
b: scala.collection.mutable.ArrayBuffer[String] = ArrayBuffer() 
scala> b += "Hello"
res0: b.type = ArrayBuffer(Hello) 
scala> val q = new Queue[Int]
q: scala.collection.mutable.Queue[Int] = Queue() 
scala> q.enqueue(3, 4, 5) 
scala> val pop = q.dequeue
pop: Int = 3 
scala> println(q)
Queue(4, 5)
Vậy còn nếu bạn chỉ cần 2 trong số 3 kiểu mutable collection thì sao?bạn có thể dùng đến cú pháp import group.
Syntax: Import Group
import <package>.{<class 1>[, <class 2>...]}
Giờ tôi chỉ cần Queue và ArrayBuffer
scala> import collection.mutable.{Queue,ArrayBuffer}
import collection.mutable.{Queue, ArrayBuffer} 
scala> val q = new Queue[Int]
q: scala.collection.mutable.Queue[Int] = Queue() 
scala> val b = new ArrayBuffer[String]
b: scala.collection.mutable.ArrayBuffer[String] = ArrayBuffer() 
scala> val m = Map(1 -> 2)
m: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2)
Vậy sẽ sao nếu bạn import 2 class cùng tên? khi này sẽ bị conflict và khi bạn gọi đến class đó bạn cần đưa cả phần package vào để Scala biết bạn dùng class nào,có 1 cách khác cho trường hợp này đó là dùng Import Alias.
Syntax: Import Alias
import <package>.{<original name>=><alias>}
Ví dụ :
scala> import collection.mutable.{Map=>MutMap}
import collection.mutable.{Map=>MutMap} 
scala> val m1 = Map(1 -> 2)
m1: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2)  
scala> val m2 = MutMap(2 -> 3)
m2: scala.collection.mutable.Map[Int,Int] = Map(2 -> 3) 
scala> m2.remove(2); println(m2)
Map()

Another way to define package

Syntax: Packaging Classes 
package <identifier> { <class definitions> }
Để dễ hiểu hơn tôi sẽ viết lại class Config ở trên :
scala> :paste -raw
// Entering paste mode (ctrl-D to finish)
package com {
  package hoaipt {
    package utilities {
      class Config(val baseUrl: String = "http://localhost")
    }
  }
}
// Exiting paste mode, now interpreting. 
scala> val url = new com.hoaipt.utilities.Config().baseUrl
url: String = http://localhost

Privacy Access

Bạn có thể điều chỉnh privacy theo package, ví dụ giờ tôi sẽ tạo nhiều class cùng cấp với Config và set Config là private trong package utilities, ta thử xem có gì khác biệt :
scala> :paste -raw
// Entering paste mode (ctrl-D to finish)
package com. hoaipt.utilities {
  private[utilities] class Config { val url = "http://localhost" }
  class Authentication {
    private[this] val password = "Hoaipt Scala" // TODO change me
    def validate = password.size > 0
  }
  class Test { println(s"url = ${new Config().url}") }
}
// Exiting paste mode, now interpreting.
scala> val valid = new com.hoaipt.utilities.Authentication().validate
valid: Boolean = true 
scala> new com.hoaipt.utilities.Test
url = http://localhost
res0: com.hoaipt.utilities.Test = com.hoaipt.utilities.Test@49964d75
scala> new com.hoaipt.utilities.Config
<console>:13: error: class Config in package utilities cannot be accessed in package com.hoai.utilities
        new com.hoaipt.utilities.Config
                                                ^
 Điều này rất hữu ích khi bạn cần define những class cho những việc chỉ được đóng gói 1 trong package và không muốn rủi ro khi package khác gọi đến nó.

Final và Sealed Class

Đây là 2 kiểu Class hữu ích cho những trường hợp bạn cần hạn chế subclass của nó, lý do tôi để nó trong phần package vì tôi thấy nó liên quan nhiều hơn tới việc private trong package.
Ở phần trên ta đã biết cách private 1 class trong package nhưng điều này bị phá vỡ khi các lớp con được tạo không có kiểm soát. Để kiểm soát điều này ta có final và sealed class :

  • Final class : để define bạn sử dụng từ khoá final trước từ khoá class và bạn không thể tạo subclass từ final class.
  • Sealed class : đề define bạn sử dụng từ khoá sealed trước từ khoá class và class này chỉ có thể tạo subclass nếu cùng package.

Một sealded class phổ biến là Option, một trong những monadic collection mà chúng ta đã đi qua. Option vừa là abstract class vừa là sealed class ( nếu bạn đang tự hỏi abstract class là gì ), nó chỉ có 2 subclass, Some and None.Điều này đảm bảo Option luôn thực hiện đúng chức năng của nó và hành vi của Some và None không bị thay đổi.
OK, hay thử kiểm chứng lại final và sealed class trong package theo cách của bạn.Hẹn gặp lại các bạn ở hôm sau ta sẽ cùng tìm hiểu về Object, Case Class, và Trait.
Packaging in Scala

Comments