עקרון עיצוב Chain of Responsibility בשפת ג'אווה

שרשרת תבנית העיצוב של עיקרי האחריות היא אחת מתבניות העיצוב ההתנהגותיות.

תבנית של שרשרת האחריות משמשת להשיג חיבור רפוי בעיצוב תוכנה, כאשר בקשה מלקוח מועברת לשרשרת של אובייקטים לעיבודם. לאחר מכן, האובייקט בשרשרת יחליטו באופן עצמאי מי יעבד את הבקשה והאם יש צורך לשלוח את הבקשה לאובייקט הבא בשרשרת או לא.

בואו נראה את דוגמת ערך של ערכת האחריות ב-JDK ואז נמשיך ליישום דוגמה ממשיית לתבנית זו. אנו יודעים שניתן להשתמש במספר רב של בלוקי תפיסה בתוך קוד try-catch. כאן כל בלוק תפיסה הוא מיני מעבד לעיבוד החריגה המסוימת הזו. לכן כאשר קורה חריגה בתוך הבלוק try, היא נשלחת לבלוק התפיסה הראשון לעיבוד. אם הבלוק הזה אינו מסוגל לעבד את זה, הוא מעביר את הבקשה לאובייקט הבא בשרשרת, כלומר הבלוק catch הבא. אם גם הבלוק catch האחרון אינו מסוגל לעבד את זה, החריגה מושלכת מחוץ לשרשרת לתכנית הקוראת.

דוגמה לתבנית עיצוב שרשרת של אחריות

אחד הדוגמאות הנהדרות לתבנית Chain of Responsibility היא מכונת הוצאת כסף ממזומן ATM. המשתמש מזין את הסכום שיש להוציא והמכונה מוציאה סכום בקרנות מטבע מוגדרות כגון 50$, 20$, 10$ וכו'. אם המשתמש מזין סכום שאינו פי עשר, היא מזריקה שגיאה. נשתמש בתבנית Chain of Responsibility כדי ליישם פתרון זה. השרשרת תעבוד על הבקשה באותו סדר כמו בתמונה למטה. שים לב שניתן ליישם פתרון זה בקלות בתוך תוכנית יחידה אך אז תגדל המורכבות והפתרון יהיה מקושר באופן חזק. לכן ניצור שרשרת של מערכות הוצאת כסף כדי להוציא קרנות של 50$, 20$ ו-10$.

תבנית העיצוב Chain of Responsibility – מחלקות בסיס וממשק

ניתן ליצור מחלקה 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, אז הבקשה לעולם לא תועבר למעבד השני והשרשרת תהפוך ללא שימוש. הנה המימוש שלנו למכונת ה-ATM Dispenser לעיבוד סכומי הבקשה של המשתמש. ATMDispenseChain.java

package com.journaldev.design.chainofresponsibility;

import java.util.Scanner;

public class ATMDispenseChain {

	private DispenseChain c1;

	public ATMDispenseChain() {
		// אתחול השרשרת
		this.c1 = new Dollar50Dispenser();
		DispenseChain c2 = new Dollar20Dispenser();
		DispenseChain c3 = new Dollar10Dispenser();

		// הגדרת שרשרת האחראיות
		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;
			}
			// עיבוד הבקשה
			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

דוגמת המכונת ה-ATM שלנו ליישום של תבנית עיצוב שרשרת האחראיות נראית כמו בתמונה למטה.

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