در این پست به بررسی کلاسهای Enum و Sealed و در انتها تفاوت این دو با یکدیگر میپردازیم. شما هم احتمالا در هنگام کد نویسی به این نکته برخورد کردهاید که گاهی اوقات نیاز است مقادیر ثابت و مشخصی را داخل کد تبادل کنید و با دو ساختاری که در Kotlin وجود دارد میتوانید پیادهسازی مناسبتری داشته باشید.
دوستانی که مقالات قبلی من رو دنبال کردن میدونن که هدف من مفهومیترین و آسانترین روش برای یادگیری برنامهنویسی هستش، در صورت تمایل حتما من رو دنبال کنید. هر لایک شما برای این پست دلگرمی بیشتر برای ارائه مطالب بهتر میباشد ;)
اگر بخواهیم چهار جهت جغرافیایی را در کد خود ذخیره کنیم و آنها را به فانکشنها پاس دهیم چه راهی را انتخاب میکنید؟
ذخیره به عنوان String را انتخاب میکنید؟
object Direction { val NORTH = "north" val SOUTH = "south" val EAST = "east" val WEST = "west" } fun move(direction: String) = when (direction) { Direction.NORTH -> {} Direction.SOUTH -> {} Direction.EAST -> {} Direction.WEST -> {} else -> {} }
این کد در نگاه اول هیچ مشکلی اجرایی یا منطقی ندارد، ولی چندین دلیل میتوان ارائه داد که احتمال خطای کدنویسی و عدم کارکرد دلخواه ایجاد خواهد شد.
برای حل این چالش ما میتوانیم از ساختار Enum استفاده کنیم:
enum class Direction { NORTH, SOUTH, EAST, WEST; } fun move(direction: Direction) = when (direction) { Direction.NORTH -> {} Direction.SOUTH -> {} Direction.EAST -> {} Direction.WEST -> {} }
به هر مقدار درون Enum یک Constant (ثابت) میگویند.
کلمه Enum مخفف Enumeration به معنای شمارش میباشد.
حالا که دلیل استفاده از Enum را میدانیم، کافی است مفهوم و چند تکنیک را که میتوانیم با آنها پیادهسازی شود بررسی کنیم.
هر مقداری که در داخل یک Enum تعریف میشود (مثلا NORTH) یک object که از آن کلاس Enum ارثبری کرده محسوب میشود.
در کاتلین ساختار object باعث میشود که تنها یک نمونه (instance) از آن کلاس داشته باشیم. در واقع میتوانیم بگوییم که ساختار object در کاتلین ترکیبی از Singleton و Static جاوا میباشد.
سینگلتون (Singleton) یکی از الگوهای طراحی (Design Pattern) های پر استفاده میباشد که در آن تنها و تنها یک نمونه (شی) از آن کلاس ساخته میشود.
میتوانید یک Enum را ساختاری برابر با یک کلاس شامل object در نظر بگیرید:
enum class Direction { NORTH, SOUTH, EAST, WEST; } abstract class Direction { object NORTH : Direction() object SOUTH : Direction() object EAST : Direction() object WEST : Direction() }
enum class PaymentMethod(val balance: Double) { CASH(147.50) { override fun printFormattedAmount(): String { return NumberFormat.getCurrencyInstance(Locale.US).format(balance) } }, CREDIT(15.00) { override fun printFormattedAmount(): String { return "$balance" } }; abstract fun printFormattedAmount(): String }
fun main() { println("+-------------------+") PaymentMethod.values().forEach { medthod -> println("Payment Method: ${medthod.name} - - - Balance: ${medthod.balance}") } } enum class PaymentMethod(val balance: Double) { CASH(147.50), CREDIT(15.00) }
fun main() { val creditPayment = PaymentMethod.valueOf("CREDIT") println(paymentMethod.balance) enumValues<PaymentMethod>().forEach { method -> println(method) } val cashPayment: PaymentMethod = enumValueOf<PaymentMethod>("CASH") println(paymentMethod.name) } enum class PaymentMethod(val balance: Double) { CASH(147.50), CREDIT(15.00) }
میتوان Sealed Class ها را Enum های پیشرفته معرفی کرد. به دلیل object بودن هر مقدار (Constant) داخل Enum نمیتوانید چندین نمونه از آنها بگیرید. فرض کنید دو کاربر داریم که برای هر کدام میخواهیم یک روش پرداخت با موجودی متفاوت بسازیم:
fun main() { val user1 = PaymentMethod.CASH val user2 = PaymentMethod.CASH user1.balance = 2_000.00 println("+-----------------------------------+") println(user1.balance) //Prints 2000.0 println(user2.balance) //Also prints 2000.0 } enum class PaymentMethod(var balance: Double) { CASH(147.50), CREDIT(15.00) }
برای حل این مشکل از Sealed Class ها استفاده میکنیم که دارای ویژگیهای زیر میباشند:
کلاس abstract کلاسی است که نمیتوان از آن شی (Object) ساخت و به همین دلیل کلاسی که از آن ارث بری میکند و فانکشنها و پراپرتیهای abstract (بدون بدنه) آن را پیاده سازی میکند Concrete گفته میشود.
fun main() { val user1 = PaymentMethod.CASH(2_000.0) val user2 = PaymentMethod.CASH(9_000.0) println("+---------------------+") println(user1.amount) println(user2.amount) } sealed class PaymentMethod(var amount: Double) { class CASH(cashAmount: Double) : PaymentMethod(cashAmount) class CREDIT(credit: Double) : PaymentMethod(credit) data class Crypto(val name: String, val balance: Double) : PaymentMethod(balance) //object Gift : PaymentMethod(0.0) }