JavaRush /مدونة جافا /Random-AR /تصميم الفئات والواجهات (ترجمة المقال)
fatesha
مستوى

تصميم الفئات والواجهات (ترجمة المقال)

نشرت في المجموعة
تصميم الفئات والواجهات (ترجمة المقال) - 1

محتوى

  1. مقدمة
  2. واجهات
  3. علامات الواجهة
  4. الواجهات الوظيفية والأساليب الثابتة والأساليب الافتراضية
  5. دروس مجردة
  6. فئات غير قابلة للتغيير (الدائمة).
  7. فئات مجهولة
  8. الرؤية
  9. ميراث
  10. تعدد الميراث
  11. الميراث والتكوين
  12. التغليف
  13. الفصول والأساليب النهائية
  14. ماذا بعد
  15. تحميل كود المصدر

1 المقدمة

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

2. واجهات

في البرمجة كائنية التوجه ، يشكل مفهوم الواجهات الأساس لتطوير العقود . باختصار، تحدد الواجهات مجموعة من الأساليب (العقود) وكل فئة تتطلب دعمًا لتلك الواجهة المحددة يجب أن توفر تنفيذًا لتلك الأساليب: فكرة بسيطة إلى حد ما ولكنها قوية. تحتوي العديد من لغات البرمجة على واجهات بشكل أو بآخر، لكن Java على وجه الخصوص توفر الدعم اللغوي لذلك. دعونا نلقي نظرة على تعريف واجهة بسيط في جافا.
package com.javacodegeeks.advanced.design;

public interface SimpleInterface {
void performAction();
}
في المقتطف أعلاه، SimpleInterfaceتعلن الواجهة التي أطلقنا عليها اسم طريقة واحدة فقط تسمى performAction. الفرق الرئيسي بين الواجهات والفئات هو أن الواجهات تحدد ما يجب أن تكون عليه جهة الاتصال (تعلن عن طريقة)، لكنها لا توفر إمكانية تنفيذها. ومع ذلك، يمكن أن تكون الواجهات في Java أكثر تعقيدًا: حيث يمكن أن تتضمن واجهات متداخلة، وفئات، وأعدادًا، وتعليقات توضيحية، وثوابت. على سبيل المثال:
package com.javacodegeeks.advanced.design;

public interface InterfaceWithDefinitions {
    String CONSTANT = "CONSTANT";

    enum InnerEnum {
        E1, E2;
    }

    class InnerClass {
    }

    interface InnerInterface {
        void performInnerAction();
    }

    void performAction();
}
في هذا المثال الأكثر تعقيدًا، هناك العديد من القيود التي تفرضها الواجهات دون قيد أو شرط على بنيات التداخل وإعلانات الطرق، ويتم فرضها بواسطة مترجم Java. أولًا، حتى لو لم يتم التصريح عنها بشكل صريح، فإن كل تعريف للأسلوب في الواجهة يكون عامًا (ويمكن أن يكون عامًا فقط). وبالتالي فإن إعلانات الطريقة التالية متكافئة:
public void performAction();
void performAction();
تجدر الإشارة إلى أن كل طريقة في الواجهة يتم تعريفها ضمنيًا على أنها مجردة ، وحتى إعلانات الطرق هذه متكافئة:
public abstract void performAction();
public void performAction();
void performAction();
أما بالنسبة للحقول الثابتة المعلنة، بالإضافة إلى كونها عامة ، فهي أيضًا ثابتة ضمنيًا ويتم وضع علامة نهائية عليها . لذلك فإن التصريحات التالية متكافئة أيضًا:
String CONSTANT = "CONSTANT";
public static final String CONSTANT = "CONSTANT";
وأخيرًا، فإن الفئات أو الواجهات أو الأعداد المتداخلة، بالإضافة إلى كونها عامة ، يتم أيضًا الإعلان عنها ضمنيًا بأنها ثابتة . على سبيل المثال، هذه الإعلانات تعادل أيضًا:
class InnerClass {
}

static class InnerClass {
}
يعد النمط الذي تختاره بمثابة تفضيل شخصي، ولكن معرفة هذه الخصائص البسيطة للواجهات يمكن أن ينقذك من الكتابة غير الضرورية.

3. علامة الواجهة

