I. Mở đầu
- Sealed Classes được sử dụng để biểu diễn các hệ thống phân cấp class bị hạn chế, khi một giá trị có thể có một trong các loại từ một tập hợp giới hạn, nhưng không thể có bất kỳ loại nào khác. Về mặt nào đó, chúng là một phần mở rộng của các Class Enum: tập hợp các giá trị cho một kiểu Enum cũng bị hạn chế, nhưng mỗi hằng số Enum chỉ tồn tại dưới dạng một single instance, trong khi một lớp con của một Sealed Class có thể có nhiều instance có thể chứa state.
II. Các quy tắc của Sealed Class
- Sealed classes là abstract và không thể có các abstract member.
- Sealed classes không thể khởi tạo trực tiếp.
- Sealed classes không thể có public constructor (Constructor mặc định là private).
- Sealed classes có thể có các subclass, nhưng các subclass đó phải nằm trong cùng 1 file hoặc trong sealed classes được địng nghĩa.
III. Khai báo Sealed Class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <span class="token comment">// Nested</span> sealed <span class="token keyword">class</span> <span class="token class-name">Fruit</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">class</span> <span class="token class-name">Apple</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token function">Fruit</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">class</span> <span class="token class-name">Orange</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token function">Fruit</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> sealed <span class="token keyword">class</span> <span class="token class-name">Fruit</span> <span class="token comment">// Not nested</span> <span class="token keyword">class</span> <span class="token class-name">Apple</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token function">Fruit</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">class</span> <span class="token class-name">Orange</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token function">Fruit</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// Fruits.kt</span> sealed <span class="token keyword">class</span> <span class="token class-name">Fruit</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">class</span> <span class="token class-name">Apple</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token function">Fruit</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">class</span> <span class="token class-name">Orange</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token function">Fruit</span><span class="token punctuation">(</span><span class="token punctuation">)</span> open <span class="token keyword">class</span> <span class="token class-name">UnknownFruit</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token function">Fruit</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token comment">// SomeOtherFile.kt</span> <span class="token keyword">class</span> <span class="token class-name">Grape</span> <span class="token operator">:</span> <span class="token function">Fruit</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// Not Acceptable</span> <span class="token keyword">class</span> <span class="token class-name">Tomato</span> <span class="token operator">:</span> <span class="token function">UnknownFruit</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// Acceptable</span> |
- Một trong những lợi ích quan trọng khi bạn sử dụng Sealed Class đó là khi bạn sử dụng chúng với When Expression. Nó có thể xác thực tất cả các trường hợp mà không cần phải gọi đến Else.
1 2 3 4 5 6 7 | <span class="token comment">// Invalid</span> <span class="token keyword">override</span> fun <span class="token function">getItemViewType</span><span class="token punctuation">(</span>position<span class="token punctuation">:</span> <span class="token builtin">Int</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token builtin">Int</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> when <span class="token punctuation">(</span>fruits<span class="token punctuation">[</span>position<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">is</span> <span class="token builtin">Fruit</span><span class="token punctuation">.</span><span class="token builtin">Apple</span> <span class="token operator">-</span><span class="token operator">></span> R<span class="token punctuation">.</span>layout<span class="token punctuation">.</span>item_apple <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- Đoạn code trên sẽ lỗi vì ta cần phải implement tất cả các type. Lỗi sẽ trả ra là:
When expression must be exhaustive, add necessary 'is Orange' branch or else branch instead
- Vì vậy chúng ta phải implement tất cả các type của Fruit
1 2 3 4 5 6 7 | override fun getItemViewType(position: Int): Int { return when (fruits[position]) { is Fruit.Apple -> R.layout.item_apple is Fruit.Orange -> R.layout.item_orange } } |
IV. Áp dụng Sealed Class thực tế trong Android
- Có rất nhiều cách để áp dụng Sealed Class trong Android Code, dưới đây tôi sẽ chỉ cho bạn cách sử dụng chúng trong Recyclerview.
- Chúng ta sẽ hiển thị list các Fruits trong RecyclerView tương ứng với mỗi ViewHolder.
- Apple có AppleViewHolder, Orange có OrangeViewHolder. Nhưng chúng ta sẽ muốn chắc chắn rằng mỗi Fruit mới được thêm vào sẽ có 1 ViewHolder tương ứng hoặc ko thì sẽ thrown Exception. Hãy xem đoạn code dưới đây
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | <span class="token keyword">sealed</span> <span class="token keyword">class</span> Fruit <span class="token punctuation">{</span> <span class="token keyword">class</span> Apple <span class="token operator">:</span> Fruit<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">class</span> Orange <span class="token operator">:</span> Fruit<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">class</span> FruitAdapter <span class="token operator">:</span> RecyclerView<span class="token punctuation">.</span>Adapter<span class="token generics function"><span class="token punctuation"><</span>RecyclerView<span class="token punctuation">.</span>ViewHolder<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">val</span> fruits <span class="token operator">=</span> arrayListOf<span class="token generics function"><span class="token punctuation"><</span>Fruit<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">override</span> fun getItemCount<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> fruits<span class="token punctuation">.</span>size <span class="token keyword">override</span> fun getItemViewType<span class="token punctuation">(</span>position<span class="token operator">:</span> <span class="token builtin">Int</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Int</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> when <span class="token punctuation">(</span>fruits<span class="token punctuation">[</span>position<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> is Fruit<span class="token punctuation">.</span>Apple <span class="token operator">-</span><span class="token operator">></span> R<span class="token punctuation">.</span>layout<span class="token punctuation">.</span>item_apple is Fruit<span class="token punctuation">.</span>Orange <span class="token operator">-</span><span class="token operator">></span> R<span class="token punctuation">.</span>layout<span class="token punctuation">.</span>item_orange <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">override</span> fun onCreateViewHolder<span class="token punctuation">(</span>parent<span class="token operator">:</span> ViewGroup<span class="token punctuation">,</span> viewType<span class="token operator">:</span> <span class="token builtin">Int</span><span class="token punctuation">)</span><span class="token operator">:</span> RecyclerView<span class="token punctuation">.</span>ViewHolder <span class="token punctuation">{</span> <span class="token keyword">val</span> layoutInflater <span class="token operator">=</span> LayoutInflater<span class="token punctuation">.</span>from<span class="token punctuation">(</span>parent<span class="token punctuation">.</span>context<span class="token punctuation">)</span> <span class="token keyword">return</span> when <span class="token punctuation">(</span>viewType<span class="token punctuation">)</span> <span class="token punctuation">{</span> R<span class="token punctuation">.</span>layout<span class="token punctuation">.</span>item_apple <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">val</span> itemView <span class="token operator">=</span> layoutInflater<span class="token punctuation">.</span>inflate<span class="token punctuation">(</span>R<span class="token punctuation">.</span>layout<span class="token punctuation">.</span>item_apple<span class="token punctuation">,</span> parent<span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span> AppleViewHolder<span class="token punctuation">(</span>itemView<span class="token punctuation">)</span> <span class="token punctuation">}</span> R<span class="token punctuation">.</span>layout<span class="token punctuation">.</span>item_orange <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">val</span> itemView <span class="token operator">=</span> layoutInflater<span class="token punctuation">.</span>inflate<span class="token punctuation">(</span>R<span class="token punctuation">.</span>layout<span class="token punctuation">.</span>item_orange<span class="token punctuation">,</span> parent<span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span> OrangeViewHolder<span class="token punctuation">(</span>itemView<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token keyword">throw</span> UnknownViewTypeException<span class="token punctuation">(</span><span class="token string">"Unknown view type $viewType"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">override</span> fun onBindViewHolder<span class="token punctuation">(</span>holder<span class="token operator">:</span> RecyclerView<span class="token punctuation">.</span>ViewHolder<span class="token punctuation">,</span> position<span class="token operator">:</span> <span class="token builtin">Int</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">val</span> fruit <span class="token operator">=</span> fruits<span class="token punctuation">[</span>position<span class="token punctuation">]</span> when <span class="token punctuation">(</span>fruit<span class="token punctuation">)</span> <span class="token punctuation">{</span> is Fruit<span class="token punctuation">.</span>Apple <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">(</span>holder as AppleViewHolder<span class="token punctuation">)</span><span class="token punctuation">.</span>bind<span class="token punctuation">(</span>fruit<span class="token punctuation">)</span> is Fruit<span class="token punctuation">.</span>Orange <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">(</span>holder as OrangeViewHolder<span class="token punctuation">)</span><span class="token punctuation">.</span>bind<span class="token punctuation">(</span>fruit<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment">// ... other methods</span> <span class="token punctuation">}</span> |