התבנית עיצוב הדקורטור (Decorator design pattern) משמשת לשינוי פונקציונליות של אובייקט בזמן ריצה. באותו הזמן, מופעים אחרים של אותה המחלקה לא ייתפגעו מכך, כך שכל אובייקט מקבל את ההתנהגות ששונתה. תבנית עיצוב הדקורטור היא אחת מתבניות העיצוב המבניות (כגון Adapter Pattern, Bridge Pattern, Composite Pattern) ומשתמשת במחלקות אבסטרקטיות או ממשק עם הרכבה כדי ליישם.
תבנית עיצוב הדקורטור
אנו משתמשים במורשת או בהרכבה כדי להרחיב על ההתנהגות של אובייקט, אך הפעולה נעשית בזמן הקומפילציה והיא תקפה על כל המופעים של המחלקה. אין באפשרותנו להוסיף פונקציות חדשות או להסיר פונקציות קיימות בזמן ריצה – וזהו המקרה בו נכנסת לתמונה התבנית של הדקורטור. נניח שאנו רוצים לממש סוגים שונים של רכבים – אנו יכולים ליצור ממשק Car להגדיר את השיטה assemble ולאחר מכן יש לנו רכב בסיסי, ניתן להרחיב אותו לרכב ספורט ולרכב יוקרה. ההיררכיה של המימוש תיראה כמו בתמונה למטה. אך אם נרצה לקבל רכב בזמן ריצה שיש לו גם את התכונות של רכב ספורט ורכב יוקרה, אז המימוש מתקשה ואם נרצה גם לציין אילו תכונות יש להוסיף ראשונות, המצב מתרכז עוד יותר. כעת דמו אם יש לנו עשרה סוגים שונים של רכבים, הלוגיקה של המימוש באמצעות מורשת והרכבה תהיה בלתי אפשרית לניהול. כדי לפתור מצב תכנות כזה, אנו מחילים את תבנית העיטוף בשפת ג'אווה. יש לנו צורך לקיים את הסוגים הבאים כדי לממש את תבנית עיטוף.
-
ממשק הרכיב – הממשק או מחלקה מופשטת המגדירה את השיטות שיתופלו. במקרה שלנו,
Car
יהיה ממשק הרכב.package com.journaldev.design.decorator; public interface Car { public void assemble(); }
-
מימוש הרכיב – המימוש הבסיסי של ממשק הרכב. יכולים להיות לנו מחלקה בשם
BasicCar
כמימוש של הרכיב.package com.journaldev.design.decorator; public class BasicCar implements Car { @Override public void assemble() { System.out.print("Basic Car."); } }
-
מעצב – מחלקת המעצב מיישמת את ממשק הרכיב ויש לה קשר עם ממשק הרכיב באמצעות החזקה. משתנה הרכיב צריך להיות נגיש למחלקות המעצב הילד, ולכן נגדיר את משתנה זה כמוגן.
package com.journaldev.design.decorator; public class CarDecorator implements Car { protected Car car; public CarDecorator(Car c){ this.car=c; } @Override public void assemble() { this.car.assemble(); } }
-
מעצבים קונקרטיים – הרחבת פונקציונליות המעצב הבסיסי ושינוי התנהגות הרכיב בהתאם. ניתן ליצור מחלקות מעצבות קונקרטיות כמו
LuxuryCar
ו-SportsCar
.package com.journaldev.design.decorator; public class SportsCar extends CarDecorator { public SportsCar(Car c) { super(c); } @Override public void assemble(){ super.assemble(); System.out.print(" Adding features of Sports Car."); } }
package com.journaldev.design.decorator; public class LuxuryCar extends CarDecorator { public LuxuryCar(Car c) { super(c); } @Override public void assemble(){ super.assemble(); System.out.print(" Adding features of Luxury Car."); } }
תבנית עיצוב המקשט – תרשים מחלקות
תוכנית בדיקה של תבנית עיצוב המקשט
package com.journaldev.design.test;
import com.journaldev.design.decorator.BasicCar;
import com.journaldev.design.decorator.Car;
import com.journaldev.design.decorator.LuxuryCar;
import com.journaldev.design.decorator.SportsCar;
public class DecoratorPatternTest {
public static void main(String[] args) {
Car sportsCar = new SportsCar(new BasicCar());
sportsCar.assemble();
System.out.println("\n*****");
Car sportsLuxuryCar = new SportsCar(new LuxuryCar(new BasicCar()));
sportsLuxuryCar.assemble();
}
}
שימו לב שתוכנית הלקוח יכולה ליצור סוגים שונים של אובייקט בזמן ריצה ולציין גם את הסדר של הביצועים. פלט של תוכנית הבדיקה לעיל הוא:
Basic Car. Adding features of Sports Car.
*****
Basic Car. Adding features of Luxury Car. Adding features of Sports Car.
נקודות חשובות על תבנית עיצוב המקשט
- תבנית עיצוב המקשט מועילה בספק אפשרויות שינוי בזמן ריצה ולכן גמישה יותר. קל לתחזק ולהרחיב כאשר מספר האפשרויות גדול יותר.
- חסרון של תבנית עיצוב המקשט הוא שהיא משתמשת במון רב של אובייקטים דומים מסוגו (מקשטים).
- תבנית המעטפת (Decorator pattern) משמשת הרבה בקבצי Java IO, כמו FileReader, BufferedReader וכו'.
Source:
https://www.digitalocean.com/community/tutorials/decorator-design-pattern-in-java-example