شرح مبادئ SOLID - المبدأ الرابع Interface Segregation Principle

0

هذا المبدأ من أسهل مبادئ SOLID وهو ينص على أن العميل Client والمقصود به في هذا السياق الكود الذي سيطبق واجهة Interface معينة، ليس ملزما بأن يقوم بتعريف كافة الوظائف المذكورة فيها، وإنما ينبغي أن يطبق فقط ما يحتاجه، وذلك لتفادي الوقوع في انتهاك لمبدأ Liskov Substitution Principle.

لماذا نحتاج إلى مبدأ فصل الواجهات ISP
كما ذكرنا في الفقرة السابقة فإننا نحتاج إلى مبدأ ISP من أجل أن نتفادى الوقوع في أخطاء منهجية تخرق مبدأ LSP، بحيث نقوم ببناء Interface تحتوي على مجموعة من الوظائف التي من الوارد أن الكود العميل لن يحتاجها كلها، وبالتالي ستبقى عدة وظائف Not Implemented مما سيضر بالبرنامج.
كما أن الواجهات الكبيرة Large Interfaces تضر بجودة التصميم، الذي يشير إلى أن المكونات البرمجية ينبغي أن تكون صغيرة small، مركزة focused، ومتماسكة Cohesive، وبالتالي الاعتماد على واجهات كبيرة قد لا يحترم هذه المعايير.

متى نحتاج إلى مبدأ فصل الواجهات ISP
حينما تجد أنك جعلت الواجهة كبيرة جدا Large interface فهنا ينبغي أن تفكر في مبدأ Interface Segregation Principle، وحينما تجد بأن بعض الكلاسات تحتوي على وظائف غير مبرمجة Not implemented methods فمن الضروري أن تعتمد هذا المبدأ.

كيف نطبق مبدأ فصل الواجهات ISP
لتطبيق مبدأ فصل الواجهات Interface Segregation Principle، سنقوم بإضافة بعض الخصائص إلى مشروعنا NumberConverter للقيام ببعض العمليات الإضافية كتحويل البيانات الثانوية إلى نصوص، والعكس صحيح، وكذلك تحويل البيانات الست عشرية إلى نصوص والعكس صحيح.
سنأتي إلى مشروعنا NumberConverter ونقوم بإضافة واجهة Interface نسميها IConverter، سنضع فيها كافة الوظائف التي سنحتاج أن نقوم بتطبيقها على مستوى الكلاسات الخاصة بالتحويل.
فيما يلي محتوى الواجهة IConverter:

IConverter.cs:

    public interface IConverter
    {
        string BinaryToText(string binary);
        string TextToBinary(string text);
        string HexadecimalToText(string text);
        string TextToHexadecimal(string text);
    }

أول ملاحظة لهذه الواجهة أنها غير متماسكة Not cohesive لأنها تحتوي على وظائف مختلفة بعضها يعمل مع النظام الست عشري والبعض الآخر مع النظام الثماني.
لكن ما علينا، سنقوم بتطبيق هذه الواجهة على مستوى الفئتين BinaryConverter و HexadecimalConverter، وفيما يلي محتوى الكلاس BinaryConverter:

BinaryConverter.cs:

    public class BinaryConverter : Converter, IConverter
    {
        public BinaryConverter(int decimalNumber)
            : base(decimalNumber)
        {

        }

        public override string Convert()
        {
            return $"The result is: {System.Convert.ToString(DecimalNumber, 2)}";
        }

        public string BinaryToText(string binaryNumber)
        {
            binaryNumber = binaryNumber.Replace(" ", "");

            var list = new List<Byte>();

            for (int i = 0; i < binaryNumber.Length; i += 8)
            {
                String t = binaryNumber.Substring(i, 8);

                list.Add(System.Convert.ToByte(t, 2));
            }

            var result = list.ToArray();

            return Encoding.ASCII.GetString(result);
        }


        public string TextToBinary(string text)
        {
            var bytes = Encoding.ASCII.GetBytes(text);

            return string.Join(" ",
                bytes.Select(byt => System.Convert.ToString(byt, 2).PadLeft(8, '0')));
        }

        public string HexadecimalToText(string text)
        {
            throw new NotImplementedException();
        }

        public string TextToHexadecimal(string text)
        {
            throw new NotImplementedException();
        }
    }

كل الوظائف التي تم تطبيقها من الواجهة IConverter تحتوي على Implementation باستثناء الوظيفتين HexadecimalToText و TextToHexadecimal، مما يعني أننا خرقنا مبدأ Liskov Substitution، نفس المشكل سيتكرر مع الكلاس HexadecimalConverter وفيما يلي محتواها:

HexadecimalConverter.cs:

    public class HexadecimalConverter : Converter, IConverter
    {
        public HexadecimalConverter(int decimalNumber) : base(decimalNumber)
        {
        }

        public override string Convert()
        {
            return $"The result is: {DecimalNumber.ToString("X")}";
        }

        public string HexadecimalToText(string text)
        {
            text = text.Replace(" ", "");

            byte[] raw = new byte[text.Length / 2];

            for (int i = 0; i < raw.Length; i++)
            {
                raw[i] = System.Convert.ToByte(text.Substring(i * 2, 2), 16);
            }

            return Encoding.ASCII.GetString(raw); ;
        }

        public string TextToHexadecimal(string text)
        {
            byte[] bytes = Encoding.ASCII.GetBytes(text);

            var hexString = BitConverter.ToString(bytes);

            hexString = hexString.Replace("-", "");

            return hexString;
        }

        public string TextToBinary(string text)
        {
            throw new NotImplementedException();
        }

        public string BinaryToText(string binary)
        {
            throw new NotImplementedException();
        }
    }

لاحظ أن الوظيفتين TextToBinary و BinaryToText لا تحتويان على أي Implementation مما يعني أننا أيضا على مستوى هذا الكلاس انتهكنا مبدأ LSP.
هنا ينبغي أن نفكر في حل مناسب يضمن لنا تفادي الوقوع في انتهاك مبدأ LSP وفي نفس الوقت جعل الواجهة أكثر تماسكا.
الحل هنا هو أن نقوم بفصل حالات الواجهة IConverter ليصبح لدينا واجهتين، الأولى خاصة ب BinaryConverter والثانية خاصة ب HexadecimalConverter.
وفيما يلي محتوى الواجهة الجديدة التي أسميناها IBinaryConverter:

IBinaryConverter.cs:

    public interface IBinaryConverter
    {
        string BinaryToText(string binary);
        string TextToBinary(string text);
    }

لاحظ أن الواجهة أصبحت صغيرة، متماسكة ومفهومة بسرعة.
الآن سنقوم بتطبيقها بدل استعمال IConverter وبالتالي سنقوم بحذف الكود الزائد الذي يولد NotImplementedException.
نفس الأمر سنقوم به مع الواجهة الثانية IHexadecimalConverter والتي محتواها كالآتي:

IHexadecimalConverter.cs:

    public interface IHexadecimalConverter
    {
        string HexadecimalToText(string text);
        string TextToHexadecimal(string text);
    }
بهذه الكيفية نكون قد طبقنا مبدأ ISP بنجاح، وتفادينا أن نفرض على الكود العميل أن يقوم بتعريف وظائف لا يحتاجها.

خلاصة مبدأ فصل الواجهات ISP
هذا المبدأ يخبرنا بأن علينا تقديم واجهات مركزة تخدم حاجة العميل، دون إلزامه بوظائف لا يحتاجها وبالتالي سيتكرها Not implemented مما يؤدي إلى خرق مبدأ LSP وبالتالي حدوث مشاكل في البرنامج من جراء هذا التصميم السيء.

لا يوجد تعليقات

أضف تعليق