توضح هذه المقالة مفاهيم الميراث والتكوين في جافا. يوضح المثال الأول الميراث ثم يوضح كيفية تحسين تصميم الميراث باستخدام التركيب. وسنلخص كيفية الاختيار بينهما في النهاية.
1. الميراث
لنفترض أن لدينا صنفInsect
(حشرة إنجليزية) يحتوي هذا الصنف على طريقتين: 1. move()
(من التحرك الإنجليزي) و 2. attack()
(من الهجوم الإنجليزي)
class Insect {
private int size;
private String color;
public Insect(int size, String color) {
this.size = size;
this.color = color;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public void move() {
System.out.println("Move");
}
public void attack() {
move(); //assuming an insect needs to move before attacking
System.out.println("Attack");
}
}
الآن تريد تحديد فئة Bee
(English bee)، وهي أحد الأنواع Insect
، ولكن لها تطبيقات مختلفة attack()
لـ و move()
. ويمكن القيام بذلك باستخدام الميراث:
class Bee extends Insect {
public Bee(int size, String color) {
super(size, color);
}
public void move() {
System.out.println("Fly");
}
public void attack() {
move();
super.attack();
}
}
public class InheritanceVSComposition {
public static void main(String[] args) {
Insect i = new Bee(1, "red");
i.attack();
}
}
مخطط التسلسل الهرمي للفئة بسيط للغاية: نتيجة التنفيذ:
Fly
Fly
Attack
move()
تتم كتابة "Fly" مرتين، لذلك يتم استدعاء الأسلوب مرتين. ولكن يجب أن يتم استدعاؤه مرة واحدة فقط. المشكلة سببها super.attack()
. attack ()
تستدعي الطريقة طريقة move()
الفصل Insect
. عندما تستدعي فئة فرعية super.attack ()
، فإنها تستدعي أيضًا الطريقة المتجاوزة move()
. لإصلاح المشكلة يمكننا:
- القضاء على
attack()
طريقة الفئة الفرعية. سيؤدي هذا إلى جعل الفئة الفرعية تعتمد علىattack()
تنفيذ أسلوب الفئة الفائقة. إذاattack()attack()
بدأت الفئة الفائقة باستخدام طريقة مختلفة للتحرك، فسوف تحتاج الفئة الفرعية إلى التغيير أيضًا. هذا تغليف سيء. -
أعد كتابة الطريقة
attack()
على النحو التالي:public void attack() { move(); System.out.println("Attack"); }
-
وهذا يضمن النتيجة الصحيحة لأن الفئة الفرعية لم تعد تعتمد على الفئة الفائقة. ومع ذلك، فإن الكود عبارة عن نسخة مكررة من الفئة الفائقة. (تقوم الطريقة
attack()
بأشياء أكثر تعقيدًا من مجرد إخراج سلسلة). هذا ليس تصميمًا جيدًا للبرامج ويجب ألا يكون هناك رمز مكرر.
2. التكوين
يمكنك استخدام التكوين بدلاً من الميراث. دعونا نلقي نظرة على الحل باستخدامه. يتم تجريد الوظيفةattack()
كواجهة.
interface Attack {
public void move();
public void attack();
}
يمكن تحديد أنواع مختلفة من الهجمات من خلال تنفيذ واجهة الهجوم.
class AttackImpl implements Attack {
private String move;
private String attack;
public AttackImpl(String move, String attack) {
this.move = move;
this.attack = attack;
}
@Override
public void move() {
System.out.println(move);
}
@Override
public void attack() {
move();
System.out.println(attack);
}
}
نظرًا لأن وظيفة الهجوم خارجية، فإن الفئة Insect
لم تعد تحتوي عليها.
class Insect {
private int size;
private String color;
public Insect(int size, String color) {
this.size = size;
this.color = color;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
فئة Bee
(من الإنجليزية Bee)، كيف يمكن للنوع Insect
أن يهاجم.
// This wrapper class wrap an Attack object
class Bee extends Insect implements Attack {
private Attack attack;
public Bee(int size, String color, Attack attack) {
super(size, color);
this.attack = attack;
}
public void move() {
attack.move();
}
public void attack() {
attack.attack();
}
}
مخطط الطبقة:
public class InheritanceVSComposition2 {
public static void main(String[] args) {
Bee a = new Bee(1, "black", new AttackImpl("fly", "move"));
a.attack();
// if you need another implementation of move()
// there is no need to change Insect, we can quickly use new method to attack
Bee b = new Bee(1, "black", new AttackImpl("fly", "sting"));
b.attack();
}
}
نتيجة التنفيذ:
fly
move
fly
sting
3. متى يجب استخدام هذه الأساليب؟
يمكن أن تساعدك النقطتان التاليتان في الاختيار بين الميراث والتركيب:- إذا كنت تتعامل مع علاقة بين فئات من النموذج "IS" وتريد فئة توفير جميع واجهاتها إلى فئة أخرى، فالوراثة هي الأفضل.
- إذا كانت العلاقة "HAS"، فالتركيب هو المفضل.
- بلوخ، جوشوا. جافا فعالة. بيرسون التعليم الهند، 2008
- https://stackoverflow.com/questions/49002/prefer-composition-over-inheritance
- https://www.javaworld.com/article/2076814/core-java/inheritance-versus-composition--what-one-should...
GO TO FULL VERSION