امید فرجی
امید فرجی
خواندن ۴ دقیقه·۵ سال پیش

مفهوم In و Out در کاتلین

اگر شما کد Generic در کاتلین نوشته باشید، احتمالا برایتان پیش آمده است که IDE به شما پیشنهاد کند هنگام نوشتن کد های Generic از in یا out استفاده کنید. وقتی اولین با با این موضوع آشنا شدم برای من سوال بود که in و out به چه دردی میخورد. اگر برای شما هم سوال است ادامه مقاله را بخوانید.

درواقع این یک راه برای تعریف contravariance و covariant است. خود من زمان زیادی را برای درک این دو مفهوم صرف کرده ام. در این جا قصد دارم مفهوم این دو و تفاوتشان را برای شما توضیح دهم.

یادگیری راحت in و out

نوع Out یا covariant

اگر کلاس شما از نوع Generic فقط برای مقدار خروجی فانکشن های خود استفاده کند، می توانید از out استفاده کنید:

interface Production<out T> { fun produce(): T }

من اسم این interface را Production گذاشتم چون کارش تولید نوع Generic می باشد. عبارت زیر به شما کمک می کند تعریف out را راحت تر به خاطر بسپارید:

produce = output = out


نوع In یا contravariance

اگر کلاس شما از نوع Generic فقط برای مقدار ورودی فانکشن های خود استفاده کند، می توانید از in استفاده کنید:

interface Consumer<in T> { fun consume(item: T) }

من اسم این interface را Consumer گذاشتم چون کارش مصرف نوع Generic می باشد. عبارت زیر به شما کمک می کند تعریف in را راحت تر به خاطر بسپارید:

consume = input = in


نوع ثابت یا Invariant

اگر کلاس شما از نوع Generic هم برای مقدار ورودی و هم برای مقدار خروجی فانکشن های خود استفاده کند، نباید از هیچ کدام از انواع in یا out استفاده کنید:

interface ProductionConsumer<T> { fun produce(): T fun consume(item: T) }


این ها چه فایده ای دارند؟

خب. حالا شما می دانید که in و out کجای کد نوشته می شوند. اما مسئله مهمتر این است که در نهایت این موارد به چه دردی میخورد؟

قبل از پاسخ به این سوال اجازه بدهید کلاسی به نام Burger تعریف کنیم که از نوع FastFood است و FastFood از نوع Food می باشد.


سلسله مراتب این کلاس ها به شکل زیر می باشد:

Burger سلسله مراتب
Burger سلسله مراتب


open class Food open class FastFood : Food() class Burger : FastFood()



تولید کننده Burger

اینترفیس جنریک Production را در بالا تعریف کردیم، حالا بیاید برای تولید food و fastfood و burger آن را به شکل زیر توسعه بدهیم:

class FoodStore : Production<Food> { override fun produce(): Food { println(&quotProduce food&quot) return Food() } } class FastFoodStore : Production<FastFood> { override fun produce(): FastFood { println(&quotProduce fast food&quot) return FastFood() } } class InOutBurger : Production<Burger> { override fun produce(): Burger { println(&quotProduce burger&quot) return Burger() } }


حالا بیاید یک سری متغییر برای نگهداری نمونه هایی از این Food Consumer ها تعریف کنیمم

val production1: Production<Food> = FoodStore() // تولید کننده Food val production2: Production<Food> = FastFoodStore() // تولید کننده FastFood val production3: Production<Food> = InOutBurger() // تولید کننده Burger

هم تولید کننده burger و هم تولید کننده fastFood ، هر دو هنوز تولید کننده Food هستند. از این رو:

برای جنریک out ما می توانیم کلاسی با نوع جنریک Sub-type را در کلاسی با نوع جنریک Super-type قرار دهیم.

اگر کد بالا را به شکل زیر تغییر دهیم، خطا به وجود می آید، چون food و fastFood فقط یک تولید کننده ی burger نیستند.

val production1 : Production<Burger> = FoodStore() // Error val production2 : Production<Burger> = FastFoodStore() // Error val production3 : Production<Burger> = InOutBurger()



مصرف کننده Burger

اینترفیس جنریک Consumer را در بالا تعریف کردیم، حالا بیاید برای مصرف کردن food و fastfood و burger آن را به شکل زیر توسعه بدهیم:

class Everybody : Consumer<Food> { override fun consume(item: Food) { println(&quotEat food&quot) } } class ModernPeople : Consumer<FastFood> { override fun consume(item: FastFood) { println(&quotEat fast food&quot) } } class American : Consumer<Burger> { override fun consume(item: Burger) { println(&quotEat burger&quot) } }


حالا بیایید یک سری متغییر برای نگهداری نمونه هایی از این Food Consumer ها تعریف کنیم

val consumer1: Consumer<Burger> = Everybody() val consumer2: Consumer<Burger> = ModernPeople() val consumer3: Consumer<Burger> = American()

در اینجا یکی از مصرف کننده های burger کلاس American است که خود جزئی از ModernPeople و آن هم جزئی Everybody است، از این رو:

برای جنریک in ما می توانیم کلاسی با نوع جنریک Super-type را در کلاسی با نوع جنریک Sub-type قرار دهیم.

اگر کد بالا را به شکل زیر تغییر دهیم، خطا به وجود می آید، چون مصرف کننده food هم می تواند American باشد هم ModernPeople و نه فقط American یا فقط ModernPeople

val consumer1 : Consumer<Food> = Everybody() val consumer2 : Consumer<Food> = ModernPeople() // Error val consumer3 : Consumer<Food> = American() // Error


راه دیگر برای به خاطر سپردن in و out

با توجه به مطاب بالا، یک راه دیگر برای درک کردن تفاوت این دو این است:

وقتی که می خواهید Super-type به Sub-type اساین شود از in استفاده کنید
وقتی که می خواهید Sub-type به Super-type اساین شود از out استفاده کنید



امیدوارم این مقاله برای شما مفید بوده باشد و آن را با دیگران به اشتراک بگذارید.

لطفا مرا در ویرگول دنبال کنید و نظرات خود را با من در میان بگذارید.

kotlinandroidAndroid App DevelopmentAndroidDevMobile App Development
برنامه نویس جاوا، کاتلین و اندروید
شاید از این پست‌ها خوشتان بیاید