Structured vs Functional vs Object Oriented

هر کدوم از این پارادایم های برنامه نویسی شرایط خاصی دارن.

OOP یا برنامه نویسی شیء گرا. وقتی میگیرم یه برنامه OOP هست که سه پیلار زیر در کل برنامه حاکم باشه. بنابراین به ما میگه که برنامه شامل قسمت هایی هست که ما کنترل رو به صورت غیرمستقیم به کامپوننتهای مختلف داده میشه و بنابراین میشه بخش های مختلف برنامه جداگانه قابل توسعه باشه.

  1. Encapsulation
  2. Inheritance
  3. Polymorphism

وقتی میگیم یک برنامه Structured هست که از توابع قابل تست استفاده شده باشه. عملا الان هر کسی داره کد میزنه structured کد میزنه. این مفهوم زمانی شکل گرفت که با زبانهایی مثل Assembly برنامه نوشته میشد. برنامه نویسی Functional روی انتقال کنترل برنامه به صورت مستقیم تاکید داره.

Functional Programming وقتی میتونیم ادعا کنیم که برنامه مون کاملا Functional هست که تمامی متغیرها غیرقابل تغییر باشن Immutable و بنابراین مجبور میشیم از یک زبان Functional هم استفاده کنیم که ابزارهای کار با این متغیرها رو به ما میده. Functional Programming بر روی انتصاب به متغیرها محدودیت میزاره.

یعنی عملا هر کدام از این پارادایم ها یک محدودیت خاص بر روی برنامه های ما میزاره و هیچ کدوم چیزی اضافه نمیکنه.

Structured Programming

کسایی که قبلا Assembly کد زدن میدونن برنامه ها چطور بود. شما یک سری دستور پشت هم میچیدید و هر جایی لازم بود از GOTO و JUMP استفاده میکردید که برنامه بره به جای جدید و اون تیکه رو اجرا کنه. مثلا از خط 20 میگفتیم برگرد به خط 10. با این تکنیک عملا حلقه هم پیاده سازی میشد. ولی بعدا این روش منسوخ شد و توی برنامه توابع تعریف شدن. مفاهیم حلقه با کلید های جدید شکل گرفتن. به این روش جدید میگن structured programming.

Structured programming یعنی برنامه رو به بخش های مختلف و توابع مختلف تقسیم کنیم و بتونیم هر کدوم از اون توابع یا بخش ها رو تست کنیم. تست کنیم که در چه شرایطی غلط کار میکنن و بنابراین اطمینانی از برنامه داشته باشیم.

Functional programming

جمله زیر یه تعریف ریشه ای از Functional Programming هست.

variables in functional programming do not vary

یعنی اگر برنامه ای بنویسیم که تمامی متغیرهای immutable یا غیرقابل تغییر باشن میتونیم ادعا کنیم که برنامه ما از پارادایم functional استفاده میکنه. ربان هایی مثل clojure از این پاردایم استفاده میکنن.

برای اینکه متغیرها تغییر نکنن نیاز هست که برنامه شامل اجرای توابع پشت سر هم باشن. یعنی توی یک زبان functional چیزی که میبینیم یه رشته از توابع هستن که پشت سر هم اجرا میشن

Event Sroucing چیه؟

فرض کنیم یه اپلیکیشن بانکی میخایم بنویسیم که حسابهای افراد رو مدیریت کنه. برای این کار یک روش اینه که اگه طرف مثلا 1 میلیون بریزه توی حسابش اپلیکیشن بره و مقداره متغیر مانده حساب رو با 1 میلیون اضافه کنه. و هر وقت x تومن خرج میکنه این متغیر رو x واحد کم کنه. این چه مشکلی میتونه داشته باشه:

1.ممکنه race condition پیش بیاد و چند تا thread یا پروسه مقدار رو با هم تغییر بدن و در این حالت عملا حساب بانکی مشتری رو خراب کردیم

2.تاریخچه ای از تراکنش ها وجود نداره که بتونیم پرینت حساب بگیریم

