اصول SOLID چیست و چه کاربردی در برنامه‌نویسی شی گرا دارد؟

اصول SOLID

آنچه در مقاله می‌خوانید

اصول SOLID یکی از مهم‌ترین مفاهیم در برنامه‌نویسی شی‌گرا (OOP) است که هدف اصلی آن ارتقاء طراحی نرم‌افزار و تسهیل نگهداری و توسعه آن است. این اصول به برنامه‌نویسان کمک می‌کنند تا کدهایی نوشته و طراحی کنند که خوانا، قابل نگهداری و توسعه‌پذیر باشند. در این مقاله به بررسی اصول SOLID در برنامه نویسی می‌پردازیم و کاربرد آن‌ها را در بهبود کیفیت نرم‌افزارها و کدنویسی مؤثر بررسی خواهیم کرد. SOLID در برنامه‌نویسی نه‌تنها به معنای کد بهتر و شفاف‌تر است، بلکه به افزایش بهره‌وری تیم‌های توسعه و کاهش هزینه‌های طولانی‌مدت نگهداری سیستم‌ها نیز کمک می‌کند.

توجه: این اصول می‌توانند در زبان‌های برنامه‌نویسی مختلف کاربرد داشته باشند، اما کدهای نمونه موجود در این مقاله با استفاده از PHP نوشته خواهند شد.

اصل تک‌مسئولیتی (Single-Responsibility Principle)

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

برای مثال، تصور کنید یک برنامه داریم که مجموعه‌ای از اشکال؛ دایره‌ها و مربع‌ها؛ را دریافت می‌کند و مساحت مجموع همه اشکال در مجموعه را محاسبه می‌کند.

اول، باید کلاس‌های اشکال را ایجاد کرده و پارامترهای لازم را در constructors تنظیم کنید.

برای مربع‌ها، نیاز دارید تا طول یک ضلع را بدانید:

class Square

{

    public $length;




    public function __construct($length)

    {

        $this->length = $length;

    }

}

 

برای دایره‌ها، نیاز دارید تا شعاع را بدانید:

class Circle

{

    public $radius;




    public function __construct($radius)

    {

        $this->radius = $radius;

    }

}

سپس، کلاس AreaCalculator را ایجاد کرده و منطق جمع کردن مساحت‌های تمام اشکال داده شده را بنویسید. مساحت یک مربع با طول ضلع به توان ۲ محاسبه می‌شود، و مساحت یک دایره با π ضربدر شعاع به توان ۲ محاسبه می‌شود.

class AreaCalculator 

{

    protected $shapes;




    public function __construct($shapes = [])

    {

        $this->shapes = $shapes;

    }




    public function sum()

    {

        $area = [];

        foreach ($this->shapes as $shape) {

            if (is_a($shape, 'Square')) {

                $area[] = pow($shape->length, 2);

            } elseif (is_a($shape, 'Circle')) {

                $area[] = pi() * pow($shape->radius, 2);

            }

        }




        return array_sum($area);

    }




    public function output()

    {

        return implode('', [

            '',

            'Sum of the areas of provided shapes: ',

            $this->sum(),

            '',

        ]);

    }

}

برای استفاده از کلاس AreaCalculator، باید آن را نمونه‌سازی کنید، آرایه‌ای از اشکال را به آن بدهید و خروجی را در انتهای صفحه نمایش دهید.

در اینجا مثالی با مجموعه‌ای از سه شکل آورده شده است:

  • یک دایره با شعاع ۲
  • یک مربع با طول ضلع ۵
  • یک مربع دیگر با طول ضلع ۶
$shapes = [

  new Circle(2),

  new Square(5),

  new Square(6),

];




$areas = new AreaCalculator($shapes);




echo $areas->output();

