اصل دوم پیاده سازی SOLID با کدهای دارت برای فریم ورک Flutter

Open Closed Principle
Open Closed Principle

“اصل باز/بسته “یا (Open Closed Principle) یا به طور خلاصه (OCP) دومین اصل از SOLID است و می گوید که: اجزای نرم افزار ( اعم از کلاس ها، ماژول ها و توابع) باید برای توسعه باز باشد، اما برای اصلاح بسته شود.

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

"بسته برای اصلاح" به این معنی است که وقتی کلاسی را توسعه دادید، هرگز نباید آن را تغییر دهید، مگر اینکه برای اصلاح جزئی اشکالات باشد.

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

راه های گسترش کلاس شامل ارث بری از کلاس و همینطور توسعه یا بازنویسی Method های مورد نیاز است و شیوه کار به طور کلی این است که شما به جای استفاده از کلاس‌های پایه تان، با refer دادن به abstract کلاس ها برای ایجاد وابستگی‌ها کارتان را انجام می دهید. ما می‌توانیم با ایجاد کلاس‌های جدید که روابط را پیاده‌سازی می‌کنند، عملکرد جدید را اضافه کنیم.

پیاده سازی به این روش، احتمال ایجاد مشکلات جدید در کدها را کاهش می دهد و منجر به تولید برنامه ای قوی تر می شود.



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

۱- مثال زیر کدی که OCP را نقض می کند.

قرار است برنامه ای بنویسیم که مساحت اشکال مختلف را محاسبه کند. یک کلاس برای مستطیل که دارای 2 ویژگی طول و عرض است ایجاد می کنیم. نام کلاس را Rectangle میگذاریم.

class Rectangle {
  final double length;
  final double width;
  Rectangle(this.width, this.length);
}

علاوه بر این، ما یک کلاس برای محاسبه مساحت این مستطیل ایجاد می کنیم که دارای یک متد محاسبه ()RectangleArea است که Rectangle را به عنوان پارامتر ورودی می گیرد و مساحت آن را محاسبه می کند.

class AreaCalculator {
  double calculateRectangleArea(Rectangle rectangle) {
    return rectangle.length * rectangle.width;
  }
}

تا اینجا که خوب بود. حالا فرض کنید باید برای شکل دوم (که دایره است) نیز، کد مربوطه را بنویسیم. بنابراین، ما سریع یک کلاس Circle با ویژگی radius (به عنوان شعاع) ایجاد می کنیم.

class Circle {
  final double radius;
  Circle(this.radius);
}

سپس کلاس AreaCalculator را تغییر می دهیم تا محاسبات دایره را از طریق یک متد جدید به نام ()calculatCircleArea انجام دهد.

class AreaCalculator {
  double calculateRectangleArea(Rectangle rectangle) {
    return rectangle.length * rectangle.width;
  }

  double calculateCircleArea(Circle circle) {
    return (3.14159265359) * circle.radius * circle.radius;
  }
}

بد شد ! اگر توجه داشته باشید اشکالاتی در راه حل ما در بالا وجود دارد.

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

اکنون که با مشکل از نزدیک روبرو شدیم و درک بهتری از موضوع داریم، بیایید این بار با رعایت اصل باز/بسته، ایرادات برنامه فوق را برطرف کنیم.

۲- مثال: کدی که OCP را رعایت می کند.

اول از همه، ما کد را توسعه پذیر می کنیم. برای این کار ابتدا باید یک شکل از نوع پایه تعریف کنیم و اینترفیس شکل دایره و مستطیل را پیاده سازی کنیم. کد زیر را مشاهده کنید!

abstract class Shape {
  double calculateArea();
}

class Rectangle implements Shape {
  late double length;
  late double width;

  @override
  double calculateArea() {
    return length * width;
  }
}

class Circle implements Shape {
  late double radius;

  @override
  double calculateArea() {
    return (3.14159265359) * radius * radius;
  }
}

همانطور که قبلا گفتیم، یک شکل اصلی وجود دارد به نام کلاس Shape که سایر اشکال از آن ارث بری می کنند. کلاس Shape دارای یک متد انتزاعی محاسبه ()calculateArea است. دایره و مستطیل هر دو پیاده سازی خاص خود را برای ()calculateArea دارند . اگر در آینده بخواهیم مساحت اشکال دیگری مانند مثلث، مربع و غیره را محاسبه کنیم، این کار بدون تغییر کلاس Shape امکان پذیر خواهد بود.

نکته آخر Consumer یا استفاده کننده نهایی این اشکال است. استفاده کننده نهایی، کلاس AreaCalculator خواهد بود که اکنون به این شکل است.

class AreaCalculator {
  double calculateShapeArea(Shape shape) {
    return shape.calculateArea();
  }
}

این کلاس AreaCalculator اکنون به طور کامل ایرادات مثال قبل را برطرف می کند و یک راه حل Clean ارائه می دهد که به اصل باز-بسته پایبند است.


سوال : مزایای استفاده از اصل بسته باز چیست؟

جواب: توسعه پذیری آسان تر / نگهداری آسان تر / انعطاف پذیری


سوال : محدودیت های Open Closed Principle چیست؟

جواب: زمانی که در کد تغییری را به عنوان مورد جدید اضافه می کنیم، باید قبل و بعد از آن , Unit Test را انجام دهیم که از صحت اجرای سایر کدها مطمئن شویم.


سوال : الگوهای طراحی که از اصل بسته باز پیروی می کنند کدامند؟

جواب: چندین الگوی طراحی وجود دارد که به ما کمک می کند تا کد را بدون تغییر آن گسترش دهیم. به عنوان مثال، الگوی Decorator به ما پیشنهاد می دهد که از اصل Open Close پیروی کنیم. علاوه بر این، ممکن است از روش Factory، الگوی Strategy و الگوی Observer برای طراحی برنامه‌ای با حداقل تغییرات در کد موجود استفاده کنیم.


در مقالات بعدی جزئیات پیاده سازی هر بخش از Solid با کدهای دارت رو با همدیگر بررسی می کنیم.

مقدمه ای بر SOLID

توضیح اصل اول : Single Responsibility Principle

توضیح اصل دوم : Open Closed Principle

توضیح اصل سوم : Liskov Substitution Principle

توضیح اصل چهارم : Interface Segregation Principle

توضیح اصل پنجم : Dependency Inversion Principle