اصول SOLID در برنامه نویسی شی گرا قسمت دوم : Open Close Principle
ﺯﻣﺎﻥ ﻣﻄﺎﻟﻌﻪ: 8 دقیقه

اصول SOLID در برنامه نویسی شی گرا قسمت دوم : Open Close Principle

در این مقاله در ادامه ی مبحث SOLID قصد داریم به معرفی دومین اصل  یعنی Open Close Principle که به صورت کوتاه OCP هم می گویند،بپردازیم.با ما همراه باشید:

تعریف را اینگونه بیان کرده اند :

Objects or entities should be open for extension, but closed for modification.

منظور از OCP این است که برنامه نویس بایستی کلاس ها و اشیاء را طوری ایجاد نماید که همواره امکان گسترش (Extend) آن وجود داشته باشد اما برای گسترش نیازی به تغییر در اصله کلاس  نباشد .

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

با کمی دقت متوجه می شویم که OCP و SRP مکمل همدیگر هستند. البته به این معنی نیست که هر جا SRP را رعایت کرده باشیم پس OCP را نیز رعایت کرده ایم. اما با رعایت هر یک از اصول دستیابی به مورد دیگر راحت تر و ساده تر خواهد بود.

به مثال زیر توجه کنید :


class AreaCalculator {

    protected $shapes;

    public function __construct($shapes = array()) {

        $this->shapes = $shapes;

    }


    public function sum() {

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

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

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

        } else if(is_a($shape, 'Circle')) {

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

        }

    }

    return array_sum($area);

}

    public function output() {

        return implode('', array(

            "

", "Sum of the areas of provided shapes: ", $this->sum(), "

PHP

"

        ));

    }

}

همانطور که در جلسه قبلی هم ذکر شد : یک کلاس به نام AreaCalculator داریم که  مسؤل محاسبه ی جمع , محیط های اشکال هندسی هست .

اگر بخواهیم در آینده , متد sum , محیط های اشکال بیشتر و متنوع تری را باهم جمع کند نیاز است تا از if/else blocks های بیشتری استفاده کنیم زیرا فعلا فقط به Square و Circle اشاره کردیم . این اضافه کردن if/else blocks در واقع برخلاف اصل Open Close Principle است .پس راه حل چیست ؟

یکی از راه های خوبی که می توانیم این مشکل را حل کنیم این است که منطق محاسباتی محیط ها را از متد sum جدا کنیم و به کلاس های خود اشکال هندسی ببریم .مثلا برای

کلاس Square :

class Square {

    public $length;

    public function __construct($length) {

        $this->length = $length;

    }

    public function area() {

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

    }

}

همانطور که مشاهده می کنید در خط 8 منطق محاسبه ی محیط را در قالب متد area  به کلاس square اضافه کردیم . همین کار را باید برای کلاس Circle انجام دهیم .وقتی منطق

محاسبه ی محیط را به داخل کلاس های اشکال بردیم , متد sum ای که در AreaCalculator  قرار داشت را  به صورت زیر تغییر می دهیم :

public function sum() {

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

        $area[] = $shape->area;

    }

    return array_sum($area);
}

مشکل اولیه ما حل شد ..به راحتی برای هر شکل هندسی جدیدی یه کلاس اضافه می کنیم و متد area را داخل اش قرار می دهیم .

حالا یه مشکل دیگه ؟ از کجا متوجه شیم کلاسی که به AreaCalculator پاس داده می شود کلاسی است که مربوط به یک shape است و متد area را دارد ؟

برای برطرف کردن این مشکل فقط کافی است یک interface بسازیم و کلاس های مورد نظر که اشکال ما هستند را از این interface در واقع implements نمایم.

interface ShapeInterface {

    public function area();

}

class Circle implements ShapeInterface {

    public $radius;



    public function __construct($radius) {

        $this->radius = $radius;

    }

    public function area() {

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

    }

}

در خط 1 interface را تعریف کردیم و در ادامه کلاس هایمان را از این اینترفیس implements کردیم تا مجبور شود متد area و بقیه چیزهای لازم را داشته باشد .

حالا  برای چک کردن اینکه کلاس های وارد شده یک نوع shape باشند به راحتی می توانیم  چک کنیم آیا کلاس های ارسال شده از نوع اینترفیس ShapeInterface هستند یا خیر .

public function sum() {

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

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

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

            continue;

        }

        throw new AreaCalculatorInvalidShapeException;

    }

    return array_sum($area);

}