کلاس AreaCalculator که به این صورت طراحی شده، مشکلاتی از نظر اصل تک‌مسئولیتی در اصول SOLID دارد:

  1. این کلاس مستقیما مساحت هر شکل خاص را در متد sum() محاسبه می‌کند. این بدان معناست که اگر شکل جدیدی (مانند مثلث) معرفی کنید، باید کلاس AreaCalculator را تغییر دهید. این موضوع با اصل SRP از اصول SOLID مغایرت دارد (کلاس AreaCalculator برای هر نوع شکل جدید نیاز به تغییر دارد).
  2. همچنین، مسئولیت نمایش داده‌ها (مانند HTML، JSON یا متن ساده) را در متد output() دارد. این مسئله یک نگرانی جداگانه از محاسبه است.

حالا بیایید اصل SRP را در دو مرحله پیاده‌سازی کنیم تا این مشکلات را برطرف کنیم.

مرحله اول: تخصیص مسئولیت به اشکال

در طراحی شیءگرا و طبق اصل تک‌مسئولیتی (SRP)، مسئولیت هر شکل باید شامل دانستن ویژگی‌های خودش و نحوه انجام عملیات مربوط به آن ویژگی‌ها باشد، مانند محاسبه مساحت خود. کلاس AreaCalculator نباید نیاز داشته باشد که فرمول خاص هر شکل را بداند.

برای حل این مشکل، منطق محاسبه مساحت را به داخل کلاس هر شکل می‌بریم:

در اینجا متد area() در کلاس مربع (Square) تعریف شده است:

class Square{

    public $length;




    public function __construct($length)

    {

        $this->length = $length;

    }




    public function area()

    {

        return pow($this->length, 2);

    }

}

و در اینجا متد area() در کلاس دایره (Circle) تعریف شده است:

class Circle{

    public $radius;




    public function __construct($radius)

    {

        $this->radius = $radius;

    }




    public function area()

    {

        return pi() * pow($this->radius, 2);

    }

}

حال، متد sum() در کلاس AreaCalculator می‌تواند مجددا نوشته شود تا به سادگی از هر شکل بخواهد که مساحت خود را محاسبه کند، بدون اینکه نیازی به دانستن فرمول خاص محاسبه باشد:

class AreaCalculator{
protected $shapes;

public function __construct($shapes = [])
{
$this->shapes = $shapes;
}

public function sum()
{
$area = [];
foreach ($this->shapes as $shape) {
// Each shape is now responsible for its own area calculation
$area[] = $shape->area();
}

return array_sum($area);
}

public function output()
{
// This method still handles output, which we'll address next
return implode('', [
'',
'Sum of the areas of provided shapes: ',
$this->sum(),
'',
]);
}
}

اکنون، افزودن نوع شکل جدید (مثلا مثلث) نیازی به تغییر متد sum() در کلاس AreaCalculator ندارد. ما اصل SRP را با اختصاص مسئولیت محاسبه مساحت به هر شکل، پیاده‌سازی کرده‌ایم.

مرحله دوم: جداسازی محاسبه از خروجی

حتی پس از مرحله اول، کلاس AreaCalculator همچنان دو مسئولیت اصلی دارد:

  1. محاسبه مجموع مساحت‌ها: این عملکرد اصلی ریاضی آن است.
  2. مدیریت خروجی داده‌ها/نمایش: این شامل فرمت‌بندی مجموع برای نمایش است (مثلا HTML، JSON یا متن ساده).

تصور کنید که خروجی باید به فرمت دیگری مانند JSON تبدیل شود. در حال حاضر، کلاس AreaCalculator تمام این منطق نمایش را مستقیما در متد output() خود مدیریت می‌کند. اگر نیاز به خروجی JSON باشد، باید منطق بیش‌تری به output() اضافه کنیم یا متد جدیدی به نام outputJson() ایجاد کنیم. این به این معناست که اگر نیاز به تغییر فرمت خروجی باشد، کلاس AreaCalculator نه تنها باید تغییر کند بلکه باید به تغییرات فرمت نیز پاسخ دهد.

کلاس AreaCalculator باید به طور اصلی فقط نگران محاسبه مجموع مساحت‌های اشکال داده شده باشد و نباید اهمیتی به این بدهد که کاربر JSON می‌خواهد یا HTML.

برای حل این مشکل، می‌توانیم اصل SRP را با جداسازی مسئولیت‌ها اعمال کنیم. شما می‌توانید یک کلاس جداگانه به نام SumCalculatorOutputter (یا کلاس‌هایی مشابه) ایجاد کنید و از این کلاس جدید برای مدیریت منطق مورد نیاز جهت نمایش داده‌ها به کاربر استفاده کنید:

class SumCalculatorOutputter

{

    protected $calculator;




    public function __construct(AreaCalculator $calculator)

    {

        $this->calculator = $calculator;

    }




    public function JSON()

    {

        $data = [

            'sum' => $this->calculator->sum(),

        ];




        return json_encode($data);

    }




    public function HTML()

    {

        return implode('', [

            '',

            'Sum of the areas of provided shapes: ',

            $this->calculator->sum(),

            '',

        ]);

    }

}


کلاس SumCalculatorOutputter به این صورت کار خواهد کرد:

$shapes = [

  new Circle(2),

  new Square(5),

  new Square(6),

];




$areas = new AreaCalculator($shapes);

$output = new SumCalculatorOutputter($areas);




echo $output->JSON();

echo $output->HTML();

اکنون، کلاس AreaCalculator مسئولیت واحد خود را دارد: محاسبه مجموع مساحت‌ها. همچنین کلاس SumCalculatorOutputter مسئولیت واحد خود را دارد: فرمت‌بندی و نمایش نتایج محاسبات. این جداسازی مسئولیت‌ها، اصل تک‌مسئولیتی را برای این دو کلاس برآورده می‌کند.

اصل باز و بسته (Open-Closed Principle)

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

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

بیایید به کلاس AreaCalculator نگاه کنیم و روی متد sum تمرکز کنیم:

class AreaCalculator

{

    protected $shapes;




    public function __construct($shapes = [])

    {

        $this->shapes = $shapes;

    }




    public function sum()

    {

        foreach ($this->shapes as $shape) {

            if (is_a($shape, 'Square')) {

                $area[] = pow($shape->length, 2);

            } elseif (is_a($shape, 'Circle')) {

                $area[] = pi() * pow($shape->radius, 2);

            }

        }




        return array_sum($area);

    }

}

حالا فرض کنید کاربر بخواهد مساحت اشکال اضافی مانند مثلث‌ها، پنج‌ضلعی‌ها، شش‌ضلعی‌ها و غیره را محاسبه کند. در این صورت، باید دائم این فایل را ویرایش کرده و بلوک‌های if/else بیشتری اضافه کنید. این کار اصول باز-بسته را نقض می‌کند.

برای بهبود متد sum، می‌توان منطق محاسبه مساحت هر شکل را از متد کلاس AreaCalculator جدا کرده و به کلاس هر شکل متصل کرد.

در اینجا متد area که در کلاس Square تعریف شده است را مشاهده می‌کنید:

class Square

{

    public $length;




    public function __construct($length)

    {

        $this->length = $length;

    }




    public function area()

    {

        return pow($this->length, 2);

    }

}


و در اینجا متد area که در کلاس Circle تعریف شده است:

class Circle

{

    public $radius;




    public function __construct($radius)

    {

        $this->radius = $radius;

    }




    public function area()

    {

        return pi() * pow($this->radius, 2);

    }

}

حال، متد sum در کلاس AreaCalculator می‌تواند به شکل زیر بازنویسی شود:

class AreaCalculator

{

    // ...




    public function sum()

    {

        foreach ($this->shapes as $shape) {

            $area[] = $shape->area();

        }




        return array_sum($area);

    }

}

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

با این حال، مشکل دیگری پیش می‌آید. چگونه می‌توانید اطمینان حاصل کنید که شیء وارد شده به AreaCalculator واقعا یک شکل است و یا اینکه شکل مورد نظر متدی به نام area دارد؟

کدنویسی بر اساس یک رابط (Interface) بخشی اساسی از اصول SOLID است.

ایجاد یک ShapeInterface که از متد area پشتیبانی کند، به شکل زیر خواهد بود:

interface ShapeInterface

{

    public function area();

}

کلاس‌های اشکال خود را طوری تغییر دهید که ShapeInterface را پیاده‌سازی کنند.

در اینجا به‌روزرسانی کلاس Square را مشاهده می‌کنید:

