شیما سیف الهی
شیما سیف الهی
خواندن ۱۹ دقیقه·۳ سال پیش

خلاصه کتاب معماری نرم‌افزار تمیز

مقدمه

مهم‌ترین دارایی شرکت‌های نرم‌افزاری انسان‌ها هستند. روابط و احساسات بین افراد در مهندسی نرم‌افزار مهم است. در معماری باید نتایج بر اساس حساب و کتاب و اندازه‌گیری باشد و بتوان آن‌ها را توضیح داد و مستند کرد. همچنین باید نرم‌افزار دارای عملکرد و انعطاف‌پذیری خوبی باشد و به اشکال‌زدایی، ردیابی، انرژی و زمان زیاد برای رفع خطاها و مشکلات و سندهای زیادی نیاز نداشته باشد. در نتیجه باید دارای برنامه‌نویسی تمیز با سربار کمتر باشد.

کتاب معماری نرم‌افزار تمیز
کتاب معماری نرم‌افزار تمیز

فصل اول: طراحی و معماری چیست؟

معماری طراحی سطح بالا است و معماری و طراحی یعنی جزئیات و مؤلفه‌های سطح بالا دو عنصر جدایی ناپذیر هستند. باید کدنویسی به گونه‌ای باشد که سبب هرج و مرج نشود زیرا هزینه دارد و نباید فقط روی ددلاین پروژه تمرکز کرد بلکه باید کد و معماری درست و تمیز باشند در غیر این صورت مشکلی زیادی را ایجاد می‌کند.

آنکل باب فرقی بین معماری و طراحی قائل نیست و معتقد است که برخی از جزئیات طراحی خیلی مهم هستند، مورد نظر معماری هستند و با هم ارتباط دارند. جزئیات می‌توانند روی معماری کلان تاثیر بگذارند، بنابراین در معماری مواردی مربوط به سطح بالا و سطح پایین وجود دارند.

هدف معماری نرم‌افزار: هدف معماری نرم‌افزار کمینه کردن نیاز به زحمت افراد در توسعه و نگهداری نرم‌افزار و کمینه کردن زمان و منابع مصرف شده است.

همان طور که در نمودار شکل 1 مشاهده می‌شود، پروژه به مرور زمان بزرگتر می‌شود ولی از یک جایی به بعد بهره‌وری افزایش نمی‌یابد و بهره‌وری ثابت می‌ماند. بر اساس نمودار شکل 2 مشاهده می‌شود که به مرور زمان هزینه مربوط به تولید نرم‌افزار و حقوق افراد با افزایش افراد تیم افزایش می‌یابد ولی کد جدیدی تولید نمی‌شود و همچنین ممکن است سرعت کاهش یابد زیرا آموزش به افراد جدید زمانبر است.

           شکل ۱: نمودار بهره‌وری در طول بازه زمانی یکسان
شکل ۱: نمودار بهره‌وری در طول بازه زمانی یکسان
شکل 2: نمودار هزینه در هر خط کد در طول زمان
شکل 2: نمودار هزینه در هر خط کد در طول زمان


همان طور که در نمودار شکل 3 مشاهده می‌شود نسخه‌های بعدی نرم‌افزار بهره‌وری را کاهش می‌دهد که نشان‌دهنده تمیز نبودن معماری و ساختار پروژه است. بنابراین هزینه تولید یک نرم‌افزار تمیز کمتر از هزینه تولید یک نرم‌افزار کثیف است و تمیز بودن نرم‌افزار سرعت را افزایش می‌دهد. معمار نرم‌افزار باید کارفرما را راضی نگه دارد و گاهی اوقات باید trade off کند تا ساختار منظم شود.

شکل 3: بهره‌وری در طول نسخه‌های بعدی نرم‌افزار
شکل 3: بهره‌وری در طول نسخه‌های بعدی نرم‌افزار