واجهة العلامة هي نوع خاص من الواجهات التي لا تحتوي على طرق أو بنيات متداخلة أخرى. كيف تحددها مكتبة جافا:
public interface Cloneable {
}
علامات الواجهة ليست عقودًا في حد ذاتها، ولكنها تقنية مفيدة إلى حد ما "لربط" أو "ربط" بعض السمات المحددة بفئة ما. على سبيل المثال، فيما يتعلق بـ Cloneable ، تم وضع علامة على الفئة على أنها قابلة للاستنساخ، ولكن الطريقة التي يمكن أو ينبغي بها تنفيذ ذلك ليست جزءًا من الواجهة. مثال آخر مشهور جدًا ومستخدم على نطاق واسع لعلامة الواجهة هو Serializable:
public interface Serializable {
}
تحدد هذه الواجهة أن الفئة مناسبة للتسلسل وإلغاء التسلسل، ومرة ​​أخرى، لا تحدد كيف يمكن أو ينبغي تنفيذ ذلك. علامات الواجهة لها مكانها في البرمجة الشيئية، على الرغم من أنها لا تفي بالغرض الرئيسي للواجهة وهو العقد. 

4. الواجهات الوظيفية والأساليب الافتراضية والأساليب الثابتة

منذ إصدارات Java 8، اكتسبت الواجهات بعض الميزات الجديدة المثيرة للاهتمام: الأساليب الثابتة، والأساليب الافتراضية، والتحويل التلقائي من lambdas (الواجهات الوظيفية). في قسم الواجهات، أكدنا على حقيقة أن الواجهات في Java يمكنها فقط الإعلان عن الأساليب، ولكنها لا توفر تنفيذها. مع الطريقة الافتراضية، تختلف الأمور: يمكن للواجهة وضع علامة على الطريقة باستخدام الكلمة الأساسية الافتراضية وتوفير تطبيق لها. على سبيل المثال:
package com.javacodegeeks.advanced.design;

public interface InterfaceWithDefaultMethods {
    void performAction();

    default void performDefaulAction() {
        // Implementation here
    }
}
كونها على مستوى المثيل، يمكن تجاوز الطرق الافتراضية من خلال كل تطبيق واجهة، ولكن يمكن أن تتضمن الواجهات الآن أيضًا طرقًا ثابتة ، على سبيل المثال: package com.javacodegeeks.advanced.design؛
public interface InterfaceWithDefaultMethods {
    static void createAction() {
        // Implementation here
    }
}
يمكن القول أن توفير التنفيذ في الواجهة يتعارض مع الغرض الكامل من برمجة العقود. ولكن هناك العديد من الأسباب وراء إدخال هذه الميزات إلى لغة Java وبغض النظر عن مدى فائدتها أو إرباكها، فهي موجودة من أجلك ولاستعمالك. تعد الواجهات الوظيفية قصة مختلفة وقد أثبتت أنها إضافات مفيدة جدًا للغة. في الأساس، الواجهة الوظيفية هي واجهة تحتوي على طريقة مجردة واحدة معلنة عليها. Runnableتعد واجهة المكتبة القياسية مثالًا جيدًا جدًا على هذا المفهوم.
@FunctionalInterface
public interface Runnable {
    void run();
}
يتعامل مترجم Java مع الواجهات الوظيفية بشكل مختلف ويمكنه تحويل وظيفة lambda إلى تطبيق واجهة وظيفية حيث يكون ذلك منطقيًا. دعونا نفكر في وصف الوظيفة التالية: 
public void runMe( final Runnable r ) {
    r.run();
}
لاستدعاء هذه الوظيفة في Java 7 والإصدارات الأقدم، يجب توفير تنفيذ للواجهة Runnable(على سبيل المثال باستخدام فئات مجهولة)، ولكن في Java 8 يكفي توفير تنفيذ لأسلوب run() باستخدام بناء جملة lambda:
runMe( () -> System.out.println( "Run!" ) );
بالإضافة إلى ذلك، يشير التعليق التوضيحي @FunctionalInterface (سيتم تغطية التعليقات التوضيحية بالتفصيل في الجزء 5 من البرنامج التعليمي) إلى أن المترجم يمكنه التحقق مما إذا كانت الواجهة تحتوي على طريقة مجردة واحدة فقط، وبالتالي فإن أي تغييرات يتم إجراؤها على الواجهة في المستقبل لن تنتهك هذا الافتراض .

5. الطبقات المجردة

مفهوم آخر مثير للاهتمام تدعمه لغة Java هو مفهوم الفئات المجردة. تشبه الفئات المجردة إلى حد ما الواجهات في Java 7 وهي قريبة جدًا من واجهة الطريقة الافتراضية في Java 8. على عكس الفئات العادية، لا يمكن إنشاء مثيل للفئة المجردة، ولكن يمكن تصنيفها إلى فئات فرعية (راجع قسم الوراثة لمزيد من التفاصيل). والأهم من ذلك، يمكن أن تحتوي الفئات المجردة على أساليب مجردة: نوع خاص من الأساليب بدون تطبيق، تمامًا مثل الواجهة. على سبيل المثال:
package com.javacodegeeks.advanced.design;

public abstract class SimpleAbstractClass {
    public void performAction() {
        // Implementation here
    }

    public abstract void performAnotherAction();
}
SimpleAbstractClassفي هذا المثال، تم تعريف الفئة على أنها مجردة وتحتوي على طريقة مجردة معلنة. تعتبر الفئات المجردة مفيدة جدًا؛ يمكن مشاركة معظم أو حتى بعض أجزاء تفاصيل التنفيذ بين العديد من الفئات الفرعية. مهما كان الأمر، فإنها لا تزال تترك الباب مفتوحًا وتسمح لك بتخصيص السلوك المتأصل في كل فئة فرعية باستخدام الأساليب المجردة. تجدر الإشارة إلى أنه على عكس الواجهات، التي يمكن أن تحتوي فقط على إعلانات عامة ، يمكن للفئات المجردة استخدام القوة الكاملة لقواعد إمكانية الوصول للتحكم في رؤية الطريقة المجردة.

6. الفصول الفورية

أصبحت الثبات أكثر أهمية في تطوير البرمجيات في الوقت الحاضر. أثار ظهور الأنظمة متعددة النواة العديد من القضايا المتعلقة بمشاركة البيانات والتوازي. ولكن ظهرت مشكلة واحدة بالتأكيد: وجود حالة قليلة (أو حتى معدومة) قابلة للتغيير يؤدي إلى قابلية توسعة أفضل (قابلية للتوسع) وتفكير أسهل حول النظام. لسوء الحظ، لا توفر لغة Java دعمًا لائقًا لثبات الفئة. ومع ذلك، باستخدام مجموعة من التقنيات، يصبح من الممكن تصميم فئات غير قابلة للتغيير. بادئ ذي بدء، يجب أن تكون جميع حقول الفصل الدراسي نهائية (تم وضع علامة عليها كنهائية ). هذه بداية جيدة، لكنها ليست ضمانة. 
package com.javacodegeeks.advanced.design;

import java.util.Collection;

public class ImmutableClass {
    private final long id;
    private final String[] arrayOfStrings;
    private final Collection<String> collectionOfString;
}
ثانيًا، تأكد من التهيئة الصحيحة: إذا كان الحقل مرجعًا لمجموعة أو مصفوفة، فلا تقم بتعيين هذه الحقول مباشرة من وسيطات المُنشئ، بل قم بعمل نسخ بدلاً من ذلك. سيضمن هذا عدم تعديل حالة المجموعة أو المصفوفة خارجها.
public ImmutableClass( final long id, final String[] arrayOfStrings,
        final Collection<String> collectionOfString) {
    this.id = id;
    this.arrayOfStrings = Arrays.copyOf( arrayOfStrings, arrayOfStrings.length );
    this.collectionOfString = new ArrayList<>( collectionOfString );
}
وأخيرًا، ضمان الوصول المناسب (الحروف). بالنسبة للمجموعات، يجب توفير الثبات كغلاف  Collections.unmodifiableXxx: مع المصفوفات، الطريقة الوحيدة لتوفير الثبات الحقيقي هي توفير نسخة بدلاً من إرجاع مرجع إلى المصفوفة. قد لا يكون هذا مقبولاً من وجهة نظر عملية، لأنه يعتمد بشكل كبير على حجم المصفوفة ويمكن أن يضع ضغطًا هائلاً على أداة تجميع البيانات المهملة.
public String[] getArrayOfStrings() {
    return Arrays.copyOf( arrayOfStrings, arrayOfStrings.length );
}
حتى هذا المثال الصغير يعطي فكرة جيدة أن الثبات ليس مواطنًا من الدرجة الأولى في جافا. يمكن أن تتعقد الأمور إذا كانت الفئة غير القابلة للتغيير تحتوي على حقل يشير إلى كائن من فئة أخرى. يجب أن تكون هذه الفئات أيضًا غير قابلة للتغيير، لكن لا توجد طريقة لضمان ذلك. هناك العديد من أدوات تحليل كود مصدر Java اللائقة، مثل FindBugs وPMD، والتي يمكن أن تساعدك بشكل كبير من خلال التحقق من التعليمات البرمجية الخاصة بك والإشارة إلى عيوب برمجة Java الشائعة. هذه الأدوات هي أصدقاء رائعون لأي مطور Java.