class Square implements ShapeInterface

{

    // ...

}

به‌روزرسانی کلاس Circle:

class Circle implements ShapeInterface

{

    // ...

}

در متد sum در کلاس AreaCalculator می‌توانید بررسی کنید که آیا اشکالی که وارد شده‌اند، واقعا نمونه‌هایی از ShapeInterface هستند یا نه؛ در غیر این صورت، یک استثنا ایجاد کنید:

class AreaCalculator

{

    // ...




    public function sum()

    {

        foreach ($this->shapes as $shape) {

            if (is_a($shape, 'ShapeInterface')) {

                $area[] = $shape->area();

                continue;

            }




            throw new AreaCalculatorInvalidShapeException();

        }




        return array_sum($area);

    }

}


این راه‌حل اصول باز-بسته را به درستی رعایت می‌کند.

اصل جانشینی لیسکوف (Liskov Substitution Principle – LSP)

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

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

فرض کنید q(x) ویژگی‌ای است که برای اشیاء از نوع T قابل اثبات است. سپس q(y) باید برای اشیاء از نوع S که S یک زیرنوع از T است، قابل اثبات باشد.

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

برای توضیح بیشتر، از مثال کلاس AreaCalculator استفاده می‌کنیم. حالا فرض کنید یک کلاس جدید به نام VolumeCalculator داریم که از کلاس AreaCalculator ارث می‌برد:

class VolumeCalculator extends AreaCalculator
{
public function __construct($shapes = [])
{
parent::__construct($shapes);
}

public function sum()
{
// logic to calculate the volumes and then return an array of output
return [$summedData];
}
}

 

به یاد بیاورید که کلاس SumCalculatorOutputter شبیه این است:

class SumCalculatorOutputter {
protected $calculator;

public function __construct(AreaCalculator $calculator) {
$this->calculator = $calculator;
}

public function JSON() {
$data = array(
'sum' => $this->calculator->sum(),
);

return json_encode($data);
}

public function HTML() {
return implode('', array(
'',
'Sum of the areas of provided shapes: ',
$this->calculator->sum(),
''
));
}
}

 

اگر شما سعی کنید مثالی مانند این را اجرا کنید:

$areas = new AreaCalculator($shapes);

$volumes = new VolumeCalculator($solidShapes);




$output = new SumCalculatorOutputter($areas);

$output2 = new SumCalculatorOutputter($volumes);

 

زمانی که متد HTML را روی شیء $output2 فراخوانی کنید، یک خطای E_NOTICE دریافت خواهید کرد که شما را از تبدیل آرایه به رشته آگاه می‌کند.

برای رفع این مشکل، به جای بازگرداندن یک آرایه از متد sum در کلاس VolumeCalculator، باید $summedData را بازگردانید:

class VolumeCalculator extends AreaCalculator
{
public function __construct($shapes = [])
{
parent::__construct($shapes);
}

public function sum()
{
// logic to calculate the volumes and then return a value of output
return $summedData;
}
}

در این جا، $summedData می‌تواند یک عدد اعشاری (float)، دوتایی (double) یا صحیح (integer) باشد.

این کار اصل جانشینی لیسکوف را برآورده می‌کند.

اصل تفکیک رابط (Interface Segregation Principle – ISP)

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

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

برای ادامه مثال قبلی از ShapeInterface، فرض کنید که نیاز دارید اشکال سه‌بعدی جدیدی مانند Cuboid و Spheroid را پشتیبانی کنید، و این اشکال باید هم مساحت و هم حجم را محاسبه کنند.

حالا فرض کنید که شما بخواهید رابط ShapeInterface را به گونه‌ای تغییر دهید که یک قرارداد جدید اضافه کنید:

interface ShapeInterface

{

    public function area();




    public function volume();

}


حال، هر شکلی که ایجاد کنید باید متد volume را پیاده‌سازی کند، اما شما می‌دانید که مربع‌ها اشکال دو‌بعدی هستند و حجم ندارند، بنابراین این رابط مجبور خواهد کرد که کلاس Square متدی را پیاده‌سازی کند که به آن نیاز ندارد.

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