توانستیم اصل Open Close Principle را در کلاسمان ایجاد نماییم تا وابستگی را از بین ببریم .

مثال 2 :

به عنوان مثال کلاس های زیر را در نظر بگیرید :

class TXTFile {

    public $length;

    public $sent;
}



class Progress {

    private $file;

    function __construct(TXTFile $file) {

        $this->file = $file;

    }

    function getAsPercent() {

        return $this->file->sent * 100 / $this->file->length;

    }

}

این روش کد نویسی اصل  OCP را نقض می کند. چرا؟ علت این است که کلاس Progress متغیر file از نوع TXTFile را به عنوان ورودی دریافت می کند و نوع دیگری از فایل ها را نمی شناسد. پس اگر در آینده بخواهیم به جز فایل txt فایل دیگری مثل mp3 به این بخش اضافه کنیم باید کلاس Progress را تغییر دهیم تا این نوع از فایل را نیز شناسایی کند.

شاید بگویید که خوب اگر نوع متغیر را در ورودی Progress مشخص نکنیم, میتوانیم در آینده کلاس دیگری مانند mp3 را به عنوان ورودی برای Progress ارسال کنیم. یعنی کد ما به شکل زیر شود :

class Progress {

    private $file;

    function __construct($file) {

        $this->file = $file;

    }

    function getAsPercent() {

        return $this->file->sent * 100 / $this->file->length;

    }

}

اما این هم قابل قبول نیست. اول اینکه دیباگ کردن این کد کمی سخت است. چرا؟ برای اینکه ممکن ورودی اشتباهی (مثلا یک رشته) برای Progress ارسال شود. و خطایی که دریافت می کنید چیزی مشابه زیر خواهد بود:

Trying to get property of non-object.

یعنی اینکه متغیر sent یا length را از چیزی غیر از یک کلاس(در اینجا رشته) می خواهید دریافت کنید که این اشتباه است و باعث خطا می شود. خوب دو چیز را باید بررسی کنید. اول اینکه مجبورید به کلاس Progress سری بزنید تا ببینید چه کدی در حال اجرا شدن است که چنین خطایی می دهد. دوم اینکه تشخیص اینکه آیا نوع متغیر ورودی اشتباه بوده یا اینکه متغیر ورودی مقدار اشتباهی دارد نیاز به بررسی دارد.

اما اگر نوع ورودی مشخص باشد, مانند کلاس Progress که در ابتدا نوشتیم امکان ارسال ورودی اشتباه وجود ندارد و خطای واضحی مشخص می کند که نوع ورودی اشتباه است. بنابراین دیباگ کردن در مرحله اول راحت تر می شود و علاوه بر آن انجام unit test بر روی این کلاس آسان تر خواهد بود.

پس به این نتیجه می رسیم که حذف کردن نوع ورودی از متد سازنده کلاس Progress بهترین کار نیست. روش دیگری که می توانیم انجام دهیم این است که مشخص کنیم تمام ورودی هایی که به کلاس Progress خواهیم داد از یک استاندارد پیروی می کنند. وقتی صحبت از استاندارد بین کلاس ها می شود معمولا پای یک اینترفیس یا یک کلاس انتزاعی(abstract) در میان است!

interface MeasurableInterface {

    function getLength();

    function getSent();

}

class File implements MeasurableInterface {

    private $length;

    private $sent;



    public $filename;

    public $owner;

    function setLength($length) {

        $this->length = $length;

    }

    function getLength() {

        return $this->length;

    }

    function setSent($sent) {

        $this->sent = $sent;

    }

    function getSent() {

        return $this->sent;

    }

    function getRelativePath() {

        return dirname($this->filename);

    }

    function getFullPath() {

        return realpath($this->getRelativePath());

    }

}

class Progress {

    private $measurableContent;

    function __construct(MeasurableInterface $measurableContent) {

        $this->measurableContent = $measurableContent;

    }

    function getAsPercent() {

        return $this->measurableContent->getSent() * 100 / $this->measurableContent->getLength();

    }

}

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

چه امتیازی برای این مقاله میدهید؟

خیلی بد
بد
متوسط
خوب
عالی
5 از 1 رای

/@roocketir

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

دیدگاه و پرسش

برای ارسال دیدگاه لازم است وارد شده یا ثبت‌نام کنید ورود یا ثبت‌نام

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

در حال دریافت نظرات از سرور، لطفا منتظر بمانید