نمط تصميم سلسلة المسؤولية في Java

تصميم نمط سلسلة المسؤولية هو أحد أنماط تصميم السلوك.

نمط تصميم سلسلة المسؤولية

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

مثال على نمط سلسلة المسؤولية في JDK

دعونا نرى مثالًا على نمط سلسلة المسؤولية في JDK ، ثم سنقوم بتنفيذ مثال حقيقي على هذا النمط. نحن نعلم أنه يمكننا أن نملك العديد من كتل الـ catch في كود try-catch. هنا كل كتلة catch هي نوعًا من المعالج لمعالجة استثناء معين. لذلك عندما يحدث استثناء في كتلة try ، يتم إرساله إلى الكتلة الأولى لمعالجته. إذا كانت الكتلة الأولى غير قادرة على معالجته ، فإنها تقوم بتوجيه الطلب إلى الكائن التالي في السلسلة أي الكتلة القادمة. إذا كانت حتى الكتلة الأخيرة غير قادرة على معالجته ، يتم رمي الاستثناء خارج السلسلة إلى البرنامج الذي يستدعيه.

مثال على نمط تصميم سلسلة المسؤولية

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

نمط تصميم سلسلة المسؤولية – الفئات الأساسية والواجهة

يمكننا إنشاء فئة Currency التي ستخزن المبلغ الذي يجب صرفه وتستخدمه في تنفيذات السلسلة. Currency.java

package com.journaldev.design.chainofresponsibility;

public class Currency {

	private int amount;
	
	public Currency(int amt){
		this.amount=amt;
	}
	
	public int getAmount(){
		return this.amount;
	}
}

يجب أن يحتوي الواجهة الأساسية على طريقة لتعريف المعالج التالي في السلسلة والطريقة التي ستقوم بمعالجة الطلب. واجهة صرف الأموال في الماكينة الآلية لدينا ستبدو كما يلي. DispenseChain.java

package com.journaldev.design.chainofresponsibility;

public interface DispenseChain {

	void setNextChain(DispenseChain nextChain);
	
	void dispense(Currency cur);
}

نمط سلسلة المسؤوليات – تنفيذ سلسلة

نحتاج إلى إنشاء فئات معالجة مختلفة ستنفذ واجهة DispenseChain وتقدم تنفيذًا لطرق التوزيع. نظرًا لأننا نقوم بتطوير نظامنا للعمل مع ثلاثة أنواع من فواتير العملة – 50 دولارًا و20 دولارًا و10 دولارات، سنقوم بإنشاء ثلاثة تنفيذات محددة. Dollar50Dispenser.java

package com.journaldev.design.chainofresponsibility;

public class Dollar50Dispenser implements DispenseChain {

	private DispenseChain chain;
	
	@Override
	public void setNextChain(DispenseChain nextChain) {
		this.chain=nextChain;
	}

	@Override
	public void dispense(Currency cur) {
		if(cur.getAmount() >= 50){
			int num = cur.getAmount()/50;
			int remainder = cur.getAmount() % 50;
			System.out.println("Dispensing "+num+" 50$ note");
			if(remainder !=0) this.chain.dispense(new Currency(remainder));
		}else{
			this.chain.dispense(cur);
		}
	}

}

Dollar20Dispenser.java

package com.journaldev.design.chainofresponsibility;

public class Dollar20Dispenser implements DispenseChain{

	private DispenseChain chain;
	
	@Override
	public void setNextChain(DispenseChain nextChain) {
		this.chain=nextChain;
	}

	@Override
	public void dispense(Currency cur) {
		if(cur.getAmount() >= 20){
			int num = cur.getAmount()/20;
			int remainder = cur.getAmount() % 20;
			System.out.println("Dispensing "+num+" 20$ note");
			if(remainder !=0) this.chain.dispense(new Currency(remainder));
		}else{
			this.chain.dispense(cur);
		}
	}

}

Dollar10Dispenser.java

package com.journaldev.design.chainofresponsibility;

public class Dollar10Dispenser implements DispenseChain {

	private DispenseChain chain;
	
	@Override
	public void setNextChain(DispenseChain nextChain) {
		this.chain=nextChain;
	}

	@Override
	public void dispense(Currency cur) {
		if(cur.getAmount() >= 10){
			int num = cur.getAmount()/10;
			int remainder = cur.getAmount() % 10;
			System.out.println("Dispensing "+num+" 10$ note");
			if(remainder !=0) this.chain.dispense(new Currency(remainder));
		}else{
			this.chain.dispense(cur);
		}
	}

}

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

نمط تصميم سلسلة المسؤوليات – إنشاء السلسلة

هذه خطوة مهمة جدًا ويجب أن نقوم بإنشاء السلسلة بعناية، وإلا فإن المعالج قد لا يتلقى أي طلب على الإطلاق. على سبيل المثال، في تنفيذنا إذا أبقينا سلسلة المعالج الأول كـ Dollar10Dispenser ثم Dollar20Dispenser، فلن يتم توجيه الطلب أبدًا إلى المعالج الثاني وستصبح السلسلة غير مفيدة. هنا تنفيذ موزع الصراف الآلي الخاص بنا لمعالجة المبلغ الذي طلبه المستخدم. ATMDispenseChain.java

package com.journaldev.design.chainofresponsibility;

import java.util.Scanner;

public class ATMDispenseChain {

	private DispenseChain c1;

	public ATMDispenseChain() {
		// initialize the chain
		this.c1 = new Dollar50Dispenser();
		DispenseChain c2 = new Dollar20Dispenser();
		DispenseChain c3 = new Dollar10Dispenser();

		// set the chain of responsibility
		c1.setNextChain(c2);
		c2.setNextChain(c3);
	}

	public static void main(String[] args) {
		ATMDispenseChain atmDispenser = new ATMDispenseChain();
		while (true) {
			int amount = 0;
			System.out.println("Enter amount to dispense");
			Scanner input = new Scanner(System.in);
			amount = input.nextInt();
			if (amount % 10 != 0) {
				System.out.println("Amount should be in multiple of 10s.");
				return;
			}
			// process the request
			atmDispenser.c1.dispense(new Currency(amount));
		}

	}

}

عند تشغيل التطبيق أعلاه، نحصل على الإخراج كما يلي.

Enter amount to dispense
530
Dispensing 10 50$ note
Dispensing 1 20$ note
Dispensing 1 10$ note
Enter amount to dispense
100
Dispensing 2 50$ note
Enter amount to dispense
120
Dispensing 2 50$ note
Dispensing 1 20$ note
Enter amount to dispense
15
Amount should be in multiple of 10s.

Chain of Responsibilities Design Pattern Class Diagram

مثالنا على موزع صراف آلي باستخدام نمط سلسلة المسؤولية يبدو كما في الصورة أدناه.

Chain of Responsibility Design Pattern Important Points

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

أمثلة لنمط سلسلة المسؤولية في JDK

  • java.util.logging.Logger#log()
  • javax.servlet.Filter#doFilter()

هذا كل شيء بالنسبة لنمط تصميم سلسلة المسؤولية، آمل أن يكون قد نال إعجابك وأن يساعد في فهمك لهذا النمط التصميمي.

Source:
https://www.digitalocean.com/community/tutorials/chain-of-responsibility-design-pattern-in-java