ما ShapeInterface را برای اشکال دو‌بعدی که فقط مساحت دارند نگه می‌داریم:

interface ShapeInterface

{

    public function area();

}

و یک رابط جدید به طور خاص برای اشکال سه‌بعدی که می‌توانند حجم را محاسبه کنند ایجاد می‌کنیم:

interface ThreeDimensionalShapeInterface

{

    public function volume();

}

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

برای اشکال دو‌بعدی:

class Square implements ShapeInterface { // Only implements area()
public $length;

public function __construct($length) {
$this->length = $length;
}

public function area() {
return pow($this->length, 2);
}
}

 

برای اشکال سه‌بعدی (مثلا Cuboid) که هم مساحت سطح و هم حجم دارند، کلاس باید هر دو رابط مرتبط را پیاده‌سازی کند.

class Cuboid implements ShapeInterface, ThreeDimensionalShapeInterface
{
public function area()
{
// calculate the surface area of the cuboid
}

public function volume()
{
// calculate the volume of the cuboid
}
}

در این حالت، کلاس Square (و هر شکل دو‌بعدی دیگری) دیگر مجبور به پیاده‌سازی متد volume() که به آن نیازی ندارد، نخواهد بود. این کلاس فقط رابط ShapeInterface را پیاده‌سازی می‌کند و متد area() را در اختیار دارد.

این رویکرد اطمینان حاصل می‌کند که مشتری‌ها مجبور نیستند به رابط‌ها (یا متدهای داخل رابط‌ها) وابسته باشند که از آن‌ها استفاده نمی‌کنند، که منجر به کد تمیزتر خواهد شد

اصل معکوس‌سازی وابستگی (Dependency Inversion Principle – DIP)

اصل معکوس‌سازی وابستگی از اصول SOLID می‌گوید که کلاس‌های سطح بالا نباید به کلاس‌های سطح پایین وابسته باشند، بلکه هر دو باید به انتزاع (Abstractions) وابسته باشند. این اصل به این معناست که سیستم شما نباید به جزئیات وابسته باشد، بلکه باید از انتزاع‌ها استفاده کند.

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

در اینجا مثالی از کلاسی به نام PasswordReminder آورده شده است که به یک پایگاه داده MySQL متصل می‌شود:

class MySQLConnection
{
public function connect()
{
// handle the database connection
return 'Database connection';
}
}

class PasswordReminder
{
private $dbConnection;

public function __construct(MySQLConnection $dbConnection)
{
$this->dbConnection = $dbConnection;
}
}

 

در اینجا، کلاس MySQLConnection یک ماژول سطح پایین است و کلاس PasswordReminder ماژول سطح بالا است. طبق تعریف اصل معکوس‌سازی وابستگی، که بیان می‌کند باید به انتزاع‌ها وابسته باشیم، این کد نقض این اصل است؛ چرا که کلاس PasswordReminder مجبور است به کلاس MySQLConnection وابسته باشد.

اگر بعدها تصمیم بگیرید که موتور پایگاه داده را تغییر دهید (مثلا از MySQL به PostgreSQL یا یک سرویس API)، باید کلاس PasswordReminder را نیز ویرایش کنید که این نقض اصل باز و بسته است، زیرا کلاس باید برای گسترش تغییر کند.

کلاس PasswordReminder نباید نگران باشد که چه نوع پایگاه داده‌ای در برنامه شما استفاده می‌شود. برای رفع این مشکل، می‌توانیم به جای وابستگی به کلاس‌های خاص، به یک رابط (interface) وابسته شویم، چرا که ماژول‌های سطح بالا و پایین باید به انتزاع‌ها وابسته باشند.

ابتدا، یک رابط برای اتصالات پایگاه داده تعریف می‌کنیم. این رابط (DBConnectionInterface) به عنوان انتزاع عمل می‌کند:

interface DBConnectionInterface

{

    public function connect();

}

 

این رابط متدی به نام connect دارد و کلاس MySQLConnection این رابط را پیاده‌سازی می‌کند. همچنین، به جای اشاره مستقیم به کلاس MySQLConnection در سازنده کلاس PasswordReminder، باید از رابط DBConnectionInterface استفاده کنیم. با این کار، مهم نیست که برنامه شما از کدام پایگاه داده استفاده می‌کند؛ کلاس PasswordReminder می‌تواند به راحتی به پایگاه داده متصل شود بدون هیچ مشکلی و اصل باز و بسته نقض نخواهد شد.

این رابط تنها اعلام می‌کند که یک شیء اتصال به پایگاه داده باید قادر به انجام چه کاری باشد (اتصال)، بدون اینکه مشخص کند چگونه این کار را انجام می‌دهد.

سپس، کلاس پیاده‌سازی‌شده MySQLConnection این رابط را پیاده‌سازی می‌کند و جزئیات اتصال به پایگاه داده MySQL را مشخص می‌کند:

class MySQLConnection implements DBConnectionInterface
{
public function connect()
{
// handle the database connection
return 'Database connection established';
}
}


حال در سازنده کلاس PasswordReminder، به جای اشاره مستقیم به کلاس خاص MySQLConnection، از رابط DBConnectionInterface استفاده می‌کنیم:

class PasswordReminder{
    private $dbConnection;

    public function __construct(DBConnectionInterface $dbConnection) // Type-hinting the interface
    {
        $this->dbConnection = $dbConnection;
    }

    public function remind() {
        $connectionStatus = $this->dbConnection->connect();
        return "Password reminder process initiated. Connection status: " . $connectionStatus;
    }
}

 

زمانی که از PasswordReminder استفاده می‌کنید، شیء‌ای از کلاسی که رابط DBConnectionInterface را پیاده‌سازی کرده است را به آن می‌دهید. کلاس PasswordReminder نیازی ندارد که نوع اتصال را بداند (چه MySQL، چه PostgreSQL و غیره)، فقط می‌داند که یک شیء دارد که تضمین می‌کند می‌تواند متد connect() را فراخوانی کند.

این نحوه استفاده در برنامه شما به صورت زیر خواهد بود:

// Create a concrete MySQL connection object


$mysqlConnector = new MySQLConnection();

// Inject the concrete MySQL connection object into the PasswordReminder


// The PasswordReminder only sees it as a DBConnectionInterface


$passwordReminder = new PasswordReminder($mysqlConnector);

echo $passwordReminder->remind(); // Output: Password reminder process initiated. Connection status: MySQL Database connection established.

اگر بعداً تصمیم بگیرید که به پایگاه داده PostgreSQL تغییر دهید، تنها کافی است که یک کلاس جدید PostgreSQLConnection ایجاد کنید که این رابط را نیز پیاده‌سازی کند و سپس در تنظیمات برنامه خود، کلاس پیاده‌سازی‌شده را تغییر دهید:

$pgConnector = new PostgreSQLConnection();

$passwordReminder = new PasswordReminder($pgConnector);




echo $passwordReminder->remind();

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

توجه داشته باشید که کلاس PasswordReminder خود نیاز به تغییر یا اصلاح نداشت وقتی که فناوری پایگاه داده تغییر کرد. هم ماژول سطح بالا PasswordReminder و هم ماژول سطح پایین MySQLConnection (یا PostgreSQLConnection) اکنون به انتزاع DBConnectionInterface وابسته‌اند، نه به جزئیات پیاده‌سازی یکدیگر. این امر سیستم را انعطاف‌پذیرتر، تست‌پذیرتر (شما می‌توانید اتصالات پایگاه داده شبیه‌سازی شده را تزریق کنید) و منطبق با اصل باز و بسته می‌کند.

جمع بندی

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

امتیاز شما به این مطلب
دیدن نظرات
small

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دو × 1 =

عضویت در خبرنامه مبین هاست
مطالب کدام دسته‌بندی‌ها برای شما جذاب‌تر است؟

آنچه در مقاله می‌خوانید

مقالات مرتبط
آموزش تجهیزات سرور و دیتاسنتر

دیتاسنتر فن آوا؛ آشنایی با ویژگی‌ها، استانداردها و خدمات مرکز داده فن‌آوا

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

خدمات مبین هاست