روش test develop development (TDD): در این روش برای توابع و مؤلفه‌ها هم‌زمان تست نوشته می‌شود تا عملکرد آن تابع یا مؤلفه به طور هم‌زمان بررسی شود. تست مؤلفه یا تست واحد قبل از شروع به پیاده‌سازی رابط کاربری ابتدا تست مربوط به آن را تولید می‌کنند یعنی ابتدا تست نوشته می‌شود و سپس کد نوشته می‌شود و برنامه‌نویس مجاز به نوشتن به کد جدید نیست مگر آن که مورد آزمون مربوط به آن fail شود و برای ایجاد ویژگی جدید ابتدا تست مربوط به آن برای نمایش رفتار آن ویژگی نوشته می‌شود و اگر fail شد، کد مربوط به آن نوشته می‌شود. اگر خطایی به وجود آمد ابتدا مورد آزمون آن نوشته می‌شود تا نشان داده شود تست با وجود آن خطا fail می‌شود و شرایط خطا در مورد آزمون باز تولید می‌شود و باید کد به گونه‌ای تغییر داده می‌شود که همه موارد آزمون پاس شوند. با استفاده از این روش استفاده از تست در تولید نرم‌افزار ترویج می‌شود و کد تمیزتر می‌شود و زمان تولید آن کاهش می‌یابد.

روش Test double: در این روش ممکن است واحدهای اطراف واحد مورد تست، برای تست آماده نشده باشند در این صورت واحدهای اطراف بدل و شبیه‌سازی می‌شوند.

روش Real Test: در این روش نیازی نیست که واحدهای مورد تست، یکی یکی تست شوند و می‌توان بعد از آماده شدن چند واحد مرتبط به هم، واحدهای مورد نظر را به هم متصل کرد و با هم تست کرد زیرا بدل کردن واحدها عوارضی دارند و به جای تست واحد مورد نظر، طراحی انجام شده برای آن واحد تست می‌شود.

بنابراین گاهی اوقات روش بدل کردن و شبیه‌سازی مفید است و گاهی اوقات این روش مفید نیست بنابراین می‌توان از روش ترکیبی که ترکیبی از روش‌های Test double و Real Test هست، استفاده کرد.

نتیجه‌گیری: تنها راه تمیز پیش رفتن است نه سریع پیش رفتن.


فصل دوم: یک داستان با دو مقدار

دو مقدار عملکرد و ساختاری وجود دارد که هر دو ارزشمند هستند و باید بررسی شود که کدام یک مهم‌تر است و معمار باید به معیار مهم‌تر، اهمیت بیشتری دهد. بنابراین باید برای این دو مقدار اولویت‌بندی صورت گیرد. رفتار چیزی هست که ذی‌نفعان انتظار دارند و به معماری توجه نمی‌کند و نرم‌افزار باید بتواند با تغییر نیازمندی‌ها ساختار و معماری خود را تغییر دهد تا با تغییر ایجاد شده منطبق باشد و بتواند ویژگی جدید را اضافه کند. همچنین باید انعطاف‌ داشته باشد. ارزش معماری بیشتر از نرم‌افزار است و باید بتواند رفتارهای مورد نظر ذی‌نفعان را برآورده کند.

بنابراین می‌توان از ماتریسی برای این منظور استفاده کرد و به وسیله آن می‌توان معماری و رفتار را اولویت‌بندی کرد. درایه اول ماتریس مربوط به کارهای مهم و ضروری۷ درایه دوم مربوط به کارهای مهم و غیر ضروری، درایه سوم مربوط به کارهای غیر مهم و ضروری و درایه چهارم مربوط به کارهای غیر مهم و غیر ضروری است. در ماتریس نشان داده می‌شود که کارها چقدر مهم و چقدر فوری هستند بنابراین کارها دارای دو بعد هستند. در نتیجه با استفاده از این ماتریس دریافت می‌شود که معماری اولویت بالاتری دارد و باید بیشتر روی آن کار شود.

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

نتیجه‌گیری: معمار و توسعه دهنده هم به نوعی در پروژه نرم‌افزاری به عنوان ذی‌نفع هستند و تنها راه برای سریع پیش رفتن کارها تمیز بودن معماری است.


فصل سوم: شروع با اجرها (برنامه‌نویسی با الگوها)

پارادایم‌های برنامه‌نویسی مانند برنامه‌نویسی ساختاریافته (Structed programming)، برنامه‌نویسی شی‌گرا (Object Oriented Programming) و برنامه‌نویسی تابعی (Functional Programming) مهم و رایج هستند و هر کدام یک سری محدودیت ایجاد می‌کنند.

پارادایم ساختار یافته: در برنامه‌نویسی می‌توان به راحتی با استفاده از go to از تابع به تابع دیگری رفت در حالی که این گونه نیست و برنامه‌نویسی سخت‌تر می‌شود در برنامه‌نویسی ساختاریافته go to به صورت محدود استفاده می‌شود یا به طور کلی مورد استفاده قرار نمی‌گیرد و دیسیپلین‌ فراخوانی‌ها و کنترل نوع برنامه را محدود می‌کند و به جای go to می‌توان از if و else استفاده کرد.

پارادایم شی‌گرا: این پارادایم اجازه استفاده از pointer برای اشاره به تابع یا شی را نمی‌دهد و جلوی استفاده از این قابلیت را می‌گیرد، دیسیپلین تبدیل و کنترل نوع برنامه را محدود می‌کند، متغیر نباید تغییر کند و باید در ابتدا مقدار شود. این پارادایم قابلیتی برای پلی‌مورفیسم ارائه می‌دهد و خودش آن را مدیریت می‌کند.

پارادایم تابعی: اجازه تغییر متغیرها را توسط برنامه و از داخل برنامه در هنگام اجرای همروند نمی‌دهد و از این قابلیت جلوگیری می‌کند.

ارتباط سه پارادایم با معماری: از پلی‌مورفیسم برای عبور از مرزهای معماری استفاده می‌شود و از functional‌ برای اعمال نظم و انضباط و مکان و دسترسی به داده‌ها استفاده می‌شود. از این پارادایم به عنوان پایه الگوریتمی برای ماژول‌ها استفاده می‌شود. این سه پارادایم با سه دغدغه معماران شامل تابع، تفکیک مؤلفه و مدیریت داده هم‌راستا هستند. هم چنین این پارادایم‌ها در تناقض با یکدیگر نیستند و می‌توانند با هم مورد استفاده قرار گیرند.

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


فصل چهارم: برنامه‌نویسی ساخت‌یافته (Functional Programming)

برای درستی برنامه باید برنامه‌نویسی را کوچکتر کرد و به ساختارهای کوچک تبدیل و اثبات کرد. عبارت go to برنامه‌نویسی را خراب می‌کند، زیان‌آور است و باید حذف شود. بنابراین می‌توان تمام برنامه‌ها را با توابع گزینش و تکرار یعنی if و else و دستورات شرطی انجام داد، درستی را بررسی کرد و در حلقه‌ها مسیرها را بررسی و اثبات کرد. نادرستی برنامه را می‌توان با تست آن فهمید ولی درستی برنامه را نمی‌توان با تست به دست آورد و اثبات کرد و تست می‌تواند ثابت کند که در برنامه خطایی هست. ولی مکانیزم‌هایی برای اثبات بدون خطا بودن برنامه مانند روش‌های فرمال و رسمی، تبدیل نرم‌افزار به مدل انتزاعی و اثبات درستی از طریق روش‌های ریاضی وجود دارد.

برنامه‌نویسی ساخت‌یافته برنامه‌ها را به مجموعه‌ای از توابع قابل اثبات تبدیل می‌کند که قابل تست هستند (به منظور بررسی نادرستی برنامه‌ها) و با حذف go to دیگر کنترل برنامه به صورت نامنظم نیست. زمانی که go to در برنامه زیاد باشد، شکستن و تقسیم برنامه سخت‌تر می‌شود که از نظر معماری مهم است.


فصل پنجم: برنامه‌نویسی شی‌گرا (Object Oriented Programming)

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

مقایسه کپسوله‌بندی در زبان جاوا و C: برای کپسوله‌بندی در زبان C از کتابخانه‌هایی با دستور include در کد استفاده می‌شود، بدون آن که نیاز به پیاده‌سازی داشته باشند. در جاوا می‌توان متدها را به صورت خصوصی تعریف کرد تا دسترسی از جایی دیگر به آن‌ها وجود نداشته باشد. در زبان C متدهای عمومی و خصوصی را می‌توان بدون پیاده‌سازی آن‌ها در فایل header قرار داد در حالی که در زبان‌های جاوا و C++ باید پیاده‌سازی آن‌ها در فایل header قرار داده شوند. بنابراین زبان C نسبت به زبان جاوا از نظر کپسوله‌بندی قوی‌تر است و کپسوله‌بندی در زبان‌های قدیمی‌تر نیز وجود داشته‌اند.

مقایسه وراثت در زبان جاوا و C: د زبان C می توان کلاس بالایی را به پایینی cast کرد و ترتیب پارامترها یکسان است و باید cast را بیان کرد در حالی که در زبان جاوا و شی‌گرا cast بیان نمی‌شود و کلاس پایینی به کلاس بالایی پاس داده می‌شود. همچنین در زبان C باید ترتیب فیلدها در کلاس فرزند و پدر یکی باشد در حالی که در زبان جاوا و شی‌گرا این محدودیت وجود ندارد، بنابراین در این بخش زبان شی‌گرا نیم‌ امتیاز دریافت می‌کند.

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

وارونگی وابستگی (Dependency Inversion): زمانی که تعدادی کلاس سطح بالا می‌خواهند از کلاس‌های سطح پایین و پایگاه‌های داده استفاده کنند و به آن‌ها وابسته شوند، در این صورت تغییر در کلاس‌های سطح پایین و جزئیات روی کلاس‌های سطح بالا اثر می‌گذارد و آن‌ها را تغییر می‌دهد و جریان اجرا از بالا به پایین است. بنابراین باید جهت وابستگی‌ها بر عکس شود و باید جریان اجرای برنامه از پایین به بالا باشد و باید جزئیات به کلیات وابسته شوند نه برعکس. برای این منظور وارونگی وابستگی مورد استفاده قرار می‌گیرد که می‌توان از یک interface که در شکل 4 نمایش داده شده است و بدون پیاده‌سازی است استفاده کرد و به آن می‌توان وابسته شد و کلاس‌های سطح بالا و سطح پایین به interface وابسته می‌شوند که در این صورت به صورت یک درگاه مشترک است که در این صورت هر کلاسی می‌تواند پیاده‌سازی متفاوتی را بدون وابستگی ارائه دهد، پیاده‌سازی متد در سطح پایین مهم نیست و متدها از طریق interface فراخوانی می‌شوند. بنابراین با شی‌گرایی می‌توان در هر جایی وابستگی را به هر سمتی که مورد نظر است قرار دارد، جریان اجرای برنامه را حفظ کرد، سبب افزایش نگهداری و تغییرپذیری می‌شود و به صورت مجزا توسعه، نگهداری و مستقر می‌شود. injection با وارونگی متفاوت است و با استفاده از وارونگی آسان می‌شود. injection فرایندی است که تولید بخش‌های سطح پایین و وابستگی به آن‌ها را تسهیل می‌کند. برای مدیریت وابستگی‌ها می‌توان از الگویی به نام mediator استفاده کرد که به جای فراخوانی وابستگی‌ها این الگو صدا زده می‌شود و همه وابستگی‌ها را inject می‌کند.

شکل 4:  وارونگی وابستگی
شکل 4: وارونگی وابستگی


فصل ششم: برنامه‌نویسی تابعی (Functional Programming)

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

در معماری نیز ثبات در متغیرها وجود دارد که جلوی بن بست را می‌گیرد، مسئله همروندی را حل می‌کند و مشکل رقابت برای گرفتن منابع را کاهش می‌دهد. بنابراین برنامه چند نخی با استفاده از برنامه‌نویسی تابعی راحت‌تر است و تغییر ناپذیری باعث کاهش مشکل چند نخی می‌گردد. بنابراین باید بخش‌هایی که شامل متغیر تغییر پذیر هستند را تفکیک کرد که به بخش‌های تغییر ناپذیر کمک می‌کند که در شکل 5 نمایش داده شده است. متغیرهای تغییر ناپذیر بدون نیاز به همگام‌سازی کمتر دچار خطا می‌شوند و نخ ایمن هستند.

شکل 5: تفکیک بخش‌هایی با متغیر تغییر پذیر
شکل 5: تفکیک بخش‌هایی با متغیر تغییر پذیر


منبع رویداد (Event Sourcing): ثبت تمام تغییرات در برنامه به صورت به هم پیوسته و دنباله‌دار صورت می‌گیرد و به جای نگهداری وضعیت سیستم می‌توان رویدادهای را از ابتدای وضعیت سیستم تا زمان فعلی نگهداری کرد که می‌‌توان وضعیت سیستم را بر اساس رویدادها تشخیص داد. بنابراین متناسب با معماری نرم‌افزار و تکنولوژی‌ها باید نوع پایگاه داده و مدل ثبت تغییرات وضعیت نرم‌افزار تعیین شود. با استفاده از منبع رویداد می‌توان تراکنش‌ها را در داخل یک پایگاه داده در برنامه‌های یکپارچه نوشت ولی در میکروسرویس‌ها که پایگاه‌های داده از هم جدا هستند و هر میکروسرویس پایگاه داده مخصوص به خود دارد که روی آن اجرا می‌شود نمی‌توان ویژگی اتمیک بودن را حفظ کرد و تراکنش‌ها جدا هستند. برای حالت اتمیک باید همه تراکنش‌ها را داخل یک پایگاه داده ذخیره کرد، زمانی که همه تراکنش‌ها ذخیره می‌شوند پیغام موفقیت‌ آمیز نمایش داده می‌شود و اگر یکی از آن‌ها انجام نشوند عقبگرد می‌کند. یکی از کاربردهای منبع رویداد استفاده از آن در فضای توزیع شده میکروسرویس‌ها هست. در منبع رویداد به جای عملیاتی مانند ایجاد، حذف، خواندن، بروزرسانی فقط از عملیات ایجاد و خواندن استفاده می‌شود یعنی فقط log اتفاقات ثبت می‌شوند و حالت و متغیری که قرار است بروزرسانی شود، بر اساس اتفاقاتی که افتاده است به دست می‌آید که در این صورت حافظه بیشتر می‌شود و قفل کردن کمتر می‌شود که در مقیاس‌های بالا سبب افزایش سرعت می‌گردد ولی در مقیاس‌های پایین سرعت کاهش می‌یابد. هر چند وقت یکبار در هر بار اجرای سیستم رویدادها از آخرین حالت پایدار خوانده می‌شوند تا از سربار جلوگیری شود.


فصل هفتم: اصل مسئولیت واحد

هر تابع و واحد باید یک کار را درست و خوب انجام دهد، باید انسجام بالایی داشته باشد و باید بتوان با مستند کمتری آن را فهمید. اگر یک تابع چند کار را انجام دهد، در صورتی خرابی یک کارکرد اشکال‌زدایی و پیگیری روند برنامه سخت می‌شود و سبب کاهش خوانایی و قابلیت استفاده مجدد می‌شود. به همین صورت هم یک ماژول باید فقط در مقابل یک actor مسئول باشد و به آن جواب دهد.

اصل مسئولیت واحد: یک ماژول باید یک و فقط یک دلیل برای تغییر داشته باشد و باید فقط در مقابل یک و فقط یک actor مسئول باشد. در هنگام پیاده‌سازی ممکن است ماژول‌ها به دلایل مختلف مانند تغییر در نیازمندی‌ها و سایر بخش‌هایی که از آن‌ها استفاده می‌کنند.

اگر هر متد مسئول بخشی از سازمان باشد و هر actor به بخش‌هایی از یک متد مرتبط باشد که هر بخش آن مسئول یک actor است در این صورت تغییر در هر مورد کاربرد سبب تغییر در یک متد می‌شود و هر actor روی کلاس تاثیر می‌گذارد که در شکل 6 نمایش داده شده است.

شکل 6: عدم رعایت اصل مسئولیت واحد
شکل 6: عدم رعایت اصل مسئولیت واحد

