Τα δημιουργικά πρότυπα περιγράφηκαν για πρώτη φορά στη διάσημη συμμορία των τεσσάρων Το βιβλίο παρουσιάζει κάθε μοτίβο σε ένα ειδικό κεφάλαιο και ακολουθεί μια αυστηρή δομή για κάθε ένα: πρόθεση, κίνητρο, εφαρμογή, δομή, συμμετέχοντες, συνεργασίες, συνέπειες, εφαρμογή, κωδικούς δείγματος, γνωστές χρήσεις και συναφή μοτίβα. Σχεδιασμός Patterns Για παράδειγμα, εδώ είναι ένα απόσπασμα για το Το πρότυπο: οικοδόμος Προσπάθεια : Διαχωρίστε την κατασκευή ενός πολύπλοκου αντικειμένου από την αναπαράστασή του, έτσι ώστε η ίδια διαδικασία κατασκευής να μπορεί να δημιουργήσει διαφορετικές αναπαραστάσεις. Εφαρμογή : Χρησιμοποιήστε το πρότυπο κατασκευαστή όταν Ο αλγόριθμος για τη δημιουργία ενός πολύπλοκου αντικειμένου πρέπει να είναι ανεξάρτητος από τα Μέρη που συνθέτουν το αντικείμενο και τον τρόπο συναρμολόγησής τους. Η διαδικασία κατασκευής πρέπει να επιτρέπει διαφορετικές αναπαραστάσεις για το αντικείμενο που κατασκευάζεται. : Intent Διαχωρίστε την κατασκευή ενός πολύπλοκου αντικειμένου από την αναπαράστασή του, έτσι ώστε η ίδια διαδικασία κατασκευής να μπορεί να δημιουργήσει διαφορετικές αναπαραστάσεις. : Applicability Χρησιμοποιήστε το πρότυπο κατασκευαστή όταν Ο αλγόριθμος για τη δημιουργία ενός πολύπλοκου αντικειμένου πρέπει να είναι ανεξάρτητος από τα Μέρη που συνθέτουν το αντικείμενο και τον τρόπο συναρμολόγησής τους. Η διαδικασία κατασκευής πρέπει να επιτρέπει διαφορετικές αναπαραστάσεις για το αντικείμενο που κατασκευάζεται. Το GoF (Gang of Four) ήταν θεμελιώδες στον τομέα του προγραμματισμού με αντικείμενο και επηρέασε το σχεδιασμό γλωσσών προγραμματισμού, συμπεριλαμβανομένων ευρέως διαδεδομένων γλωσσών όπως η Java. Καθώς επιστρέφω σε μια θέση μηχανικού, έρχομαι αντιμέτωπος με πρόσφατα γραμμένο κώδικα Java που έχει πολλές δυνατότητες βελτίωσης όσον αφορά τη συντήρηση. Λειτουργεί, αλλά φαντάζομαι ότι οι μηχανικοί πρέπει να το ενημερώσουν, συμπεριλαμβανομένου του μελλοντικού εαυτού του αρχικού συγγραφέα και του εαυτού μου, και είμαι σίγουρος ότι μπορώ να τους βοηθήσω. Ένας κατασκευαστής με πολλές παραμέτρους του ίδιου τύπου. Ας φανταστούμε έναν κατασκευαστή με πολλές παραμέτρους String: public License ( String id, String licenseeName, String licenseId, String environment, LocalDateTime generatedAt ) Κατά την κλήση αυτού του κατασκευαστή, οι πιθανότητες είναι ότι ο καλεσμένος μπορεί ακούσια να αλλάξει παραγγελίες παραμέτρων: var license = new License("My license", "XXX-123", "Customer", "User-acceptance tests", new LocalDateTime()); Oops, άλλαξα το όνομα του δικαιούχου και το αναγνωριστικό άδειας χρήσης. το IDE σας μπορεί να βοηθήσει εδώ, αλλά υπάρχουν και άλλοι τρόποι. Είδος Wrappers Οι υποστηρικτές της καθαρής OOP θα επισημάνουν ευχαρίστως ότι κανείς δεν πρέπει ποτέ να χρησιμοποιεί άμεσα μια γραμμή. Η Java : ΕΓΓ record public record Id(String id) { ... } public record LicenseeName(String licenseeName) { ... } public record LicenseeId(String licenseId) { ... } public record Environment(String environment) { ... } public record GeneratedAt(LocalDateTime generatedAt) { ... } Δεν μπορούμε να κάνουμε λάθος: var id = new Id("My license"); var licenseeName = new LicenseeName("Customer"); var licenseId = new LicenseeId("XXX-123"); var environment = new Environment("User-acceptance tests"); var generatedAt = new LocalDateTime(); var license = new License(id, licenseId, licenseName, environment, generatedAt); //1 Λάθος χρονολόγησης Ενώ αυτή η προσέγγιση βελτιώνει σίγουρα τη συντήρηση, η συσκευασία συσκευασίας αυξάνει το μέγεθος της μνήμης.Η ακριβής αύξηση εξαρτάται από την εφαρμογή JDK, αλλά για έναν μόνο τύπο, είναι περίπου 5 φορές μεγαλύτερη. Ο Κώστας Τσακαλώτος, παρέχοντας : η συσκευασία είναι μια επιλογή χρόνου σύνταξης, αλλά ο bytecode δείχνει τον τυποποιημένο τύπο με τον ακόλουθο περιορισμό: Κλάσεις αξίας inline Μια κατηγορία inline πρέπει να έχει μια μοναδική ιδιότητα που αρχικοποιείται στον κύριο κατασκευαστή. Μια κατηγορία inline πρέπει να έχει μια μοναδική ιδιότητα που αρχικοποιείται στον κύριο κατασκευαστή. Ονομαστικές παραμέτρους Η Java προσφέρει μόνο κλήσεις μεθόδου με παραμέτρους θέσης, αλλά άλλες γλώσσες, , Python, Kotlin και Rust, προσφέρουν επίσης ονομαστικές παραμέτρους. ΕΓΓ Εδώ είναι ένας κατασκευαστής Kotlin που αντικατοπτρίζει την παραπάνω κατηγορία: class License ( val id: String, val licenseeName: String, val licenseId: String, val environment: String, val generatedAt: LocalDateTime ) Μπορείτε να καλέσετε τον κατασκευαστή με το όνομα των παραμέτρων, μειώνοντας έτσι τον κίνδυνο να κάνετε ένα λάθος: val license = License( id = "My license", licenseeName = "Customer", licenseId = "XXX-123", environment = "User-acceptance tests", generatedAt = LocalDateTime() ) Η ΠΑΤΕΡΝ οικοδόμος Η Το πρότυπο είναι μια άλλη βιώσιμη προσέγγιση, παρόλο που δεν αποτελεί μέρος των περιπτώσεων χρήσης που περιγράφονται στο GoF. οικοδόμος Ιδού ο κώδικας: public class License { private final String id; private final String licenseeName; private final String licenseId; private final String environment; private final LocalDateTime generatedAt; private License ( //1 String id, String licenseeName, String licenseId, String environment, LocalDateTime generatedAt ) { ... } public static LicenseBuilder builder() { //2 return new LicenseBuilder(); } public static class LicenseBuilder { private String id; //3 private String licenseeName; //3 private String licenseId; //3 private String environment; //3 private LocalDateTime generatedAt; //3 private LicenseBuilder() {} //1 public LicenseBuilder withId(String id) { //4 this.id = id; //5 return this; //4 } // Other `withXXX` methods public License build() { //6 return new License( id, licenseeName, licenseId, environment, generatedAt ); } } } Αποφυγή άμεσης εμφάνισης αντικειμένου Δημιουργία νέου κατασκευαστή Τα πεδία του κατασκευαστή μιμούνται τα πεδία του αντικειμένου Κάθε μέθοδος επιστρέφει το ίδιο το αντικείμενο του κατασκευαστή Εισαγάγετε το χαρακτηριστικό Επιστρέφει το πλήρες αντικείμενο Μπορούμε τώρα να ονομάσουμε τον οικοδόμο ως τέτοιο: val license = License.builder() .withId("My license") .withLicenseName("Customer") .withLicenseId("XXX-123") .withEnvironment("User-acceptance tests") .withGeneratedAt(new LocalDateTime()) Η δημιουργία του κώδικα κατασκευαστή είναι επώδυνη (εκτός αν χρησιμοποιείτε AI), αλλά επιτρέπει καλύτερη αναγνωσιμότητα. Επιπλέον, μπορεί κανείς να προσθέσει επικύρωση για κάθε κλήση μεθόδου, εξασφαλίζοντας ότι το αντικείμενο υπό κατασκευή είναι έγκυρο. . Προσωπικός οικοδόμος Συνοπτική Approach Pros Cons Type wrappers Object-Oriented Programming - More verbose- Can be memory-heavy depending on the language Named parameters Easy Not available in Java Builder pattern Verbose - Allows creating complex objects- Allows validating Είδος Wrappers Προγραμματισμός προσανατολισμένος σε αντικείμενα - Περισσότερο verbose- Μπορεί να είναι μνήμη-βαρύ ανάλογα με τη γλώσσα Ονομαστικές παραμέτρους Εύκολα Δεν είναι διαθέσιμο σε Java Κατασκευαστής Pattern ΛΑΘΟΣ - Επιτρέπει τη δημιουργία πολύπλοκων αντικειμένων- Επιτρέπει την επικύρωση Οι κατασκευαστές ρίχνουν εξαιρέσεις Στην ίδια βάση κωδικών, βρήκα τον ακόλουθο κώδικα: public Stuff(UuidService uuidService, FallbackUuidService fallbackUuidService) { try { uuid = uuidService.getUuid(); } catch(CannotGetUuidException e) { try { uuid = fallbackUuidService.getUuid(); } catch(CannotGetUuidException e1) { uuid = "UUID can be fetched"; } } } Με μια μικρή εμπειρία, μπορείτε να παρατηρήσετε τι είναι λάθος στο παραπάνω κομμάτι. αρχικοποιείται με μια γραμμή που δεν είναι UUID. Όλος ο κώδικας που βασίζεται στο UUID πρέπει να ασχολείται με ενδεχομένως μη-UUID τιμές. Πρέπει να αποτύχει γρήγορα. uuid public Stuff(UuidService uuidService, FallbackUuidService fallbackUuidService) { try { uuid = uuidService.getUuid(); } catch(CannotGetUuidException e) { try { uuid = fallbackUuidService.getUuid(); } catch(CannotGetUuidException e1) { throw new RuntimeException(e1); } } } Τώρα, κάθε Το αντικείμενο έχει έγκυρο UUID. Ωστόσο, η τοποθέτηση εξαιρέσεων μέσα σε κατασκευαστές έχει πιθανά προβλήματα: Stuff Διαρροές πόρων: Εάν ο κατασκευαστής εκχωρήσει πόρους (π.χ. αρχεία, υποδοχές) και πετάξει μια εξαίρεση, αυτοί οι πόροι μπορεί να μην απελευθερωθούν. Κληρονομικότητα: Εάν ένας κατασκευαστής υπερκλάσης ρίξει μια εξαίρεση, ο κατασκευαστής υποκλάσης δεν θα τρέξει. Ελεγχόμενες εξαιρέσεις: Είναι αδύνατο να χρησιμοποιηθούν ελεγχόμενες εξαιρέσεις σε κατασκευαστές, μόνο εκείνες του χρόνου εκτέλεσης. Για αυτούς τους λόγους, νομίζω ότι οι εξαιρέσεις δεν έχουν τη θέση τους στους κατασκευαστές και τους αποφεύγω. μοτίβο που περιγράφεται στην πρώτη ενότητα, αλλά όπως αναφέρθηκε, είναι πολύ κώδικα? δεν νομίζω ότι είναι απαραίτητο. . οικοδόμος Εργοστασιακή μέθοδος Προσπάθειες Ορίστε μια διεπαφή για τη δημιουργία ενός αντικειμένου, αλλά αφήστε τις υποκατηγορίες να αποφασίσουν ποια τάξη να παρασταθεί. Εφαρμογή Χρησιμοποιήστε το πρότυπο εργοστασιακής μεθόδου όταν: Μια τάξη δεν μπορεί να προβλέψει την τάξη αντικειμένων που πρέπει να δημιουργήσει. Μια κατηγορία θέλει οι υποκατηγορίες της να καθορίζουν τα αντικείμενα που δημιουργεί. Οι κατηγορίες αναθέτουν την ευθύνη σε μία από τις διάφορες υποκατηγορίες βοηθών και θέλετε να εντοπίσετε τη γνώση της οποίας η υποκατηγορία βοηθών είναι ο αντιπρόσωπος. Intent Ορίστε μια διεπαφή για τη δημιουργία ενός αντικειμένου, αλλά αφήστε τις υποκατηγορίες να αποφασίσουν ποια τάξη να παρασταθεί. Applicability Χρησιμοποιήστε το πρότυπο εργοστασιακής μεθόδου όταν: Μια τάξη δεν μπορεί να προβλέψει την τάξη αντικειμένων που πρέπει να δημιουργήσει. Μια κατηγορία θέλει οι υποκατηγορίες της να καθορίζουν τα αντικείμενα που δημιουργεί. Οι κατηγορίες αναθέτουν την ευθύνη σε μία από τις διάφορες υποκατηγορίες βοηθών και θέλετε να εντοπίσετε τη γνώση της οποίας η υποκατηγορία βοηθών είναι ο αντιπρόσωπος. Σημειώστε ότι σε αυτή την περίπτωση, το χρησιμοποιούμε για διαφορετικό λόγο. εδώ είναι ο ενημερωμένος κώδικας: public class Stuff { private final UUID uuid; private Stuff(UUID uuid) { //1 this.uuid = uuid; } public static Stuff create(UuidService uuidService, FallbackUuidService fallbackUuidService) throws CannotGetUuidException { try { return new Stuff(uuidService.getUuid()); } catch(CannotGetUuidException e) { return new Stuff(fallbackUuidService.getUuid()); //2 } } } Αποφύγετε την εξωτερική διείσδυση Αν αποτύχει, ρίχνει ένα νέο CannotGetUuidException Κάποιος ονομάζει τα παραπάνω έτσι: var stuff = Stuff.create(uuidService, fallbackUuidService); //1 Χρειάζεται να πιάσει CannotGetUuidΕξαίρεση Σε αυτό το σημείο, είμαστε σίγουροι ότι το αντικείμενο είναι πλήρως αρχικοποιημένο εάν η κλήση είναι επιτυχής. Συμπέρασμα Σε αυτή την ανάρτηση, έχω περιγράψει δύο χρήσεις των δημιουργικών μοτίβων του GoF, τα οποία δεν αναφέρονται στο βιβλίο: βελτίωση της συντήρησης και εξασφάλιση των αντικειμένων είναι πλήρως αρχικοποιημένα. To go further: Μοντέλα σχεδιασμού GoF Μια βουτιά στο μοτίβο του οικοδόμου Το πρότυπο του κατασκευαστή είναι μια μηχανή πεπερασμένης κατάστασης! Δημοσιεύτηκε αρχικά στο A Java Geek στις 31 Αυγούστου, 2025 Το Java Geek