-
Enum
là một trong nhữngAPI
mạnh mẽ được yêu thích nhất trongSwift
. Thực tế là trongSwift
thìEnum
được phát triển cẩn thận để người dùng có thể sử dụng trong nhiều trường hợp với nhiềutype
khác nhau. -
Tuy nhiên vẫn có một số loại trường hợp chúng ta cần tránh khi sử dụng
Enum
vì rất có thể chúng ta sẽ tự làm chúng ta trở nên ngớ ngẩn cũng như việccode
trở nên khó đọc khó hnhau
1/ Liệt kê thiếu trường case
:
- Lấy ví dụ cụ thể khi làm việc với app
Podcast
với các danh mục khác nhau và haicase
đặc biệt sử dụng cho toàn bộpodcast
hoặc không sử dụng cho bất kỳ mộtpodcast
nào:
extension Podcast {
enum Category: String, Codable {
case none
case all
case entertainment
case technology
case news
...
}
}
- Sau đó chúng ta sẽ triển khai một
filter
để so sánhstring value
người dùng đã chọn trong quá trình sử dụng appPodcast
:
extension Podcast {
func matches(filter: Filter) -> Bool {
switch filter.category {
case .all, category:
return name.contains(filter.string)
default:
return false
}
}
}
- Mới nhìn thì triển khai trên trông có vẻ ổn. Tuy nhiên, nếu chúng ta dừng lại suy nghĩ kỹ 1 tẹo thì bản thân trong `Swift` cũng có một tính năng giải quyết cho trường hợp `value` của `variable` có thể có hoặc không, đó là `optronglđã
- Vì vậy chúng ta sẽ sử dụng `optional` cho `category` trong `struct` `Podcast` để tận dụng tất cả các tính năng mà `Swift` đã hỗ trợ xử lý các giá trị `optional` (`if let`) :
```swift
struct Podcast {
var name: String
var category: Category?
...
}
- Sử dụng
switch
ở đây là một điều thú vị khi chúng ta có thể áp dụngoptional
Category
ở bên trên màfunction
hoàn toàn không thay đổi cơ chế hoạt động. Cụ thể ở đây chúng ta sử dụngcase none
để liệt kê cáccase
chúng ta đãlack
:
func title(forCategory category: Podcast.Category?) -> String {
switch category {
case .none:
return "Uncategorized"
case .all:
return "All"
case .entertainment:
return "Entertainment"
case .technology:
return "Technology"
case .news:
return "News"
...
}
}
2/ Sử dụng tên cụ thể cho từng case:
-
Trên thực tế thì một
podcast
không thể thuộc tất cả cáccategory
nên trường hợpall
chỉ phát huy tác dụng khi sử dụng cùngfilter
. -
Vì vậy, thay vì bao gồm trường hợp đó trong danh mục Danh mục chính của chúng tôi, thay vào đó, hãy tạo một loại chuyên dụng cụ thể cho miền lọc. Bằng cách đó, chúng tôi có thể phân tách các mối quan tâm khá gọn gàng và vì chúng tôi đang sử dụng các loại lồng nhau, chúng tôi có thể đặt địa chỉ mới của mình sử dụng cùng tên Danh mục, chỉ lần này nó sẽ được lồng trong mô hình Bộ lọc của chúng tôi - như thế này:
-
Thay vì chỉ sử dụng các
case
trongenum
Category
chúng ta sẽ tạo mộtenum
riêng để sử dụng riêng choFilter
. Bằng cách này chúng ta có thể tách biệt rõ ràng các danh mục khi chúng ta bắt đầu sử dụngnested-type
như sau:
extension Filter {
enum Category {
case any
case uncategorized
case specific(Podcast.Category)
}
}
- Bằng cách sử dụng
where
trongswitch-case
chúng ta đã triển khai đượclogic filter
một cách cụ thể và ngắn gọn dễ hiểu hơn:
extension Podcast {
func matches(filter: Filter) -> Bool {
switch filter.category {
case .any where category != nil,
.uncategorized where category == nil,
.specific(category):
return name.contains(filter.string)
default:
return false
}
một
}
- Giờ đây chúng ta có thể tiếp tGc với việc xóa tất cả các
case
khỏi danh sáchPodcast.Category
để có một danh sách đơn giản hơn nhiều về từng danh mục:
extension Podcast {
enum Category: String, Codable {
case entertainment
case technology
case news
...
}
}
3/ Các trường hợp với các custom type
:
-
Enum
Podcast.Category
cần được lưu ý về khả năng có thể mở rộng tron tương lai khi chúng ta bổ sung cáccase
. Để làm được điều này chúng ta có thể xem việc tạo mộtcase
tuỳ chình vớitype
cụ thể. -
Như vây thì
case
cần cóassociated-value
như việc chúng ta córawValue
dưới dạngString
để đại diện chocategory
custom
:
extension Podcast {
enum Category: Codable {
case all
case entertainment
case technology
case news
...
case custom(String)
}
}
-
Trong khi
associated-value
có thể hữu ích trong một số bối cảnh thì ở bối cảnh này nó lại không phát huy được sức mạnh vì trong tương lai với cáccustom-type
đặc biệt chúng ta sẽ cần có thể cácmethod
đểdecode
hoặcencode
các value pyhù hợp vớirawValue
. -
Vì vậy chúng ta cần khám phá một hướng tiếp cận khác như chuyển đổi
enum
Categor
thànhRawRepresentable
. Chúng ta sẽ sử dụng tính năng có sẵn củaSwift
để thực hiện việcencode
/decode
khi làm việc vớistring value
củarawValue
.
extension Podcast {
struct Category: RawRepresentable, Codable, Hashable {
var rawValue: String
}
}
- Chúng ta hiện tại có thể tự do khởi tạo
Category
từ bất kỳcustom string
mà chúng ta muốn để có thể phù hợp với các tính năng trong tương lai. Tuy nhiên để đảm bảo tính năng mới có thể tương thích ngược lại thì chúng ta cần sử dụng cácstatic
cho cáctype
mới:
extension Podcast.Category {
static var entertainment: Self {
Self(rawValue: "entertainment")
}
static var technology: Self {
Self(rawValue: "technology")
}
static var news: Self {
Self(rawValue: "news")
}
...
static func custom(_ id: String) -> Self {
Self(rawValue: id)
}
}
- Chúng ta có
function
có têntitie
trả vềString
ứng với từngcase
củaCategory
khi sử dụngswitch
method
. Tuy nhiên với những thay đổi bên trên chúng ta cần có điều chỉnh thích hợp để trả về các giá trị chotitle
thích hợp. Chúng ta sẽ di chuyển các giá trịstring
đó sangLocalizable.string
và thực hiện các xử lý cần thiết trước khi trả về giá trịString
cuối cùng chotitle
:
func title(forCategory category: Podcast.Category?) -> String {
guard let id = category?.rawValue else {
return NSLocalizedString("category-uncategorized", comment: "")
}
let key = "category-\(id)"
let string = NSLocalizedString(key, comment: "")
// Handling unknown cases by returning a capitalized version
// of their key as a fallback title:
guard string != key else {
return key.capitalized
}
return string
}
4/ Đặt tên tự động cho static property
:
function
- Đây thực chất là một ịnh mà chúng ta cần bổ sung thêm khi một nhược điểm của phương pháp dựa trên
code-base
ở trên là giờ đây chúng ta phải thủ công xác định cácrawValue
cho mỗistatic property
song đó là điều mà chúng ta có thể dễ dàng giải quyết với việc sử dụngkeyword
#function
để tự động thay thế tên củafunction
(hoặcproperty
) như sau:
extension Podcast.Category {
static func autoNamed(_ rawValue: StaticString = #function) -> Self {
Self(rawValue: "\(rawValue)")
}
ion
- Với
extension
ở trên thì chỉ cần gọi đếnautoNamed()
tronvàmỗiAPI
category
được tích hợp sẵn vàSwift
sẽ tự động điền cácrawValue
đó cho chúng ta:
extension Podcast.Category {
static var entertainment: Self { autoNamed() }
static var technology: Self { autoNamed() }
static var news: Self { autoNamed() }
...
static func custom(_ id: String) -> Self {
Self(rawValue: id)
}
}