oop:composite_pattern

Composite Pattern

Το Composite Pattern (Σύνθετο Πρότυπο Σχεδίασης) ανήκει στα δομικά πρότυπα (structural patterns) και χρησιμοποιείται όταν θέλουμε να διαχειριστούμε μια ομάδα αντικειμένων με τον ίδιο ακριβώς τρόπο που θα διαχειριζόμασταν ένα μεμονωμένο αντικείμενο. Το μοτίβο αυτό οργανώνει τα αντικείμενα σε δενδρικές δομές (tree structures) για να αναπαραστήσει ιεραρχίες.

Πότε χρησιμοποιείται;

  • Όταν η εφαρμογή χρειάζεται να αναπαραστήσει μια δενδρικού τύπου ιεραρχία (π.χ. συστήματα αρχείων με φακέλους και αρχεία, δομή μιας εταιρείας με διευθυντές και υπαλλήλους, ή μενού επιλογών).
  • Όταν θέλεις ο κώδικας του πελάτη (client code) να αγνοεί τη διαφορά μεταξύ σύνθετων αντικειμένων (που περιέχουν άλλα αντικείμενα) και μεμονωμένων αντικειμένων (που δεν περιέχουν τίποτα). Ο client καλεί τις ίδιες μεθόδους σε όλα.

Τα Βασικά Συστατικά του Composite Pattern

  1. Component (Συστατικό): Ένα interface ή abstract κλάση που ορίζει τις κοινές λειτουργίες για όλα τα αντικείμενα της ιεραρχίας (τόσο για τα απλά όσο και για τα σύνθετα).
  2. Leaf (Φύλλο): Αντιπροσωπεύει τα τελικά αντικείμενα που δεν έχουν παιδιά. Υλοποιεί τις λειτουργίες του Component.
  3. Composite (Σύνθετο Αντικείμενο): Αντιπροσωπεύει τα αντικείμενα που έχουν παιδιά (άλλα Leafs ή άλλα Composites). Τυπικά, υλοποιεί τις μεθόδους του Component, αλλά συνήθως προωθεί την υλοποίηση της εργασίας στα παιδιά του και συγκεντρώνει το αποτελέσμα.

Παράδειγμα Composite Pattern - Ιεραρχικό Σύστημα Αρχείων

Το παρακάτω παράδειγμα περιγράφει ένα σύστημα αρχείων που έχουμε Αρχεία (Files) και Φακέλους (Folders). Ένας φάκελος μπορεί να περιέχει αρχεία και άλλους φακέλους.

Το interface FileSystemComponent ορίζει τη μέθοδο showDetails.

FileSystemComponent.java
// Common interface for both files and folders
public interface FileSystemComponent {
    void showDetails(String indent);
}

Ένα οποιοδήποτε αρχείο αποτελεί φύλλο της ιεραρχίας, καθώς δεν περιέχει άλλα στοιχεία. Η παρακάτω κλάση File δεν πρέπει να συγχέεται με την κλάση java.io.File του JDK.

File.java
// Leaf class
public class File implements FileSystemComponent {
    private String name;
    private long size; // σε KB
 
    public File(String name, long size) {
        this.name = name;
        this.size = size;
    }
 
    @Override
    public void showDetails(String indent) {
        System.out.println(indent + "📄 Αρχείο: " + name + " (" + size + " KB)");
    }
}

Ο φάκελος είναι το “σύνθετο” αντικείμενο. Περιέχει μια λίστα από FileSystemComponent (άρα μπορεί να περιέχει αρχεία και άλλους φακέλους).

Folder.java
import java.util.ArrayList;
import java.util.List;
 
// Composite class
public class Folder implements FileSystemComponent {
    private String name;
    private List<FileSystemComponent> components = new ArrayList<>();
 
    public Folder(String name) {
        this.name = name;
    }
 
    public void addComponent(FileSystemComponent component) {
        components.add(component);
    }
 
    public void removeComponent(FileSystemComponent component) {
        components.remove(component);
    }
 
    @Override
    public void showDetails(String indent) {
        System.out.println(indent + "Folder: [" + name + "]");
        // Αναδρομική κλήση της μεθόδου για όλα τα παιδιά του φακέλου
        for (FileSystemComponent component : components) {
            component.showDetails(indent + "   ");
        }
    }
}

Ο κώδικας προς εκτέλεση ενός συστήματος αρχείων θα μπορούσε να είναι ο εξής:

FileSystem.java
public class FileSystem {
    public static void main(String[] args) {
        // Δημιουργία απλών αρχείων (Leafs)
        FileSystemComponent file1 = new File("resume.pdf", 120);
        FileSystemComponent file2 = new File("photo.jpg", 1500);
        FileSystemComponent file3 = new File("script.py", 15);
        FileSystemComponent file4 = new File("notes.txt", 12);
 
        // Δημιουργία υποφακέλου και προσθήκη αρχείων σε αυτόν
        Folder subFolder = new Folder("Document Folder");
        subFolder.addComponent(file1);
        subFolder.addComponent(file3);
 
        // Δημιουργ`ία κεντρικού φακέλου (Root Composite)
        Folder homeFolder  = new Folder("Home Filder");
        homeFolder.addComponent(file2);      // Προσθήκη αρχείου απευθείας στο root
        homeFolder.addComponent(file4);      // Προσθήκη αρχείου απευθείας στο root
        homeFolder.addComponent(subFolder);  // Προσθήκη του υποφακέλου στο root
 
        // Εμφάνιση όλης της δομής με μία κλήση!
        System.out.println("--- File System Display ---");
        homeFolder.showDetails("");
    }
}

Πλεονεκτήματα του Composite Pattern

  • Πολυμορφισμός & Ευκολία: Μπορείς να χρησιμοποιήσεις τον πολυμορφισμό για να εκτελέσεις με ενιαίο τρόπο λειτουργίες σε όλο το δέντρο των αντικειμένων χωρίς να διαφέρει αν βρίσκεσαι σε αρχείο ή φάκελο.
  • Επεκτασιμότητα (Open/Closed Principle): Μπορείς να προσθέσεις νέους τύπους “Φύλλων” ή “Σύνθετων” αντικειμένων που υπακούουν στο δεδομένο interface, χωρίς να χαλάσεις τον υπάρχοντα κώδικα του client. Στο παραπάνω παράδειγμα, θα μπορούσαμε να προσθέσουμε μία επιπλέον κλάση που να περιγράφει symbolink links (συμβολικούς συνδέσμους) μέσα στο filesystem, δηλαδή διασυνδέσεις προς άλλα αρχεία ή φακέλους που όμως εμφανίζονται σαν να υπάρχουν στο σημείο του filesystem που περιέχεται ο σύνδεσμος.
SymbolicLink.java
// Leaf class representing a Symbolic Link (Συμβολικός Σύνδεσμος)
public class SymbolicLink implements FileSystemComponent {
    // Κρατάμε αναφορά στο αντικείμενο-στόχο (target) που δείχνει το symlink
    private FileSystemComponent target; 
 
    public SymbolicLink(FileSystemComponent target) {
        this.target = target;
    }
 
    @Override
    public void showDetails(String indent) {
        if (target != null) {
            target.showDetails(indent);
        } else {
            System.out.println("Broken Link!");
        }
    }
}
oop/composite_pattern.txt · Last modified: 2026/05/25 09:12 by gthanos