توضح هذه المقالة مفاهيم الميراث والتكوين في جافا. يوضح المثال الأول الميراث ثم يوضح كيفية تحسين تصميم الميراث باستخدام التركيب. وسنلخص كيفية الاختيار بينهما في النهاية. الوراثة مقابل التركيب في جافا - 1

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();
	}
}
مخطط التسلسل الهرمي للفئة بسيط للغاية: الميراث مقابل التركيب في جافا - 2نتيجة التنفيذ:
Fly
Fly
Attack
move()تتم كتابة "Fly" مرتين، لذلك يتم استدعاء الأسلوب مرتين. ولكن يجب أن يتم استدعاؤه مرة واحدة فقط. المشكلة سببها super.attack(). attack ()تستدعي الطريقة طريقة move()الفصل Insect. عندما تستدعي فئة فرعية super.attack ()، فإنها تستدعي أيضًا الطريقة المتجاوزة move(). لإصلاح المشكلة يمكننا:
  1. القضاء على attack()طريقة الفئة الفرعية. سيؤدي هذا إلى جعل الفئة الفرعية تعتمد على attack()تنفيذ أسلوب الفئة الفائقة. إذا attack()attack()بدأت الفئة الفائقة باستخدام طريقة مختلفة للتحرك، فسوف تحتاج الفئة الفرعية إلى التغيير أيضًا. هذا تغليف سيء.
  2. أعد كتابة الطريقة attack()على النحو التالي:

    public void attack() {
    	move();
    	System.out.println("Attack");
    }
  3. وهذا يضمن النتيجة الصحيحة لأن الفئة الفرعية لم تعد تعتمد على الفئة الفائقة. ومع ذلك، فإن الكود عبارة عن نسخة مكررة من الفئة الفائقة. (تقوم الطريقة 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();
	}
}
مخطط الطبقة: الميراث مقابل التركيب في جافا - 3
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. متى يجب استخدام هذه الأساليب؟

يمكن أن تساعدك النقطتان التاليتان في الاختيار بين الميراث والتركيب:
  1. إذا كنت تتعامل مع علاقة بين فئات من النموذج "IS" وتريد فئة توفير جميع واجهاتها إلى فئة أخرى، فالوراثة هي الأفضل.
  2. إذا كانت العلاقة "HAS"، فالتركيب هو المفضل.
وبالتالي، فإن الميراث والتكوين لهما تطبيقاتهما الخاصة ومن الجدير فهم مزاياهما. الروابط:
  1. بلوخ، جوشوا. جافا فعالة. بيرسون التعليم الهند، 2008
  2. https://stackoverflow.com/questions/49002/prefer-composition-over-inheritance
  3. https://www.javaworld.com/article/2076814/core-java/inheritance-versus-composition--what-one-should...
رابط المقال الأصلي مترجم