بنابراین بر اساس اصل مسئولیت واحد این کار نباید انجام شود. برای حل این مشکل دو مکانیزم وجود دارد:

1) شکستن طراحی به بخش‌های مختلف و مجزا مانند کلاس‌ها، رابط‌ها و ... که در شکل 7 نمایش داده شده است.

شکل 7: شکستن طراحی به بخش‌های مجزا
شکل 7: شکستن طراحی به بخش‌های مجزا

2) استفاده از الگوی Facade که در این الگو از واسطی استفاده می‌شود که امکانات مختلف را با یکدیگر ترکیب می‌کند، دارای متدهای مختلف است که از کلاس‌های مختلف جمع‌آوری شده است و هر کدام از کلاس‌ها مسئولیت مجزایی دارند و جداگانه توسعه و تغییر می‌یابند که در شکل 8 نمایش داده شده است.

شکل 8: الگوی Facade
شکل 8: الگوی Facade

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

شکل 9: حفظ برخی از امکانات و شکستن امکانات دیگر
شکل 9: حفظ برخی از امکانات و شکستن امکانات دیگر


فصل هشتم: اصل باز و بسته (Open Close Principle)

ماژول‌ها و پیاده‌سازی باید خیلی راحت و باز باشند تا بتوان به راحتی و بدون هیچ تغییری آن‌ها را در سیستم گسترش داد. برای اضافه کردن امکان جدید به پروژه و قرار دادن آن در کنار ساید امکانات موجود در پروژه با استفاده از این اصل می‌توان برای گسترش دادن برنامه هیچ چیز را تغییر نداد، امکان جدید را اضافه کرد و از امکانات موجود استفاده کرد. به این صورت که اگر مؤلفه A باید از تغییر در مؤلفه B محافظت شود، مولفه B باید به مولفه A وابسته باشد و باید مؤلفه‌های سطح بالاتر در آن سلسله مراتب از تغییرات ایجاد شده در مؤلفه‌های سطح پایین محافظت می‌شوند و نهادهای نرم‌افزاری نباید به مواردی که مستقیماً استفاده نمی‌کنند، وابسته نباشند که در شکل 10 نمایش داده شده است.

شکل 10: ارتبط مؤلفه‌ها با یکدیگر بر اساس اصل باز و بسته
شکل 10: ارتبط مؤلفه‌ها با یکدیگر بر اساس اصل باز و بسته


فصل نهم: اصل تعویض لیسکوف (Liskov Substitution Principle)

این اصل در مورد یک موضوع بدیهی در وراثت است یعنی هر جایی که از وراثت استفاده می‌شود، باید وراثت به گونه‌ای معنا داشته باشد که در هر زبانی بتوان x را با هر چیزی مانند z که y را به ارث می‌برد، جایگزین کرد. یعنی منطق سیستم در قسمت‌های بالاتر خراب و نقض نشود، یعنی اگر به جای مؤلفه x مؤلفه z که از y ارث‌بری می‌کند قرار داده شود، مشکلی پیش نیاید. یک راه برای بررسی این موضوع استفاده از instance of برای x است که اگر نتیجه شود که شی از نوع دیگری است در این صورت ارث‌بری درست نبوده است.

همیشه وراثت درست نیست و باید LSP روی آن عمل کند. اگر وراثت برقرار باشد، با استفاده از LSP می‌توان آن را بررسی کرد که اگر بعدا مؤلفه دیگری جایگزین آن شود درست کار می‌کند یا نه. اگر کنترل برنامه به if یا switchای برسد که نوعی را چک می‌کند و بر اساس هر نوعی کاری را انجام می‌دهد یا به ifای برسد که یک instance of را دریافت می‌کند در این صورت ممکن است رابطه وراثت برقرار نباشد که با اصلاح رابطه وراثت و پلی‌مورفیسم این مشکل حل می‌شود.

بنابراین اگر همه مؤلفه‌ها زیر مجموعه یک interface واحد باشند و همه از یک قالب استفاده کنند و برای استثناها if قرار داده شود، راه درستی نیست و ممکن است مؤلفه‌‌های دیگری باشند که کلاس abstract را نقض کنند. در این صورت هر مؤلفه قالب خاص خود را دارد که این قالب در پایگاه داده یا فایل پیکربندی (config file) ذخیره می‌شود تا در کد مورد بررسی و استفاده قرار گیرد. در این صورت اگر مؤلفه جدیدی به پروژه اضافه شود، یک سطر به پایگاه داده یا config file اضافه می‌شود و سایر مکان‌ها تغییری نمی‌کنند.


فصل دهم: اصل تفکیک رابط (Interface Segregation Principle)

در این فصل به اصل جداسازی رابط‌ها (ISP) پرداخته می‌شود. برای انجام یک عملیات که شامل چند عملیات است و کاربران مختلفی از آن استفاده می‌کنند، در این صورت اگر در جایی تغییری ایجاد شود ممکن است بخش‌های دیگر دوباره کامپایل و مستقر شوند. بنابراین استقرار برنامه سخت می‌شود و زمان‌های زیادی صرف استقرارهای غیر ضروری می‌شود. همچنین ممکن است خطاهایی ایجاد شوند و وابستگی‌هایی ایجاد شوند که با بقیه تداخل دارند و کار سایر اجزا را مختل کنند که در شکل 11 این موضوع نمایش داده شده است.

شکل 11: عدم رعایت اصل ISP
شکل 11: عدم رعایت اصل ISP

همان طور که در شکل 12 مشاهده می‌شود هر تغییری در F که مربوط به S نباشد (S به یک تابع از F وابسته است) و به D مربوط باشد سبب می‌شود تا S دوباره مستقر شود که ممکن است تاثیرات منفی در کد بگذارد.

شکل 12: معماری مشکل‌ساز
شکل 12: معماری مشکل‌ساز

راه‌حل: به جای وابستگی به صورت مستقیم، تعدادی interface تعریف می‌شوند یعنی برای هر کاربر یک interface تعریف می‌شود و کلاس اصلی عملیات هم به Interfaceها وابسته می‌شود. تغییر در هر interface سبب تغییر در سایر interfaceها و کلاس عملیات نمی‌شود و فقط کاربر مربوط به آن interface می‌تواند تغییرات را ببیند که این موضوع در شکل 13 نمایش داده شده است. این تغییرات دارای اثرات مخرب نیستند و این اصل در زبان‌هایی مانند جاوا و پایتون مورد استفاده قرار می‌گیرد. بنابراین اگر مؤلفه‌های مختلف از امکانات مختلف استفاده کنند و امکانات در کنار هم جمع شوند، بهتر است هر مؤلفه پنجره‌ای که لازم دارد را ببیند و همه امکانات موجود را نبیند و تغییر در سایر امکانات در آن مؤلفه تاثیر نمی‌گذارد.

شکل 13: عملیات تفکیک شده
شکل 13: عملیات تفکیک شده

نتیجه‌گیری: این اصول از یکدیگر مجزا نیستند و یک راه تسهیل ISP داشتن وارونگی وابستگی است و مکمل و هم‌پوشان یکدیگر هستند. در این جا از یک کلاس چند تا رابط تفکیک شد و کدها به رابط‌ها وابسته شدند و کلاسی که رابط‌ها را پیاده‌سازی می‌کند می‌تواند از دید آن‌ها پنهان باشد.


فصل یازدهم: اصل وارونگی وابستگی

بر اساس این اصل باید بلاک‌های سیستم به کلاس‌های abstraction و interfaceها وابسته باشند و نباید به پیاده‌سازی interfaceهایی که extend شدند وابسته باشند. وارونگی یعنی ماژول‌های سطح بالا و سطح پایین به رابط‌ها و انتزاع‌ها وابسته هستند. همچنین پیاده‌سازی‌ها و کدهای سطح بالا و سطح پایین هم به انتزاع‌ها و رابط‌ها وابسته هستند. در این جا جهت وابستگی‌ها بر عکس شدند که نمایانگر وارونگی است و جریان فراخوانی‌ها و اجراها نسبت به جریان وابستگی‌ها برعکس می‌شوند که می‌توان برای این منظور از الگوی Factory استفاده کرد که در شکل 14 نمایش داده شده است. در زبان جاوا این کار آسان‌تر است.

شکل 14: استفاده از الگوی Factory برای مدیریت وارونگی
شکل 14: استفاده از الگوی Factory برای مدیریت وارونگی

راه حل‌ها برای رعایت این اصل عبارتند از:

1) نباید به کلاس‌های پیاده‌سازی اشاره کرد و باید از رابط و انتزاع استفاده کرد، چون ممکن است در آینده به پیاده‌سازی‌های دیگری نیاز باشد.

2) نباید از کلاس‌های پیاده‌سازی drive کرد چون ممکن است ارث‌بری از یک کلاس پیاده‌سازی سبب شود پیاده‌سازی‌های دیگری که از آن کلاس هست توجه نشود و مشکل if به وجود بیاید.

3) نباید توابع کلاس‌های پیاده‌سازی را override کرد چون override یک پیاده‌سازی خاص ممکن است برای سایر پیاده‌سازی‌ها مشکل ایجاد کند چون از اصول انتزاع استفاده نشده است، همه در یک سطح نیستند، کلاسی که در آن override اتفاق افتاده است در یک سطح پایین‌تر قرار می‌گیرد، سبب ایجاد مشکل می‌شود و ممکن است گاهی اوقات رفتار خاصی را تغییر دهد.

مؤلفه concrete: اگر برخی از مؤلفه‌ها مشکلات و وابستگی‌هایی ایجاد کنند، باید تعداد آن‌ها کم باشد و از بقیه سیستم جدا باشند. کدهایی که به مؤلفه‌ concrete وابسته هستند، مستقیما به مؤلفه‌ concrete متصل نیستند و به یک interface وابسته هستند. ایجاد شی‌ از نوع مؤلفه‌ concrete از طریق یک الگوی Factory انجام می‌شود که یک شی از نوع interface را تولید می‌کند که می‌تواند یک concrete تولید کند. مؤلفه concrete‌ ممکن است تغییر کند بنابراین سایر مؤلفه‌ها و اجزا بهتر است که موارد پایدار مثل interface متصل باشند. Interface مانند یک پروتکل است که تغییرات در آن شدید نیست. با تغییر interface موارد زیادی تغییر می‌کنند.


فصل دوازدهم: مؤلفه‌ها

مؤلفه: موجودیت‌هایی هستند که می‌توان به صورت مستقل به عنوان بخشی از یک سیستم توسعه داد. اگر مؤلفه‌ها به خوبی طراحی شوند می‌توانند به صورت مستقل توسعه داده شوند. فایل‌های به هم پیوسته و پویا که می‌توانند در زمان پویا به هم پیوند بخورند، مؤلفه‌های نرم‌افزاری و معماری برنامه هستند و می‌توان به صورت مختلف بخش‌های مختلف را بدون تغییر در اصل برنامه توسعه داد.

برنامه‌ها به حجم و حافظه زیادی نیاز دارند و برای کاهش حجم کتاب‌خانه‌ها را کامپایل می‌شوند و به فایل‌های اجرایی تبدیل می‌شوند. همچنین آدرس‌ها پویا نبودند که این مشکل با استفاده از loader حل می‌شود.

بار کننده‌ (Loader): برنامه را آدرس‌دهی می‌کند و به صورت پویا و یکی یکی در حافظه بارگذاری می‌کند که به صورت دستی انجام نمی‌شود و سریع انجام می‌شود.

پیوند دهنده (Linker): بخش‌های مختلف اجرای برنامه را به هم متصل می‌کند و به فایل‌های یکپارچه تبدیل می‌کند تا به صورت یکپارچه اجرا شوند که سرعت آن پایین بود و با سریع شدن حافظه و دیسک‌ها این مشکل بر طرف شد.


«این مطلب، بخشی از تمرینهای درس معماری نرم‌افزار در دانشگاه شهیدبهشتی است»












معماری_نرم_افزار_بهشتیمعماری نرم افزاردانشگاه شهید بهشتیکتاب تمیز
شاید از این پست‌ها خوشتان بیاید