29.10.07

Composite

Από χθες άρχισα να ξεσκονίζω το GoF βιβλίο για Design Patterns. Θα ξεκινήσουμε κάτι παρουσιάσεις στην δουλειά σχετικά με το θέμα. Σε μένα έπεσε ο κλήρος του Composite pattern.

Τα patterns ξεκινήσανε από τους Erich Gamma ,Richard Helm, Ralph Johnson, John Vlissides οι οποίοι εμπενεόμενοι από αντίστοιχα βιβλία αρχιτεκτονικής, αποφασίσαν να κατασκευάσουν ένα κατάλογο από software design patterns, συνταγές, προσχέδια κώδικα τα οποία συμβαίνει να εμφανίζονται πολύ συχνά. Το βιβλίο εκδόθηκε το 1995 με τίτλο: Design Patterns: Elements of Reusable Object-Oriented Software και οι συγγραφείς γίνανε γνωστοί ως η συμμορία των τεσσάρων: Gang of Four (GoF).

Σχετικά με το Composite τώρα:
ας υποθέσουμε ότι θέλετε να κατασκευάσετε μια εφαρμογή που να υπολογίζει το συνολικό μέγεθος ενός καταλόγου στον δίσκο, ο οποίος με την σειρά του περιέχει άλλα αρχεία και άλλους καταλόγους που αναδρομικά περιέχουν δικά τους αρχεία και καταλόγους κτλ. κτλ. κτλ. Πως υπολογίζετε το συνολικό μέγεθος αυτών των αρχείων, χωρίς να καταλήξετε σε μια πληθώρα από for-if-else? Εδώ έρχεται σε βοήθεια το Composite pattern.

Απλουστεύοντας, δύο είναι οι βασικές οντότητες σε ένα σύστημα αρχείων:
  • Οι κατάλογοι: folders που περιέχουν άλλους καταλόγους και αρχεία και το μέγεθός τους αποτελείται από το άθροισμα του μεγέθους των ῾παιδιών῾ αρχείων που περιέχουν.
  • Τα αρχεία: files που έχουν μέγεθος αλλα δεν περιέχουν άλλα αρχεία ή καταλόγους.
Πέρα από τα πολλά άλλα κοινά που έχουν αυτές οι δύο οντότητες (όνομα, ημ/νία δημιουργίας, κ.α.) έχουν και κάτι κοινό που μας ενδιαφέρει στο παράδειγμά μας: Το μέγεθός τους. Για να το μοντελοποιήσουμε ορίζουμε:

/**** Interface Node - our component! *****/
public interface Node {
long calculateSize();
}


Το composite pattern βασίζεται στην έννοια του component. Το component είναι κατουσία ένα interface που ορίζει την μέθοδο (ή μεθόδους) που μας ενδιαφέρει.

Ορίζουμε λοιπόν τις εξής κλάσεις που υλοποιούν το interface Node:

/**** Class File - The leaf ****/
public class File implements Node {
private long size = 0;
private String name;

public File(String name, long size) {
this.size = size;
this.name = name;
}

public long calculateSize() {
return this.size;
}
}

/*** Class Folder - The composite **/ 
public class Folder implements Node {
private String name;

/** The list of children nodes. */
private List childrenNodes = null;

/** Constructor. */
public Folder(String name) {
this.name = name;
this.childrenNodes = new ArrayList();
}

/** Calculates the total size. */
public long calculateSize() {
long totalSize = 0;
for (Node node : childrenNodes) {
totalSize += node.calculateSize();
}
return totalSize;
}

public void addNode(Node node) {
childrenNodes.add(node);
}

public void removeNode(Node node) {
childrenNodes.remove(node);
}

public String getName() {
return name;
}

}


Από τις παραπάνω κλασεις το Folder είναι το composite γιατί είναι composed από πολλά άλλα στοιχεία-nodes. Το File είναι ένα απλο leaf το οποίο δεν έχει δικά του παρακλάδια. Η χρήση του κοινού interface θα μας βοηθήσει να υπολογίσουμε το συνολικό μέγεθος χωρίς την χρήση for-if-else ως εξής:

public class Client {
public static void main(String[] args) {
Folder root = new Folder("root");
File rootLog = new File("root-log.log", 100);
root.addNode(rootLog);

Folder home = new Folder("home");
File homeLog = new File("home-log.log", 200);
home.addNode(homeLog);

Folder homeUserA = new Folder("homeUserA");
File fileOfUserA = new File(".bashrc", 3);
homeUserA.addNode(fileOfUserA);
home.addNode(homeUserA);

Folder homeUserB = new Folder("homeUserB");
File fileOfUserB = new File(".bashrc", 7);
homeUserB.addNode(fileOfUserB);
home.addNode(homeUserB);

root.addNode(home);

Folder usr = new Folder("usr");
File usrLog = new File("usr-log.log", 50);
usr.addNode(usrLog);
root.addNode(usr);

System.out.println("Total: " + root.calculateSize());
}

}


Το pattern έχει αρκετές παραλλαγές που ταιριάζουν ανάλογα με την περίσταση. Για περισσότερες λεπτομέρεις μπορείτε απλά να google-αρετε αλλά το βιβλίο των G0F είναι το καλύτερο, απλα χρησιμοποιεί C++ κυρίως που ίσως δυσκολέψει.

Να σημειώσω ότι τα patterns δεν είναι πανάκοια. Είναι συνταγές για την συγγραφή κώδικα και τπτ άλλο και δεν πρέπει να γίνονται αυτοσκοπός αλλα το μέσω επίλυσης προβλημάτων. Για περισσότερες πληροφορίες σχετικά με την Java υπάρχει το site του Jhug με αρκετό υλικό για κάθε επίπεδο.

3 comments:

javapapo said...

γεια σου cogoun με τα patterns σου, περιμένουμε να σε απολαύσουμε και Live!

Θωμάς said...

μπράβο ρε Κώστα πολύ καλό

Anonymous said...

Ένα από τα πιο εντυπωσιακά blogs που έχω δει. Ευχαριστώ πολύ για τη διατήρηση του διαδικτύου αριστοκρατικό για μια αλλαγή. Youve πήρε το ύφος, την τάξη, νταηλίκι. Το εννοώ. Παρακαλώ κρατήστε επάνω, διότι χωρίς το Διαδίκτυο είναι σίγουρα στερείται νοημοσύνης.