بهترین روش اینه که مقدار حساب مشتری یه متغیر غیرقابل تغییر باشه 🙂 “فکر نکنم دیگه بشه بهش گفت متغیر” و تراکنش ها رو ذخیره کنیم مثلا مقدار 1 میلیون هست و وقتی x تومن خرج میکنه دیگه از اون مقدار کم نکنیم و فقط یه تراکنش -x ثبت کنیم. در این حالت دیگه حساب مشتری خراب نمیشه. تاریخچه حساب معلومه و race condition به وجود نمیاد. این روشی هست که توی اپلیکیشن های بانکی ازش استفاده میشه و بهش میگن Event Sourcing.

مشکل این روش اینه که فرض کنیم مشتری توی یک ماه گذشته به تعداد 200 هزار تراکنش داشته و الان مانده حساب میگیره. اپلیکیشن باید 200 هزار تراکنش رو باهم جمع کنه و اپلیکیشن کند میشه. برای این مشکل کافیه آخر هر شب تراکنش ها با هم جمع شن و مانده حساب نصف شب محاسبه بشه. بنابراین در هر روز فقط تراکنش ها اون روز برای محاسبه مانده حساب مهمه.

مشکل دوم اینه که دیسک زیادی نیاز هست چون باید تمامی تراکنش های تمامی کاربران نگهداری شه.

اگر منابعی مثل CPU و Disk به اندازه کافی داشته باشیم میتونیم تمامی متغیرها رو immutable کنیم و بنابراین برنامه کاملا Functional باشه. در این حالت البته توسعه سخت تر هم هست.

شیء گرایی چیه Object Oriented Programming

برنامه نویسی شیء گرا یا OOP چیه؟ Object Oriented Programming

توی مصاحه از OOP و خصوصیاتش سوال زیاد میشه. از طرفی لازمه که یه برنامه نویس این رو بدونه. میشه گفت:

به ترکیت دیتا و توابع با هم میگیم OOP. این تعریف خیلی ناقصه. از طرفی به زبان ساده تر میشه گفت “یک روش برنامه نویسی که بشه باهاش دنیای واقعی رو مدل کرد” این هم باز کلیه و اگه بخایم بیشتر توضیح بدیم:

سه تا تکنیک توی برنامه نویسی وجود داره:

  • Encapsulation یا کپسوله کردن یا بسته بندی کردن
  • Inheritance یا ازثبری
  • Polymorphism یا چند شکلی

این 3 تکنیک برنامه نویسی رو اگر در برنامه های خودمون رعایت کنیم، میگیم داریم با تکنیک OOP کد میزنیم. و توی زبانهای OOP مثل Java C# Python و … این تکنیک ها رعایت میشه بخاطر همین بهشون میگیم زبانهای شیء گرا (البته پایتون مجبورتون نمیکنه صد درصد OOP کد بزنید).

آیا این تکنیک ها با پیدایش زبان های OO اومدن؟ خیر خیلی از برنامه نویسان C از قدیم از این تکنیک ها استفاده میکردند اما مجبور نبودند با این تکنیک کد بزنن و چون اینا تکنیک های خوبی بودن بعدا با زبان C++ رسمی شد و بمرور در زبان های مختلف تکامل پیدا کرد و استفاده ازشون آسون تر و بهینه تر شد و حتی اجباری شد.

Encapsulation

فرض کنید که بشه دور دسته ای از داده ها و توابع یک خط کشید و از بیرون این خط به این داده ها نشه دسترسی داشت و فقط خود اون توابع بتونن این داده ها رو تغییر بدن. در واقع داده ها و توابع private باشن و فقط به روش هایی استاندارد بشه بهشون به صورت غیر مستقیم یا بعضی از اونا مستقیم دسترسی داشت. این کار با مفهوم class انجام میشه و به این تکنیک میگن Encapsulation یا کپسوله کردن.

Inheritance

ارثبری ساده است. فرض کنید بخشی از توابع یا داده های یک کلاس رو دوباره از اول تعریف کنیم و بقیه بخش ها رو بدون تغییر کپی کنیم و استفاده کنیم به این میگن ارثبری.

مثال بالا خیلی خوب ارثبری رو نشون میده. یک کلاس Animal داریم. طبیعتا همه حیوانات یه سری خصوصیات مشترک دارن مثلا پا دارن حرکت میکنن و صدا دارن. دو کلاس Duck و Lion از Animal ارث بردن چون اینها هم حیوان هستن و این خصوصیات رو دارن و اونا رو به ارث بردن اما این خصوصیات مشترکشون کمی فرق داره اردک شنا میکنه و شیر میدوه، صدای هر حیوان هم فرق داره پس همینطور که میبینید این خصوصیات دوباره تعریف شدن با این که به ارث هم بردن.

Polymorphism

برای توضیح چندشکلی یه مثال واقعی میزنیم. میدونیم توی UNIX برای خوندن و نوشتن داده device های مختلفی وجود داره که به دیوایس های ورودی میگیم STDIN و به دیوایس های خروجی میگیم STDOUT. دیوایس خروجی مثل صفحه مانتور و اسپیکر و دیوایس ورودی مثل کیبورد و ماوس. حالا یه تابع نوشتیم با اسم کپی که از یه دیوایس ورودی میخونه و کپی میکنه تو دیوایس خروجی

ولی توی این کد ما مشخص نکردیم کدوم دیوایس ورودی و خروجی. UNIX این مشکل رو حل کرده میگه شما هر دیوایس که میسازی و می نویسی توی دایورش باید 5 تا تابع تغریف کنی که برنامه های مختلف بتونن باهاش کار کنن. این توابع open close read write seek هستن. یعنی همه دیوایس ها توی درایورشون این توابع رو دارن. و باید تعریف اونا رو هم بنویسید مثلا چجوری بخونی و بنویسی توش چه جوری باز کنی و ببندیش بنابراین مثلا یک فایل باید این توابع رو اینجوری تعریف کنن براش:

و توی اون درایور باید همین توابع رو تعریف کنه و اون رو لود کنه چیزی مثل زیر (یعنی در واقع قابل استفاده اش کنه)

حالا اون تابع copy میتونه ازش استفاده کنه اگر تابع getchar اینجوری تعریف شده باشه

ببینید اینجوری تابع copy نیاز به تغییر نداره فقط getchar رو صدا میزنه و این getchar هم همون تابع read رو صدا میزنه که توی درایور هست.

بنابراین polymorhpism این قابلیت رو به برنامه ها میده که مثل پلاگین عمل کنن. یعنی اگر صد تا دیوایس اضافه بشه با صد تا درایور دیگه نیازی به تغییر copy نیست چون بهرحال اون درایور تابع read داره.

plymophism اینه یعنی این تابع copy صد شکل از دیوایس رو پوشش میده پس بهش میگیم تکنیک چند شکلی.

قدرت polymorphism در همینه که بخش های برنامه از هم جدا میشن و میتونی هر کدوم رو جدا توسعه بدی و تکامل بدی بخش های محتلف برنامه مثل پلاگین با هم کار میکنن توسعه هرکدام مستقل ادامه پیدا میکنه.

Signal و Middleware توی پروسه لاگین (جلوگیری از چندین لاگین همزمان با یک نام کاربری)

توی یه پروژه خواستیم یک نام کاربری دو تا session نداشته باشه یعنی یه کاربر توی دوتا مرورگر یا دو تا سیستم لاگین نکنه. برای این کار از Middleware و Signal توی پروژه استفاده کردم.

توی Middleware.py توی app مربط کد زیر کد زیر رو زدم:

حالا اینو توی MIDDLEWARE توی SETTING اضافه میکنیم.

Signal زیر رو اضافه میکنم که اگر کسی logtout کرد session اش پاک شه که بشه جای دیگه لاگین کنن:

خروجی کد مصاحبه

خروجی کد زیر چیه؟

طبق حدثم:

ولی else چی میگه

خروجی:

یعنی اگه Exception اتفاق نیوفته else هم اتفاق میوفته. در هر حالتی finally اتفاق میوفته

سوال مصاحبه ارثبری

امروز توی یه مصاحبه سوال زیر مطرح شد و گفتن که خروجی برنامه چیه و من بعدا که تستش کردم سورپرایز شدم.

خروجی کد از قرار زیره:

بله سورپرایز شدیم. حالا برای اینکه بفهمیم چرا اینطور شد کد زیر رو اجرا میکنیم:

و خروجی کد:

خوب مشخص شد مساله چیه. توی ارثبری کلاس ها توی پایتون حتی اگه هیچی هم تغییر نکنه کلاس جدید به جای جدیدی از حافظه اشاره میکنه. در واقع کلاس پدر کپی میشه توی جای جدیدی از حافظه و کلاس فرزند یه pointer به جای جدیده. و در زمان اجرا کلاس هر تغییری هم بکنه به جای جدیدی نمیره که بنابراین آدرس حافظه کلاس در هیچ شرایطی تغییر نمیکنه.

اما متغیری که توی کلاس تعریف شده داستانش فرق میکنه. توی کلاس A پدر متغیر val به یه آدرس توی حافظه اشاره میکنه. کلاس B و C که فرزند هستن به همون آدرس حافظه اشاره مکنه. حالا وقتی B.val رو تغییر میدیم (از اونجایی که integer توی پایتون immutable هست) اون خونه حافظه تغییر نمیکنه بلکه یک خونه جدید با مقدار جدید ساخته میشه و B.val به اون اشاره میکنه. حالا باید خونه قبلی حافظه رو پاک کنه اما چون 2 تا pointer دیگه بهش وصله اونو پاک نمیکنه (Garbage collector وقتی اون خونه حافظه رو پاک میکنه که هیچ pointer ای به اون اشاره نکنه)

حالا داستان دوم. با این منطق اگر A.val رو تغییر بدیم باید C.val تغییر نکنه چون A.val به آدرس جدید اشاره میکنه ولی خانه قبلی پاک نمیشه چون هنوز C.val به خانه قبلی اشاره میکنه. اما مساله دیگه ای هم هست این جا از ارثبری استفاده کردیم و C فرزند A هست پس حالا که A.val به خانه جدیدی از حافظه اشاره میکنه این pointer رو توی فرزندانش هم عوض میکنه و بازم C.val و A.val به یک خانه جدید یکسان اشاره میکنن. ولی B.val دیگه این اتفاق براش نمیوفته چون از قبل به خونه دیگه ای اشاره میکنه (تغییر کرده) اگر پایتون این رو جوری دیگه پیاده سازی میکرد قطعا به مشکل میخوردیم.

واقعا سوال باحالی بود.

افزودن فیلد به مدل با کمترین impact

یه مدل داریم تو جنگو که پروفایل شرکت های مختلف توی اون ذخیره میشه:

لازم شد که لوگو شرکت هم اضافه بشه اما از اونجایی که این مدل جاهای مختلف استفاده شده بود و توی فرمها و view های مختلف ازش استفاده شده اگه یه فیلد برای لوگو بهش اضافه کنیم قطعا باید خیلی از جاهای کد هم تغییر بدیم و احتمالا باگ هایی تولید بشن و از اونجایی که این سیستم براش تست نوشته نشده ممکن اطفاقات بدی بیوفته.

بنابراین رفتم دنبال اینکه چطور میشه بدون تغییر این مدل لوگو رو اضافه کنم. حداقل طوری که اگر جایی از این مدل داره استفاده میشه دیگه فیلدهاش عوض نشه. راه های مختلفی وجود داره ولی بهترینش بنظرم اینه که یه مدل جدید برای لوگو بسازیم و ارتباط OneToOne بینشون برقرار کنیم.

و اینطوری ازش استفاده کنم