CodeGym /مدونة جافا /Random-AR /الطبقات الداخلية المتداخلة
John Squirrels
مستوى
San Francisco

الطبقات الداخلية المتداخلة

نشرت في المجموعة
أهلاً! سنتناول اليوم موضوعًا مهمًا - كيفية عمل الفئات المتداخلة في Java. تتيح لك Java إنشاء فئات داخل فئة أخرى:
class OuterClass {
    ...
    static class StaticNestedClass {
        ...
    }
    class InnerClass {
        ...
    }
}
تسمى هذه الفئات الداخلية متداخلة. وهي مقسمة إلى نوعين:
  1. فئات متداخلة غير ثابتة. وتسمى هذه أيضًا الطبقات الداخلية.
  2. فئات متداخلة ثابتة.
في المقابل، الطبقات الداخلية لديها فئتين فرعيتين متميزتين. بالإضافة إلى كون الطبقة الداخلية مجرد طبقة داخلية، فمن الممكن أن تكون أيضًا:
  • فئة محلية
  • فئة مجهولة
مشوش؟ :) حسنا. وهنا رسم تخطيطي للوضوح. ارجع إليها أثناء الدرس إذا وجدت نفسك فجأة في حيرة من أمرك! الطبقات الداخلية المتداخلة - 2في درس اليوم، سنناقش الفئات الداخلية (المعروفة أيضًا باسم الفئات المتداخلة غير الثابتة). لقد تم تسليط الضوء عليها بشكل خاص في المخطط العام حتى لا تضيع :) لنبدأ بالسؤال الواضح: لماذا يطلق عليهم الطبقات "الداخلية"؟ الجواب بسيط للغاية: لأنه يتم إنشاؤها داخل فئات أخرى. هنا مثال:
public class Bicycle {

   private String model;
   private int weight;

   public Bicycle(String model, int weight) {
       this.model = model;
       this.weight = weight;
   }

   public void start() {
       System.out.println("Let's go!");
   }

   public class Handlebar {

       public void right() {
           System.out.println("Steer right!");
       }

       public void left() {

           System.out.println("Steer left!");
       }
   }

   public class Seat {

       public void up() {

           System.out.println("Seat up!");
       }

       public void down() {

           System.out.println("Seat down!");
       }
   }
}
هنا لدينا الفصل Bicycle. يحتوي على حقلين وطريقة واحدة: start(). الطبقات الداخلية المتداخلة - 3وهو يختلف عن الفصل العادي في أنه يحتوي على فئتين: Handlebarو Seat. يتم كتابة التعليمات البرمجية الخاصة بهم داخل Bicycleالفصل. هذه فصول كاملة: كما ترون، كل واحد منهم لديه أساليبه الخاصة. عند هذه النقطة، قد يكون لديك سؤال: لماذا بحق السماء نضع فئة داخل أخرى؟ لماذا جعلهم الطبقات الداخلية؟ حسنًا، لنفترض أننا بحاجة إلى فصول منفصلة لمفاهيم المقود والمقعد في برنامجنا. بالطبع، ليس من الضروري بالنسبة لنا أن نجعلها متداخلة! يمكننا أن نجعل الطبقات العادية. على سبيل المثال، مثل هذا:
public class Handlebar {
   public void right() {
       System.out.println("Steer right!");
   }

   public void left() {

       System.out.println("Steer left");
   }
}

public class Seat {

   public void up() {

       System.out.println("Seat up!");
   }

   public void down() {

       System.out.println("Seat down!");
   }
}
سؤال جيد جدا! بالطبع، نحن لسنا مقيدين بالتكنولوجيا. القيام بذلك هو بالتأكيد خيار. الشيء المهم هنا هو التصميم الصحيح للفصول الدراسية من منظور برنامج معين والغرض منه. الطبقات الداخلية مخصصة لفصل كيان مرتبط بشكل لا ينفصم بكيان آخر. تعتبر المقاود والمقاعد والدواسات من مكونات الدراجة. منفصلة عن الدراجة، فهي لا معنى لها. إذا جعلنا كل هذه المفاهيم منفصلة عن الفئات العامة، لكان لدينا كود مثل هذا في برنامجنا:
public class Main {

   public static void main(String[] args) {
       Handlebar handlebar = new Handlebar();
       handlebar.right();
   }
}
حسنًا... من الصعب أيضًا شرح معنى هذا الرمز. لدينا بعض المقود الغامض (لماذا هو ضروري؟ لأكون صادقًا، ليس لدي أي فكرة). وهذا المقبض يستدير لليمين... من تلقاء نفسه، بدون دراجة... لسبب ما. ومن خلال فصل مفهوم المقود عن مفهوم الدراجة، فقدنا بعض المنطق في برنامجنا. باستخدام فئة داخلية، يبدو الرمز مختلفًا تمامًا:
public class Main {

   public static void main(String[] args) {

       Bicycle peugeot = new Bicycle("Peugeot", 120);
       Bicycle.Handlebar handlebar = peugeot.new Handlebar();
       Bicycle.Seat seat = peugeot.new Seat();

       seat.up();
       peugeot.start();
       handlebar.left();
       handlebar.right();
   }
}
إخراج وحدة التحكم:

Seat up! 
Let's go! 
Steer left! 
Steer right!
الآن ما نراه فجأة أصبح منطقيًا! :) لقد أنشأنا كائنًا للدراجة. لقد أنشأنا "شيئين فرعيين" للدراجة - المقود والمقعد. لقد رفعنا المقعد من أجل الراحة وانطلقنا: استخدام الدواسات والتوجيه حسب الحاجة! :) يتم استدعاء الأساليب التي نحتاجها على الكائنات المناسبة. كل شيء بسيط ومريح. في هذا المثال، يؤدي فصل المقود والمقعد إلى تحسين التغليف (نقوم بإخفاء البيانات المتعلقة بأجزاء الدراجة داخل الفئة ذات الصلة) ويتيح لنا إنشاء تجريد أكثر تفصيلاً. الآن دعونا ننظر إلى وضع مختلف. لنفترض أننا نريد إنشاء برنامج يحاكي متجر دراجات وقطع غيار للدراجات. الطبقات الداخلية المتداخلة - 4في هذه الحالة، لن يكون حلنا السابق ناجحًا. في متجر الدراجات، يكون كل جزء فردي من أجزاء الدراجة منطقيًا حتى عند فصله عن الدراجة. على سبيل المثال، سنحتاج إلى أساليب مثل "بيع الدواسات للعميل"، و"شراء مقعد جديد"، وما إلى ذلك. سيكون من الخطأ استخدام الفئات الداخلية هنا - فكل جزء فردي من أجزاء الدراجة في برنامجنا الجديد له معنى يقف على خاص بها: يمكن فصله عن مفهوم الدراجة. هذا هو بالضبط ما تحتاج إلى الانتباه إليه إذا كنت تتساءل عما إذا كان يجب عليك استخدام الفئات الداخلية أو تنظيم جميع الكيانات كفئات منفصلة. تعد البرمجة الشيئية جيدة لأنها تجعل من السهل نمذجة كيانات العالم الحقيقي. يمكن أن يكون هذا هو المبدأ التوجيهي الخاص بك عند تحديد ما إذا كنت تريد استخدام الفئات الداخلية أم لا. في المتجر الحقيقي، تكون قطع الغيار منفصلة عن الدراجات - وهذا أمر جيد. وهذا يعني أنه لا بأس أيضًا عند تصميم البرنامج. حسنًا، لقد اكتشفنا "الفلسفة" :) الآن دعنا نتعرف على الميزات "التقنية" المهمة للطبقات الداخلية. إليك ما تحتاج بالتأكيد إلى تذكره وفهمه:
  1. لا يمكن أن يوجد كائن من فئة داخلية بدون كائن من فئة خارجية.

    هذا منطقي: ولهذا السبب قمنا بإنشاء الصفوف Seatالداخلية Handlebarفي برنامجنا - حتى لا ينتهي بنا الأمر بمقود ومقاعد يتيمة.

    لا يتم تجميع هذا الرمز:

    public static void main(String[] args) {
    
       Handlebar handlebar = new Handlebar();
    }

    ميزة أخرى مهمة تتبع هذا:

  2. كائن من فئة داخلية لديه حق الوصول إلى متغيرات الفئة الخارجية.

    على سبيل المثال، دعونا نضيف متغيرًا int seatPostDiameter(يمثل قطر عمود المقعد) إلى فئتنا Bicycle.

    ثم في Seatالطبقة الداخلية، يمكننا إنشاء displaySeatProperties()طريقة تعرض خصائص المقعد:

    public class Bicycle {
    
       private String model;
       private int weight;
    
       private int seatPostDiameter;
    
       public Bicycle(String model, int weight, int seatPostDiameter) {
           this.model = model;
           this.weight = weight;
           this.seatPostDiameter = seatPostDiameter;
    
       }
    
       public void start() {
           System.out.println("Let's go!");
       }
    
       public class Seat {
    
           public void up() {
    
               System.out.println("Seat up!");
           }
    
           public void down() {
    
               System.out.println("Seat down!");
           }
    
           public void displaySeatProperties() {
    
               System.out.println("Seat properties: seatpost diameter = " + Bicycle.this.seatPostDiameter);
           }
       }
    }

    والآن يمكننا عرض هذه المعلومات في برنامجنا:

    public class Main {
    
       public static void main(String[] args) {
    
           Bicycle bicycle = new Bicycle("Peugeot", 120, 40);
           Bicycle.Seat seat = bicycle.new Seat();
    
           seat.displaySeatProperties();
       }
    }

    إخراج وحدة التحكم:

    
    Seat properties: seatpost diameter = 40

    ملحوظة:يتم الإعلان عن المتغير الجديد باستخدام معدل الوصول الأكثر صرامة ( private). وما زال للطبقة الداخلية حق الوصول!

  3. لا يمكن إنشاء كائن من فئة داخلية بطريقة ثابتة لفئة خارجية.

    يتم تفسير ذلك من خلال الميزات المحددة لكيفية تنظيم الطبقات الداخلية. يمكن أن تحتوي الفئة الداخلية على مُنشئات ذات معلمات، أو مُنشئ افتراضي فقط. ولكن بغض النظر، عندما نقوم بإنشاء كائن من فئة داخلية، يتم تمرير مرجع إلى كائن من الفئة الخارجية بشكل غير مرئي إلى الكائن الذي تم إنشاؤه من الفئة الداخلية. بعد كل شيء، فإن وجود مثل هذا المرجع للكائن هو مطلب مطلق. وإلا فلن نتمكن من إنشاء كائنات من الفئة الداخلية.

    لكن إذا كانت إحدى طرق الطبقة الخارجية ثابتة، فقد لا يكون لدينا كائن من الطبقة الخارجية! وهذا من شأنه أن يشكل انتهاكًا لمنطق كيفية عمل الطبقة الداخلية. في هذه الحالة، سيولد المترجم خطأ:

    public static Seat createSeat() {
    
       // Bicycle.this cannot be referenced from a static context
       return new Seat();
    }
  4. لا يمكن للفئة الداخلية أن تحتوي على متغيرات وأساليب ثابتة.

    المنطق هو نفسه: يمكن أن توجد الأساليب والمتغيرات الثابتة ويمكن استدعاؤها أو الرجوع إليها حتى في حالة عدم وجود كائن.

    لكن بدون كائن من الطبقة الخارجية، لن نتمكن من الوصول إلى الطبقة الداخلية.

    تناقض واضح! هذا هو السبب في عدم السماح بالمتغيرات والأساليب الثابتة في الفئات الداخلية.

    سيقوم المترجم بإنشاء خطأ إذا حاولت إنشائها:

    public class Bicycle {
    
       private int weight;
    
    
       public class Seat {
    
           // An inner class cannot have static declarations
           public static void displaySeatProperties() {
    
               System.out.println("Seat properties: seatpost diameter = " + Bicycle.this.seatPostDiameter);
           }
       }
    }
  5. عند إنشاء كائن من فئة داخلية، يعد معدل الوصول الخاص به مهمًا.

    يمكن تمييز الفئة الداخلية باستخدام معدِّلات الوصول القياسية: publicو privateو protectedو و package private.

    لماذا يهم هذا؟

    يؤثر هذا على المكان الذي يمكننا فيه إنشاء مثيلات للطبقة الداخلية في برنامجنا.

    إذا Seatتم الإعلان عن فئتنا كـ public، فيمكننا إنشاء Seatكائنات في أي فئة أخرى. الشرط الوحيد هو أن كائن الطبقة الخارجية يجب أن يكون موجودًا أيضًا.

    بالمناسبة، لقد فعلنا هذا بالفعل هنا:

    public class Main {
    
       public static void main(String[] args) {
    
           Bicycle peugeot = new Bicycle("Peugeot", 120);
           Bicycle.Handlebar handlebar = peugeot.new Handlebar();
           Bicycle.Seat seat = peugeot.new Seat();
    
           seat.up();
           peugeot.start();
           handlebar.left();
           handlebar.right();
       }
    }

    لقد تمكنا بسهولة من الوصول إلى Handlebarالطبقة الداخلية من Mainالفصل.

    إذا أعلنا أن الفئة الداخلية هي private، فسنكون قادرين على إنشاء كائنات داخل الطبقة الخارجية فقط.

    لم يعد بإمكاننا إنشاء Seatكائن "من الخارج":

    private class Seat {
    
       // Methods
    }
    
    public class Main {
    
       public static void main(String[] args) {
    
           Bicycle bicycle = new Bicycle("Peugeot", 120, 40);
    
           // Bicycle.Seat has private access in Bicycle
           Bicycle.Seat seat = bicycle.new Seat();
       }
    }

    ربما تكون قد فهمت المنطق بالفعل :)

  6. تعمل معدّلات الوصول للفئات الداخلية بنفس الطريقة بالنسبة للمتغيرات العادية.

    يوفر المعدل protectedالوصول إلى متغير مثيل في الفئات الفرعية والفئات الموجودة في نفس الحزمة.

    protectedيعمل أيضًا للطبقات الداخلية. يمكننا إنشاء protectedكائنات من الطبقة الداخلية:

    • في الطبقة الخارجية؛
    • في فئاتها الفرعية.
    • في الفئات الموجودة في نفس الحزمة.

    إذا لم يكن للفئة الداخلية معدل وصول ( package private)، فيمكن إنشاء كائنات للفئة الداخلية:

    • في الطبقة الخارجية؛
    • في الفئات الموجودة في نفس الحزمة.

    لقد كنت على دراية بالمعدلات لفترة طويلة، لذلك لا توجد مشاكل هنا.

هذا كل شيء في الوقت الحالي :) ولكن لا تتكاسل! تعد الفصول الداخلية موضوعًا واسع النطاق إلى حد ما وسنستمر في استكشافه في الدرس التالي. الآن يمكنك تحديث ذاكرتك بدرس دورتنا حول الصفوف الداخلية . وفي المرة القادمة، دعونا نتحدث عن الطبقات المتداخلة الثابتة.
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION