سیستم
مشخصات کلی مطلوب سیستم عبارتند از:- حداقل پیچیدگی - از پروژه های بیش از حد پیچیده باید اجتناب شود. نکته اصلی سادگی و وضوح است (بهترین = ساده)؛
- سهولت نگهداری - هنگام ایجاد یک برنامه، باید به یاد داشته باشید که باید از آن پشتیبانی شود (حتی اگر شما نباشید)، بنابراین کد باید واضح و واضح باشد.
- کوپلینگ ضعیف حداقل تعداد اتصالات بین بخش های مختلف برنامه است (حداکثر استفاده از اصول OOP).
- قابلیت استفاده مجدد - طراحی سیستمی با قابلیت استفاده مجدد از قطعات آن در برنامه های دیگر.
- قابلیت حمل - سیستم باید به راحتی با محیط دیگری سازگار شود.
- تک سبک - طراحی یک سیستم در یک سبک واحد در قطعات مختلف آن.
- توسعه پذیری (مقیاس پذیری) - بهبود سیستم بدون ایجاد اختلال در ساختار اصلی آن (اگر قطعه ای را اضافه یا تغییر دهید، این نباید بر بقیه تأثیر بگذارد).
مراحل طراحی سیستم
- سیستم نرم افزاری - طراحی یک برنامه کاربردی به شکل کلی.
- جداسازی به زیرسیستم ها/ بسته ها - تعریف بخش های منطقی قابل تفکیک و تعریف قوانین تعامل بین آنها.
- تقسیم سیستم های فرعی به کلاس ها - تقسیم بخش های سیستم به کلاس ها و رابط های خاص و همچنین تعریف تعامل بین آنها.
- تقسیم کلاس ها به متدها ، تعریف کاملی از متدهای لازم برای یک کلاس، بر اساس وظیفه این کلاس است. طراحی روش - تعریف دقیق از عملکرد روش های فردی.
اصول و مفاهیم اصلی طراحی سیستم
اصطلاح اولیه سازی تنبل برنامه زمانی را صرف ایجاد یک شی نمی کند تا زمانی که از آن استفاده شود، که روند اولیه سازی را سرعت می بخشد و بار جمع کننده زباله را کاهش می دهد. اما نباید در این مورد زیاده روی کنید، زیرا این می تواند منجر به نقض ماژولار شود. ممکن است ارزش آن را داشته باشد که تمام مراحل طراحی را به یک قسمت خاص، به عنوان مثال، اصلی، یا به کلاسی که مانند یک کارخانه کار می کند، منتقل کنید . یکی از جنبه های کد خوب، عدم وجود کدهای مکرر تکرار شونده است. به عنوان یک قاعده، چنین کدی در یک کلاس جداگانه قرار می گیرد تا بتوان آن را در زمان مناسب فراخوانی کرد. AOP به طور جداگانه، می خواهم به برنامه نویسی جنبه گرا اشاره کنم . این برنامهنویسی با معرفی منطق انتها به انتها است، یعنی تکرار کد در کلاسها - جنبهها قرار داده میشود و هنگامی که شرایط خاصی بدست میآید فراخوانی میشود. به عنوان مثال، هنگام دسترسی به یک متد با یک نام خاص یا دسترسی به متغیری از یک نوع خاص. گاهی اوقات جنبه ها ممکن است گیج کننده باشند، زیرا بلافاصله مشخص نیست که کد از کجا فراخوانی شده است، اما با این وجود، این یک عملکرد بسیار مفید است. به طور خاص، هنگام ذخیره سازی یا ورود به سیستم: ما این قابلیت را بدون اضافه کردن منطق اضافی به کلاس های معمولی اضافه می کنیم. شما می توانید اطلاعات بیشتری در مورد OAP در اینجا بخوانید . 4 قانون برای طراحی معماری ساده از نظر کنت بک- بیانگر بودن - نیاز به یک هدف مشخص کلاس، از طریق نامگذاری صحیح، اندازه کوچک و رعایت اصل مسئولیت واحد حاصل می شود (در ادامه با جزئیات بیشتری به آن نگاه خواهیم کرد).
- حداقل کلاس ها و روش ها - در تمایل خود برای تقسیم کلاس ها به کوچکترین و یک طرفه ترین شکل ممکن، می توانید بیش از حد پیش بروید (ضد الگو - تیراندازی). این اصل مستلزم فشرده نگه داشتن سیستم و جلو نرفتن زیاد، ایجاد یک کلاس برای هر عطسه است.
- عدم تکرار - کد اضافی که باعث سردرگمی می شود نشانه طراحی ضعیف سیستم است و به مکان جداگانه منتقل می شود.
- اجرای تمام تست ها - سیستمی که تمام تست ها را گذرانده است، کنترل می شود، زیرا هر تغییری ممکن است باعث شکست تست ها شود، که می تواند به ما نشان دهد که تغییر در منطق داخلی روش نیز منجر به تغییر در رفتار مورد انتظار شده است.
رابط
شاید یکی از مهمترین مراحل ایجاد یک کلاس کافی، ایجاد یک رابط مناسب باشد که نمایانگر یک انتزاع خوب است که جزئیات پیاده سازی کلاس را پنهان می کند و در عین حال گروهی از روش ها را نشان می دهد که به وضوح با یکدیگر سازگار هستند. . بیایید نگاهی دقیقتر به یکی از اصول SOLID بیندازیم - تفکیک رابط : کلاینتها (کلاسها) نباید روشهای غیرضروری را اجرا کنند که از آنها استفاده نکنند. یعنی اگر در مورد ساخت واسط هایی با حداقل تعداد روش صحبت می کنیم که با هدف انجام تنها وظیفه این رابط انجام می شود (برای من بسیار شبیه به مسئولیت تک است ) بهتر است چند تا کوچکتر ایجاد کنیم. آنهایی که به جای یک رابط متورم. خوشبختانه، یک کلاس می تواند بیش از یک اینترفیس را پیاده سازی کند، همانطور که در مورد وراثت وجود دارد. همچنین باید در مورد نامگذاری صحیح رابط ها به خاطر داشته باشید: نام باید وظیفه خود را تا حد امکان دقیق نشان دهد. و البته هر چه کوتاهتر باشد، سردرگمی کمتری ایجاد خواهد کرد. معمولاً در سطح رابط است که نظرات برای مستندات نوشته میشود ، که به نوبه خود به ما کمک میکند تا با جزئیات توضیح دهیم که روش باید چه کاری انجام دهد، چه آرگومانهایی را میگیرد و چه چیزی را باز خواهد گرداند.کلاس
بیایید به تشکیلات داخلی کلاس ها نگاه کنیم. یا بهتر است بگوییم برخی نماها و قوانینی که در ساخت کلاس ها باید رعایت شود. به طور معمول، یک کلاس باید با لیستی از متغیرها شروع شود که به ترتیب خاصی مرتب شده اند:- ثابت های استاتیک عمومی؛
- ثابت استاتیک خصوصی؛
- متغیرهای نمونه خصوصی
اندازه کلاس
اکنون می خواهم در مورد اندازه کلاس صحبت کنم. بیایید یکی از اصول مسئولیت تک SOLID را به خاطر بسپاریم . مسئولیت واحد - اصل مسئولیت واحد. بیان میکند که هر شی فقط یک هدف (مسئولیت) دارد و منطق همه روشهای آن در جهت اطمینان از آن است. یعنی بر این اساس باید از کلاسهای بزرگ و پف کرده (که طبیعتاً ضد الگو هستند - "شیء الهی") اجتناب کنیم و اگر در یک کلاس روشهای منطقی متنوع و ناهمگون داریم، باید فکر کنیم. در مورد تقسیم آن به چند بخش منطقی (کلاس). این به نوبه خود خوانایی کد را بهبود می بخشد، زیرا اگر هدف تقریبی یک کلاس را بدانیم، به زمان زیادی برای درک هدف یک متد نیاز نداریم. شما همچنین باید به نام کلاس توجه داشته باشید : باید منطقی را که در آن وجود دارد منعکس کند. فرض کنید، اگر کلاسی داریم که نام آن بیش از 20 کلمه داشته باشد، باید به فکر refactoring باشیم. هر کلاسی که به خود احترام می گذارد نباید دارای این تعداد متغیر داخلی باشد. در واقع، هر متد با یکی از آنها یا چند متد کار می کند، که باعث جفت شدن بیشتر در کلاس می شود (که دقیقاً همان چیزی است که باید باشد، زیرا کلاس باید به عنوان یک کل واحد باشد). در نتیجه افزایش انسجام یک کلاس منجر به کاهش آن می شود و البته تعداد کلاس های ما افزایش می یابد. برای برخی، این آزاردهنده است؛ آنها باید بیشتر به کلاس بروند تا ببینند یک کار بزرگ خاص چگونه کار می کند. در میان چیزهای دیگر، هر کلاس یک ماژول کوچک است که باید حداقل به بقیه متصل شود. این جداسازی تعداد تغییراتی را که باید هنگام اضافه کردن منطق اضافی به یک کلاس انجام دهیم، کاهش می دهد.اشیاء
کپسوله سازی
در اینجا اول از همه در مورد یکی از اصول OOP - encapsulation صحبت خواهیم کرد . بنابراین، پنهان کردن پیادهسازی به ایجاد یک لایه متد بین متغیرها خلاصه نمیشود (محدود کردن بدون فکر دسترسی از طریق روشهای منفرد، گیرندهها و تنظیمکنندهها، که خوب نیست، زیرا کل نقطه کپسولهسازی از بین رفته است). هدف پنهان کردن دسترسی، شکل دادن به انتزاعات است، به این معنا که کلاس روشهای عینی رایجی را ارائه میکند که از طریق آنها با دادههای خود کار میکنیم. اما کاربر نیازی ندارد دقیقاً بداند که چگونه با این داده ها کار می کنیم - کار می کند، و این خوب است.قانون دمتر
شما همچنین می توانید قانون Demeter را در نظر بگیرید: این مجموعه کوچکی از قوانین است که به مدیریت پیچیدگی در سطح کلاس و روش کمک می کند. بنابراین، بیایید فرض کنیم که یک شی داریمCar
و آن یک روش - move(Object arg1, Object arg2)
. طبق قانون دمتر، این روش محدود به فراخوانی است:
- روش های خود شی
Car
(به عبارت دیگر، این)؛ - روش های اشیاء ایجاد شده در
move
; - متدهای اشیاء ارسال شده به عنوان آرگومان -
arg1
,arg2
; - روش های اشیاء داخلی
Car
(همان).
ساختار داده ها
ساختار داده مجموعه ای از عناصر مرتبط است. هنگامی که یک شی را به عنوان یک ساختار داده در نظر می گیریم، مجموعه ای از عناصر داده ای است که توسط روش هایی پردازش می شوند که وجود آنها به طور ضمنی اشاره دارد. یعنی شیئی است که هدف آن ذخیره و عملیات (پردازش) داده های ذخیره شده است. تفاوت اصلی با یک شی معمولی این است که یک شی مجموعه ای از روش ها است که بر روی عناصر داده ای که وجود آنها ضمنی است عمل می کند. آیا می فهمی؟ در یک شی معمولی، جنبه اصلی روشها است و متغیرهای داخلی برای عملکرد صحیح آنها هدف قرار میگیرند، اما در ساختار داده برعکس است: روشها از عناصر ذخیره شده پشتیبانی میکنند و به کار با عناصر ذخیرهشده کمک میکنند، که در اینجا اصلیترین آنها هستند. یکی از انواع ساختار داده، شیء انتقال داده (DTO) است . این یک کلاس با متغیرهای عمومی است و هیچ روشی ندارد (یا فقط روشهای خواندن/نوشتن) که دادهها را هنگام کار با پایگاههای داده، کار با تجزیه پیامها از سوکتها و غیره ارسال میکند. به طور معمول، دادهها در چنین اشیایی برای مدت طولانی ذخیره نمیشوند و تقریباً بلافاصله به نهادی که برنامه ما با آن کار می کند تبدیل می شود. یک موجودیت نیز به نوبه خود یک ساختار داده است، اما هدف آن مشارکت در منطق تجاری در سطوح مختلف برنامه است، در حالی که DTO انتقال داده ها به/از برنامه است. مثال DTO:@Setter
@Getter
@NoArgsConstructor
public class UserDto {
private long id;
private String firstName;
private String lastName;
private String email;
private String password;
}
همه چیز واضح به نظر می رسد، اما در اینجا با وجود هیبریدها آشنا می شویم. هیبریدها اشیایی هستند که حاوی روش هایی برای مدیریت منطق مهم و ذخیره عناصر داخلی و روش های دسترسی (get/set) به آنها هستند. چنین اشیایی کثیف هستند و اضافه کردن روش های جدید را دشوار می کنند. شما نباید از آنها استفاده کنید، زیرا مشخص نیست که آنها برای چه چیزی در نظر گرفته شده اند - ذخیره عناصر یا انجام نوعی منطق. شما می توانید در مورد انواع ممکن از اشیاء در اینجا بخوانید .
اصول ایجاد متغیرها
بیایید کمی در مورد متغیرها فکر کنیم، یا بهتر است بگوییم، به این فکر کنیم که اصول ایجاد آنها چه می تواند باشد:- در حالت ایده آل، شما باید بلافاصله قبل از استفاده یک متغیر را اعلام و مقداردهی اولیه کنید (به جای ایجاد آن و فراموش کردن آن).
- در صورت امکان، متغیرها را به عنوان نهایی اعلام کنید تا از تغییر مقدار آنها پس از مقداردهی اولیه جلوگیری شود.
- متغیرهای شمارنده را فراموش نکنید (معمولاً از آنها در نوعی حلقه استفاده می کنیم
for
، یعنی نباید فراموش کنیم آنها را تنظیم مجدد کنیم ، در غیر این صورت می تواند کل منطق ما را خراب کند). - شما باید سعی کنید متغیرها را در سازنده مقداردهی اولیه کنید.
- اگر انتخابی بین استفاده از یک شی با یا بدون مرجع (
new SomeObject()
) وجود دارد، بدون ( ) را انتخاب کنید، زیرا این شی، پس از استفاده، در جمع آوری زباله بعدی حذف می شود و منابع را هدر نمی دهد. - طول عمر متغیرها را تا حد امکان کوتاه کنید (فاصله بین ایجاد یک متغیر و آخرین دسترسی).
- متغیرهای مورد استفاده در یک حلقه را بلافاصله قبل از حلقه، به جای در ابتدای روش حاوی حلقه، مقداردهی کنید.
- همیشه با محدودترین محدوده شروع کنید و فقط در صورت لزوم آن را گسترش دهید (شما باید سعی کنید متغیر را تا حد امکان محلی کنید).
- از هر متغیر فقط برای یک هدف استفاده کنید.
- از متغیرهایی با معانی پنهان اجتناب کنید (متغیر بین دو کار پاره شده است، یعنی نوع آن برای حل یکی از آنها مناسب نیست).
مواد و روش ها
بیایید مستقیماً به سمت اجرای منطق خود یعنی روش ها برویم.-
اولین قانون فشردگی است. در حالت ایدهآل، یک روش نباید از 20 خط تجاوز کند، بنابراین اگر، مثلاً، یک روش عمومی به طور قابل توجهی متورم شود، باید به فکر انتقال منطق جدا شده به روشهای خصوصی باشید.
-
قانون دوم این است که بلوکهای دستورات
if
،else
وwhile
غیره نباید خیلی تودرتو باشند: این امر خوانایی کد را به میزان قابل توجهی کاهش میدهد. در حالت ایده آل، تودرتو نباید بیشتر از دو بلوک باشد{}
.همچنین توصیه می شود که کد این بلوک ها فشرده و ساده باشد.
-
قانون سوم این است که یک متد باید فقط یک عملیات را انجام دهد. یعنی اگر روشی منطق پیچیده و متنوع را انجام دهد، آن را به روشهای فرعی تقسیم میکنیم. در نتیجه، خود روش یک نما خواهد بود که هدف آن فراخوانی سایر عملیات به ترتیب صحیح است.
اما اگر این عملیات برای ایجاد یک روش جداگانه بسیار ساده به نظر برسد، چه؟ بله، گاهی اوقات ممکن است شبیه شلیک گنجشک ها به بیرون از توپ به نظر برسد، اما روش های کوچک چندین مزیت را به همراه دارند:
- خواندن کد آسان تر؛
- روشها در طول توسعه پیچیدهتر میشوند و اگر روش در ابتدا ساده بود، پیچیده کردن عملکرد آن کمی آسانتر خواهد بود.
- پنهان کردن جزئیات پیاده سازی؛
- تسهیل استفاده مجدد از کد؛
- قابلیت اطمینان کد بالاتر
-
قانون رو به پایین این است که کد باید از بالا به پایین خوانده شود: هر چه کمتر، عمق منطق بیشتر باشد، و بالعکس، بالاتر، روشها انتزاعیتر میشوند. به عنوان مثال، دستورات سوئیچ کاملا غیر فشرده و نامطلوب هستند، اما اگر نمی توانید بدون استفاده از سوئیچ این کار را انجام دهید، باید سعی کنید آن را تا حد ممکن به پایین ترین سطح ممکن منتقل کنید.
-
آرگومان های روش - چند مورد ایده آل هستند؟ در حالت ایده آل، اصلاً وجود ندارد)) اما آیا واقعاً این اتفاق می افتد؟ با این حال، باید سعی کنید تا حد امکان تعداد آنها کمتر باشد، زیرا هر چه تعداد آنها کمتر باشد، استفاده از این روش آسان تر و آزمایش آن آسان تر است. اگر شک دارید، سعی کنید تمام سناریوهای استفاده از روشی با تعداد زیادی آرگومان ورودی را حدس بزنید.
-
به طور جداگانه، میخواهم روشهایی را برجسته کنم که دارای یک پرچم بولی به عنوان آرگومان ورودی هستند، زیرا این به طور طبیعی نشان میدهد که این روش بیش از یک عملیات را اجرا میکند (اگر درست است، یکی نادرست - دیگری). همانطور که در بالا نوشتم، این خوب نیست و در صورت امکان باید از آن اجتناب کرد.
-
اگر یک متد دارای تعداد زیادی آرگومان ورودی است (مقدار نهایی 7 است، اما باید بعد از 2-3 به آن فکر کنید)، باید برخی از آرگومان ها را در یک شی جداگانه گروه بندی کنید.
-
اگر چندین روش مشابه (بارگذاری بیش از حد) وجود داشته باشد ، پارامترهای مشابه باید به ترتیب ارسال شوند: این امر خوانایی و قابلیت استفاده را افزایش می دهد.
-
وقتی پارامترها را به یک متد ارسال می کنید، باید مطمئن باشید که همه آنها استفاده می شوند، در غیر این صورت استدلال برای چیست؟ آن را از رابط حذف کنید و تمام.
-
try/catch
طبیعتاً خیلی خوب به نظر نمی رسد، بنابراین یک حرکت خوب این است که آن را به یک روش متوسط جداگانه منتقل کنید (روشی برای رسیدگی به استثناها):public void exceptionHandling(SomeObject obj) { try { someMethod(obj); } catch (IOException e) { e.printStackTrace(); } }
GO TO FULL VERSION