7. فصول مجهولة المصدر

في عصر ما قبل Java 8، كانت الفئات المجهولة هي الطريقة الوحيدة لضمان تعريف الفئات بسرعة وإنشاء مثيل لها على الفور. كان الغرض من الفئات المجهولة هو تقليل النمط المعياري وتوفير طريقة قصيرة وسهلة لتمثيل الفئات كسجل. دعونا نلقي نظرة على الطريقة القديمة النموذجية لإنشاء سلسلة رسائل جديدة في Java:
package com.javacodegeeks.advanced.design;

public class AnonymousClass {
    public static void main( String[] args ) {
        new Thread(
            // Example of creating anonymous class which implements
            // Runnable interface
            new Runnable() {
                @Override
                public void run() {
                    // Implementation here
                }
            }
        ).start();
    }
}
في هذا المثال، يتم توفير تنفيذ Runnableالواجهة على الفور كفئة مجهولة. على الرغم من وجود بعض القيود المرتبطة بالفئات المجهولة، إلا أن العيوب الرئيسية لاستخدامها هي بناء الجملة المطول للغاية الذي تلزم به Java كلغة. حتى مجرد الفصل المجهول الذي لا يفعل أي شيء يتطلب 5 أسطر من التعليمات البرمجية على الأقل في كل مرة يتم كتابتها.
new Runnable() {
   @Override
   public void run() {
   }
}
لحسن الحظ، مع Java 8 وlamda والواجهات الوظيفية، ستختفي كل هذه الصور النمطية قريبًا، وأخيرًا ستبدو كتابة كود Java موجزة حقًا.
package com.javacodegeeks.advanced.design;

public class AnonymousClass {
    public static void main( String[] args ) {
        new Thread( () -> { /* Implementation here */ } ).start();
    }
}

8. الرؤية

لقد تحدثنا بالفعل قليلاً عن قواعد الرؤية وإمكانية الوصول في Java في الجزء الأول من البرنامج التعليمي. في هذا الجزء، سنعود إلى هذا الموضوع مرة أخرى، ولكن في سياق التصنيف الفرعي. تصميم الفئات والواجهات (ترجمة المقال) - 2تسمح الرؤية على مستويات مختلفة للفئات أو تمنعها من رؤية الفئات أو الواجهات الأخرى (على سبيل المثال، إذا كانت في حزم مختلفة أو متداخلة داخل بعضها البعض) أو الفئات الفرعية من رؤية الأساليب والمنشئات والحقول الخاصة بآبائها والوصول إليها. في القسم التالي، الميراث، سنرى ذلك عمليًا.

9. الميراث

يعد الميراث أحد المفاهيم الأساسية للبرمجة كائنية التوجه، ويعمل كأساس لبناء فئة من العلاقات. بالاشتراك مع قواعد الرؤية وإمكانية الوصول، يسمح الميراث بتصميم الفئات في تسلسل هرمي يمكن توسيعه وصيانته. على المستوى المفاهيمي، يتم تنفيذ الوراثة في Java باستخدام الفئات الفرعية والكلمة الأساسية الممتدة ، جنبًا إلى جنب مع الفئة الأصلية. ترث الفئة الفرعية جميع العناصر العامة والمحمية للفئة الأصلية. بالإضافة إلى ذلك، ترث الفئة الفرعية العناصر الخاصة بالحزمة من فئتها الأصلية إذا كان كلاهما (الفئة الفرعية والفئة) موجودين في نفس الحزمة. ومع ذلك، من المهم جدًا، بغض النظر عما تحاول تصميمه، الالتزام بالحد الأدنى من الأساليب التي تكشفها الفئة علنًا أو فئاتها الفرعية. على سبيل المثال، دعونا نلقي نظرة على الفئة Parentوفئتها الفرعية Childلتوضيح الفرق في مستويات الرؤية وتأثيراتها.
package com.javacodegeeks.advanced.design;

public class Parent {
    // Everyone can see it
    public static final String CONSTANT = "Constant";

    // No one can access it
    private String privateField;
    // Only subclasses can access it
    protected String protectedField;

    // No one can see it
    private class PrivateClass {
    }

    // Only visible to subclasses
    protected interface ProtectedInterface {
    }

    // Everyone can call it
    public void publicAction() {
    }

    // Only subclass can call it
    protected void protectedAction() {
    }

    // No one can call it
    private void privateAction() {
    }

    // Only subclasses in the same package can call it
    void packageAction() {
    }
}
package com.javacodegeeks.advanced.design;

// Resides in the same package as parent class
public class Child extends Parent implements Parent.ProtectedInterface {
    @Override
    protected void protectedAction() {
        // Calls parent's method implementation
        super.protectedAction();
    }

    @Override
    void packageAction() {
        // Do nothing, no call to parent's method implementation
    }

    public void childAction() {
        this.protectedField = "value";
    }
}
يعد الميراث موضوعًا كبيرًا جدًا في حد ذاته، ويحتوي على الكثير من التفاصيل الدقيقة الخاصة بجافا. ومع ذلك، هناك بعض القواعد التي يسهل اتباعها ويمكن أن تقطع شوطا طويلا في الحفاظ على إيجاز التسلسل الهرمي للفصل. في Java، يمكن لكل فئة فرعية تجاوز أي أساليب موروثة من أصلها ما لم يتم إعلانها نهائية. ومع ذلك، لا يوجد بناء جملة أو كلمة رئيسية خاصة لوضع علامة على الطريقة على أنها متجاوزة، مما قد يؤدي إلى حدوث ارتباك. ولهذا السبب تم تقديم التعليق التوضيحي @Override : عندما يكون هدفك هو تجاوز طريقة موروثة، يرجى استخدام التعليق التوضيحي @Override للإشارة إليه بإيجاز. هناك معضلة أخرى يواجهها مطورو Java باستمرار في التصميم وهي بناء التسلسلات الهرمية للفئات (مع فئات ملموسة أو مجردة) مقابل تنفيذ الواجهات. نوصي بشدة بتفضيل الواجهات على الفئات أو الفئات المجردة كلما أمكن ذلك. أصبحت الواجهات أخف وزنًا وأسهل في الاختبار والصيانة، كما أنها تقلل من الآثار الجانبية لتغييرات التنفيذ. تعتمد العديد من تقنيات البرمجة المتقدمة، مثل إنشاء فئات الوكيل في مكتبة Java القياسية، بشكل كبير على الواجهات.

10. الميراث المتعدد

على عكس C++ وبعض اللغات الأخرى، لا تدعم Java الوراثة المتعددة: في Java، يمكن أن يكون لكل فئة أصل مباشر واحد فقط (مع وجود الفئة Objectفي أعلى التسلسل الهرمي). ومع ذلك، يمكن للفئة تنفيذ واجهات متعددة، وبالتالي فإن تكديس الواجهة هو الطريقة الوحيدة لتحقيق (أو محاكاة) الميراث المتعدد في Java.
package com.javacodegeeks.advanced.design;

public class MultipleInterfaces implements Runnable, AutoCloseable {
    @Override
    public void run() {
        // Some implementation here
    }

    @Override
    public void close() throws Exception {
       // Some implementation here
    }
}
يعد تنفيذ واجهات متعددة أمرًا قويًا جدًا في الواقع، ولكن غالبًا ما تؤدي الحاجة إلى استخدام التنفيذ مرارًا وتكرارًا إلى تسلسل هرمي عميق للفئات كوسيلة للتغلب على نقص دعم Java للميراث المتعدد. 
public class A implements Runnable {
    @Override
    public void run() {
        // Some implementation here
    }
}
// Class B wants to inherit the implementation of run() method from class A.
public class B extends A implements AutoCloseable {
    @Override
    public void close() throws Exception {
       // Some implementation here
    }
}
// Class C wants to inherit the implementation of run() method from class A
// and the implementation of close() method from class B.
public class C extends B implements Readable {
    @Override
    public int read(java.nio.CharBuffer cb) throws IOException {
       // Some implementation here
    }
}
وهكذا... الإصدار الأخير من Java 8 يعالج إلى حد ما مشكلة حقن الطريقة الافتراضية. بسبب الأساليب الافتراضية، لا توفر الواجهات في الواقع عقدًا فحسب، بل توفر أيضًا التنفيذ. ولذلك، فإن الفئات التي تنفذ هذه الواجهات سوف ترث أيضًا هذه الأساليب المنفذة تلقائيًا. على سبيل المثال:
package com.javacodegeeks.advanced.design;

public interface DefaultMethods extends Runnable, AutoCloseable {
    @Override
    default void run() {
        // Some implementation here
    }

    @Override
    default void close() throws Exception {
       // Some implementation here
    }
}

// Class C inherits the implementation of run() and close() methods from the
// DefaultMethods interface.
public class C implements DefaultMethods, Readable {
    @Override
    public int read(java.nio.CharBuffer cb) throws IOException {
       // Some implementation here
    }
}
ضع في اعتبارك أن الميراث المتعدد هو أداة قوية جدًا ولكنها في نفس الوقت خطيرة. غالبًا ما يُشار إلى مشكلة Diamond of Death المعروفة على أنها عيب كبير في تنفيذ الميراث المتعدد، مما يجبر المطورين على تصميم التسلسلات الهرمية للفئات بعناية فائقة. ولسوء الحظ، فإن واجهات Java 8 ذات الأساليب الافتراضية تقع أيضًا ضحية لهذه العيوب.
interface A {
    default void performAction() {
    }
}

interface B extends A {
    @Override
    default void performAction() {
    }
}

interface C extends A {
    @Override
    default void performAction() {
    }
}
على سبيل المثال، ستفشل عملية تجميع مقتطف التعليمات البرمجية التالي:
// E is not compilable unless it overrides performAction() as well
interface E extends B, C {
}
في هذه المرحلة، من العدل أن نقول إن Java كلغة حاولت دائمًا تجنب الحالات الأساسية للبرمجة الشيئية، ولكن مع تطور اللغة، بدأت بعض هذه الحالات في الظهور فجأة. 

11. الميراث والتكوين

لحسن الحظ، الميراث ليس هو الطريقة الوحيدة لتصميم صفك. البديل الآخر الذي يعتقد العديد من المطورين أنه أفضل بكثير من الميراث هو التكوين. الفكرة بسيطة للغاية: بدلاً من إنشاء تسلسل هرمي للفئات، يجب أن تتكون من فئات أخرى. دعونا ننظر إلى هذا المثال:
// E is not compilable unless it overrides performAction() as well
interface E extends B, C {
}
يتكون الفصل Vehicleمن محرك وعجلات (بالإضافة إلى العديد من الأجزاء الأخرى التي تم تركها جانبًا من أجل البساطة). ومع ذلك، يمكن القول أن الفصل Vehicleهو أيضًا محرك، لذلك يمكن تصميمه باستخدام الميراث. 
public class Vehicle extends Engine {
    private Wheels[] wheels;
    // ...
}
ما هو حل التصميم المناسب؟ تُعرف الإرشادات الأساسية العامة بمبادئ IS-A (هو) وHAS-A (يحتوي على). IS-A هي علاقة وراثة: الفئة الفرعية تفي أيضًا بمواصفات الفئة الأصلية والفئة الفرعية (الفئة الفرعية) توسع الأصل. إذا كنت تريد معرفة ما إذا كان كيان واحد يمتد إلى كيان آخر، فقم بإجراء اختبار مطابقة - IS -A (is).") لذلك، HAS-A هي علاقة تكوينية: تمتلك الفئة (أو تحتوي على) كائنًا، وفي معظم الحالات، يعمل مبدأ HAS-A بشكل أفضل من IS-A لعدد من الأسباب: 
  • التصميم أكثر مرونة.
  • النموذج أكثر استقرارًا لأن التغيير لا ينتشر عبر التسلسل الهرمي للفئة؛
  • تقترن الفئة وتكوينها بشكل فضفاض مقارنة بالتكوين، الذي يربط بشكل وثيق بين الأصل وفئته الفرعية.
  • يعد قطار التفكير المنطقي في الفصل الدراسي أبسط، حيث يتم تضمين جميع تبعياته فيه في مكان واحد. 
