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

Go یک زبان رویه‌ای است (Procedural)

سوالی که برای تعداد زیادی از برنامه نویسان مشتاق پیش می‌آید این است که آیا Go یک زبان شیءگراست؟ جواب این است که خوشبختانه خیر! حداقل نه به شکلی که در زبان‌های شیءگرا با آن آشنا هستید.

باید توجه داشته باشید که شیءگرایی یک مفهوم است نه یک قابلیت. اینطور نیست که در زبانی باشد و در زبان دیگری وجود نداشته باشد. برای مثال در زبانی مثل C که شیءگرا نیست هم می‌توان از مفاهیم شیءگرایی استفاده کرد. حتی در یک زبان Functional مانند Lisp هم می‌توان از شیءگرایی بهره برد.

البته مسلم است که اگر یک زبان دارای گرامر خاصی برای این منظور باشد پیاده سازی کدهای شیءگرا در آن آسان تر خواهد بود. مانند همان چیزی که در Java ،PHP و ... موجود است.

اولین نکته‌ای که باید درک شود این است که شیءگرا بودن یک زبان، به هیچ عنوان تضمینی بر کیفیت ساخت آن زبان نیست! حتی تضمینی بر کیفیت کدهایی که در آن زبان نوشته می‌شوند هم نیست!

دومین نکته این است که شیءگرایی، برعکس چیزی که در کتاب‌ها درباره اش می‌خوانید و تبلیغاتی که حول و حوش آن می‌شود، دارای مخالفان زیادی است!

صحبت در مورد نظرات مثبت و منفی نسبت به شیءگرایی در این نوشته نمی‌گنجد. شما می‌توانید خودتان در این باره تحقیق کنید. اما برای جلب توجه هر چه بیشتر شما به این موضوع، لیست کوچکی از اسامی افرادی را به شما نشان می‌دهیم که از مخالفان معروف شیءگرایی به حساب می‌آیند:

Ken Thompson: خالق سیستم عامل Unix، خالق زبان برنامه نویسی B، اولین توسعه دهنده Reqular expressions، خالق کدینگ UTF-8، خالق زبان برنامه نویسی Go و...

Dennis Ritchie: نفر دوم در خلق سیستم عامل Unix، خالق زبان برنامه نویسی C و...

Rob Pike: عضو تیم توسعه سیستم عامل Unix و سیستم عامل Plan9، خالق کدینگ UTF-8، خالق زبان برنامه نویسی Limbo، خالق زبان برنامه نویسی Go و ...

Richard Stallman: خالق پروژه GNU، از توسعه دهندگان اولیه مجموعه کامپایلرهای GCC، از توسعه دهندگان اولیه Emacs، GDB، Gmake و...

Gmake، GDB، ،Emacs و ...

Linux Torvalds: خالق سیستم عامل Linux، خالق Git و ...

Rich Hickey: خالق زبان برنامه نویسی Clojure و ...

Joe Armstrong: خالق زبان برنامه نویسی Erlang، از طراحان پلتفرم OTP و...

Simon peyton-Jones: از طراحان زبان برنامه نویسی Haskell، توسعه دهنده اصلی کامپایلر GHC و ...

Paul Graham: خالق زبان برنامه نویسی Arc، موسس شرکت Y Combinator شرکتی که مولد سایت‌هایی مثل Disqus، Dropbox، Reddit و Scribd است.

Edsger Dikstra: از بزرگترین محققان دنیای کامپیوتر و ابدا کننده الگوریتم‌های تاثیرگذاری مثل الگوریتم معروف دایکسترا و ...

Alexander Stepanov: طراح اولین کتابخانه STL در زبان C++.

Luca Cardelli: نویسنده اولین کامپایلر زبان برنامه نویسی ML. زبان ML ریشه اصلی زبان‌های Haskell، Ocaml و F# می‌باشد، از طراحان زبان برنامه نویسی Modula-3 و ...

می توان گفت که این‌ها سرشناس ترین افراد در دنیای برنامه نویسی هستند؛ اگر هیچکدام از این افراد نظر خیلی مثبتی نسبت به شیءگرایی ندارند، پس شاید بد نباشد که کمی از وقت خود را به تحقیق در این رابطه اختصاص دهید!

ساختار رویه‌ای Go در برابر شیءگرایی

طراحیان Go بر این باورند که مدل شیءگرایی در زبان‌هایی مثل Java و C# و C++ پیچیدگی‌های زیادی دارد. و این پیچیدگی در زبان، باعث تولید کدهای پیچیده خواهد شد.

Go یک زبان رویه‌ای است (Procedural)، اما نه یک زبان رویه‌ای کلاسیک مانند C.

طراحان Go نوآوری‌های جالبی در ساختار کلاسیک زبان‌های رویه‌ای ایجاد کرده اند تا Go را به یک زبان رویه‌ای مدرن تبدیل کنند!

برنامه نویسان با کمک این ساختار رویه‌ای مدرن، نیاز چندانی به آن شیءگرایی مرسوم در زبان‌های دیگر حس نخواهند کرد. در ادامه با تعدادی از ایده‌های جدید Go در این زمینه آشنا می‌شوید.

ابتدا یک توضیح ساده در باره کلاس‌ها و اشیا

در بیشتر زبان‌های برنامه نویسی روشی وجود دارد که برنامه نویس به کمک آن می‌تواند یک Data Type جدید ایجاد کند. Data Type که به اختصار Type خوانده می‌شود، الگوی است که تعیین می‌کند یک داده چه ساختاری در حافظه خواهد داشت و چه عملیاتی می‌تواند روی آن انجام داد.

زبان‌های مختلف، روش‌های مختلفی برای ساخت یک Type ارایه کرده اند. مثلا در زبان C از Struct برای این منظور استفاده می‌شود. (با همراهی typedef) در اکثر زبان‌های شیءگرا هم ساختاری وجود دارد به نام Class که به برنامه نویس امکان ساخت یک Type جدید را می‌دهد.

در زبان‌های شیءگرا، یک Type می‌تواند از تعدادی فیلد و متد تشکیل شود که به ترتیب تعیین کننده خصوصیات و رفتار آن Type هستند.

برای استفاده از یک Type، باید یک نمونه از آن Type را در حافظه ایجاد کنید. در زبان‌های شیءگرا این نمونه‌ها را اصطلاحا Object یا شی می‌نامند.

Go به جای Class از Struct استفاده می‌کند

Go هم مثل C از Struct‌ها برای ساخت یک Type جدید استفاده می‌کند. با این تفاوت که Struct‌های Go نسبت به C پیشرفته ترند. یک Struct در Go می‌تواند علاوه بر داشتن فیلد، دارای متد هم باشد.

متدها در Go همان توابع معمولی هستند. حتی داخل struct‌ها هم نوشته نمی‌شوند. فقط لازم است با یک تغییر کوچک به هنگام تعریفشان، آن‌ها را به struct‌ها نسبت دهیم.

در واقع با وجود داشتن چنین Struct‌هایی در زبان Go، شما نیازی به داشتن چیزی مثل Class ندارید! همان کارهایی که با Class‌ها قابل انجام است، با این Struct‌های جدید خیلی راحت تر و سبک تر قابل انجام خواهد بود.

زبان برنامه نیسی Rust هم که در حال توسعه از طرف Mozilla است، با اینکه در نسخه‌های اولیه خود دارای ساختار Class بود، اما در نسخه 0.4، ساختار Class را از زبان حذف کرد و آن را با Struct‌هایی مشابه چیزی که در Go وجود دارد جایگزین نمود.

Go از Composition به جای وراثت استفاده می‌کند

Java سعی کرد با حذف قابلیت وراثت چندگانه که در C++ وجود داشت، باعث ساده شدن مکانیزم وراثت در زبان شود. Go یک قدم جلوتر رفت و کلا با حذف وراثت، Compositon را به جای آن جایگزین کرد.

Composition چیست؟ فرض کنید دو Struct به نام‌های A و B تعریف کرده ایم. B می‌تواند A را مانند یک فیلد معمولی در Struct خود قرار دهد تا به اعضای موجود در A دسترسی داشته باشد.

همانند شیو Struct‌های تو در تو در زبان C. به این ترتیب بدون درگیر شدن با پیچیدگی‌های مبحث وراثت، می‌توانیم کانیزمی شبیه آن را در کدهایمان داشته باشیم.

حتما می‌دانیم که خیلی از زبان‌های شیءگرا از یک سیستم سلسله مرتبه‌ای برای کار با اشیا بهره می‌برند. مثلا در بیشتر آن ها، یک شی Object وجود دارد و بقیه اشیاء همگی به طریقی از آن ارث می‌برند.

در Go چنین چیزی وجود ندارد. هر Type برای خودش مستقل است. نیازی نیست کامپایلر در هر عمل کامپایل رابطه وراثت بین Type‌ها را چک کند. یکی از دلایل اصلی سرعت کامپایلر Go نیز همین مساله است.

اطمینان داشته باشید که خودتان هم با استفاده از قابلیت ترکیب سازی در Go، متوجه مزیت آن نسبت به وراثت خواهید شد.

حتی در کتاب Design Patterns: Elements of Reusable Object-Oriented Software که یکی از معروف ترین کتب مرجع در زمینه شیءگرایی می‌باشد، عنوان شده است که:

ترکیب سازی اشیاء را به وراثت ترجیح دهید

Favor Object Composition over class inheritance

Go می‌تواند برای اعضا حق دسترسی تعیین کند

اگر نام یک عضو با حرف کوچک شروع شود (مانند hello)، آن عضو فقط برای اعضای داخل Package در دسترس است.

اگر نام یک عضو با حرف بزرگ شروع شود (مانند Hello)، آن عضو می‌تواند در محیط خارج از Package نیز در دسترس قرار گیرد.

در این حالت فقط با یک نگاه به نام آن عضو، می‌توان به سطح دسترسی آن پی برد.

دقت کنید که این روش فقط یک "استایل نام گذاری" نیست. این یک "قانون" است، به این معنی که کامپایلر واقعا در زبان کامپایل این سطوح دسترسی را چک می‌کند.

Go دارای ساختار interface است

interface به عنوان یکی از بهترین قابلیت‌های معرفی شده توسط زبان‌های شیءگرا در Go حضور دارند. برنامه نویسان Java و C# با interface‌ها کاملا آشنایی دارند.

به زبان ساده، یک interface مشابه یک (سند قرارداد) است. تمام Type‌هایی که به یک interface وابسته هستند موظف اند از قراردادهایی که توسط آن interface تعریف شده تبعیت کنند. بدین صورت آن interface می‌تواند در موقعیت‌های مختلف، به وکالت از تمام Type‌های وابسته به آن مورد استفاده قرار بگیرد (به جای این که تک تک آن Type‌ها را جداگانه احضار کنید).

در Go، قرارداد بین یک interface و Type‌های وابسته به آن، فقط شامل تعاریف متدها می‌شود.

Interface‌ها نگرش اصلی زبان Go به مبحث Polymorphism می‌باشند؛ آن‌ها به عنوان یکی از قابلیت‌های مهم زبان تلقی شده و به هنگام ساخت API‌ها بسیار مورد استفاده قرار می‌گیرند. مطالعه و یادگیری آن‌ها برای افراد علاقه مند به زبان Go توصیه می‌شود.

درست است که Go چیزی تحت عنوان Class ندارد، اما اجباری نیست که برای نوشتن کدهای شیءگرا حتما از Class‌ها استفاده کنید. اجباری هم نیست که حتما برنامه‌های خود را به صورت شیءگرا طراحی کنید. این توهم‌ای است که امثال زبان‌هایی مثل Java و C# و C++ به شما تلقین کرده اند.

Go تمام قابلیت‌های لازم برای برنامه نویسی شیءگرا را در اختیار شما قرار داده است. حتی می‌توان گفت که شیءگرایی در Go به نسبت خیلی از زبان‌های دیگر ساده تر و راحت تر است.

مساله این است که دیدگاه Go نسبت به ساخت برنامه ها، با دیدگاهی که زبان‌هایی مثل Java یا C# با آن آشنا هستید متفاوت است. هدف آن‌ها یکی است، اما روش کارشان با یکدیگر فرق دارد.

برنامه نویسی در Go بر مبنای Type‌ها و توابع صورت می‌گیرد، نه Class‌ها و متدها.

سازندگان Go فکر نمی‌کنند که برای نوشتن برنامه‌های ساخت یافته در ابعاد وسیع، حتما باید به شیءگرایی متوسل شد؛ شاید راه‌های ساده تر و مناسب تری هم وجود داشته باشد. این دیدگاه شبیه همان نگرشی است که زبان‌های Functional به عنوان قطب مخالف زبان‌های شیءگرا مدت هاست که آن را عنوان می‌کنند.

Go یک زبان Static-Type است

زبان‌های Static نسبت به زبان‌های Dynamic از سه مزیت عمده برخوردارند:

  • سرعت: چون در زبان‌های Static نوع تمام داده‌ها از قبل مشخص می‌شود، سرعت اجرای برنامه به مراتب بالاتر از زبان‌های Dynamic خواهد بود. در زبان‌های Dynamic نوع داده‌ها به هنگام اجرا مشخص خواهد شد.
  • امنیت: در زبان‌های Static کامپایلر قادر است تمام داده‌ها و پارامترها را چک کند تا اگر برنامه نویس به صورت سهوی متغیری را در جای اشتباهی به کار برده بود، قبل از کامپایل برنامه به او هشدار داده شود.
  • مستندات: مستندسازی کدها در زبان‌های Dynamic نیاز به دقت بالایی دارد. برای مثال باید نوع پارامترهای یک تابع را در مستندات ذکر کنیم تا برنامه نویسان دیگر بدانند که قرار است چه نوع داده‌ای را به تابع ارسال کنند. اما در زبان‌های Static نوع هر پارامتر جزیی از خود کد است و برنامه نویس با یک نگاه ساده به نحوه تعریف تابع می‌تواند اطلاعات زیادی درباره آن بدست آورد.

جدای از مزایایی که زبان‌های Static ارائه می‌کنند، یک عیب بزرگ نیز دارند: اینکه Static هستند. درست است، Static بود نیک زبان شبیه چاقوی دولبه است. مزیت اصلی آن، همان عیب آن است.

در این زبان‌ها باید مدام با Type‌ها سرو کله بزنید. برنامه نویسان زبان‌های Dynamic به خوبی می‌دانند که Dynamic بودن زبان دلخواهشان، تا چه میزانی در سرعت کدنویسی شان تاثیر دارد.

خوشبختانه Go می‌تواند Type یک متغیر را از روی مقداری که به آن نسبت می‌دهیم تشخیص دهد. مثلا اگر عدد 12 را در متغیر A بریزیم، Go متغیر A را از نوع int فرض خواهد کرد. این قابلیت شبیه سیستم Type Inference در زبان Haskell است.

وقتی چنین سیستم تشخیص Type‌ای را با مدل ساده و سریع کامپایل برنامه‌ها ادغام کنید، متوجه می‌شوید که سرعت کدنویسی شما قابل رقابت با سرعت کدنویسی در زبان‌های Dynamic خواهد بود.

جناب آقای Joe Armstrong خالق زبان برنامه نویسی Erlang و پلتفرم OTP در مورد زبان برنامه نویسی Go می‌گوید:

من فکر می‌کنم این زبان به سنت Unix و C برگشته و کمبودهای زبان C را جبران کرده است. من فکر نمی‌کنم که C++ یک پیشرفت در این زمینه بوده باشد. اما معتقدم که Go قطعا یک پیشرفت برای زبان C به حساب می‌آید. و از طرفی هم این افراد در گذشته با آدم‌هایی مثل Kernighan و امثال اون کار می‌کردن و اطمینان دارم که تجربه بسیار بالایی در ساخت زبان‌ها برنامه نویسی دارن. این زبان خیلی ظریفانه مهندسی شده و از اول خیلی از ابزارهایی که احتیاج دارید در اون وجود داره. حتی اولین نسخه‌ای هم که از این زبان منتشر شد در سطحی از تکامل قرار داشت که انگار سال‌ها در حال توسعه بوده و در کل نظر من در مورد این زبان بسیار مثبت است.

تگ ها: golang