بغض النظر، فإن الميراث له مكانه ويحل عددًا من مشاكل التصميم الموجودة بعدة طرق، لذلك لا ينبغي إهماله. يرجى وضع هذين البديلين في الاعتبار عند تصميم نموذجك الموجه للكائنات.

12. التغليف.

مفهوم التغليف في البرمجة كائنية التوجه هو إخفاء كافة تفاصيل التنفيذ (مثل وضع التشغيل، والطرق الداخلية، وما إلى ذلك) عن العالم الخارجي. فوائد التغليف هي قابلية الصيانة وسهولة التغيير. التنفيذ الداخلي للفئة مخفي، ويتم العمل مع بيانات الفصل حصريًا من خلال الأساليب العامة للفئة (مشكلة حقيقية إذا كنت تقوم بتطوير مكتبة أو إطار عمل يستخدمه العديد من الأشخاص). يتم تحقيق التغليف في Java من خلال قواعد الرؤية وإمكانية الوصول. في Java، يعتبر من أفضل الممارسات عدم عرض الحقول بشكل مباشر، إلا من خلال الحروف والمحددات (ما لم يتم وضع علامة على الحقول كنهائية). على سبيل المثال:
package com.javacodegeeks.advanced.design;

public class Encapsulation {
    private final String email;
    private String address;

    public Encapsulation( final String email ) {
        this.email = email;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getEmail() {
        return email;
    }
}
يذكرنا هذا المثال بما يسمى JavaBeans في لغة Java: تتم كتابة فئات Java القياسية وفقًا لمجموعة من الاتفاقيات، أحدها يسمح بالوصول إلى الحقول فقط باستخدام أساليب getter وsetter. كما أكدنا بالفعل في قسم الميراث، يرجى الالتزام دائمًا بالحد الأدنى من عقد الدعاية في الفصل الدراسي، وذلك باستخدام مبادئ التغليف. كل ما لا ينبغي أن يكون عامًا يجب أن يصبح خاصًا (أو محميًا/حزمة خاصة، اعتمادًا على المشكلة التي تحلها). سيؤتي هذا ثماره على المدى الطويل من خلال منحك حرية التصميم دون (أو على الأقل التقليل من) التغييرات العاجلة. 

13. الفصول والأساليب النهائية

في Java، هناك طريقة لمنع فئة ما من أن تصبح فئة فرعية لفئة أخرى: يجب إعلان الفئة الأخرى نهائية. 
package com.javacodegeeks.advanced.design;

public final class FinalClass {
}
نفس الكلمة الأساسية النهائية في إعلان الطريقة تمنع الفئات الفرعية من تجاوز الطريقة. 
package com.javacodegeeks.advanced.design;

public class FinalMethod {
    public final void performAction() {
    }
}
لا توجد قواعد عامة لتحديد ما إذا كان الفصل أو الأساليب يجب أن تكون نهائية أم لا. تحد الفئات والأساليب النهائية من القابلية للتوسعة ومن الصعب جدًا التفكير مسبقًا فيما إذا كان ينبغي أو لا ينبغي توريث الفئة، أو ما إذا كان ينبغي أو لا ينبغي تجاوز الطريقة في المستقبل. وهذا مهم بشكل خاص لمطوري المكتبات، حيث أن قرارات التصميم مثل هذه يمكن أن تحد بشكل كبير من إمكانية تطبيق المكتبة. تحتوي مكتبة Java القياسية على عدة أمثلة للفئات النهائية، وأشهرها فئة String. في مرحلة مبكرة، تم اتخاذ هذا القرار لمنع أي محاولات من قبل المطورين للتوصل إلى حل "أفضل" خاص بهم لتنفيذ السلسلة. 

14. ما هو التالي

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

15. قم بتنزيل كود المصدر

يمكنك تنزيل المصدر هنا - Advanced-Java-part-3 المصدر: How to design Classes an
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION