Strategy (Στρατηγική) — ένα συμπεριφορικό μοτίβο σχεδίασης που ορίζει μια οικογένεια εναλλάξιμων αλγορίθμων και τοποθετεί καθέναν από αυτούς σε ξεχωριστή κλάση (Strategy). Το μοτίβο επιτρέπει την επιλογή αλγορίθμου εν κινήσει: ο κώδικας πελάτη λειτουργεί μέσω μιας κοινής διεπαφής Strategy και η συγκεκριμένη υλοποίηση εισάγεται κατά τον χρόνο εκτέλεσης. Στο iOS το μοτίβο υλοποιείται μέσω Protocol + κλάσεων στρατηγικής, στο Android — μέσω Interface + υλοποιήσεων. Το Strategy — ένα από τα 23 μοτίβα GoF, χρησιμοποιείται ευρέως για επεξεργασία πληρωμών, επικύρωση, ταξινόμηση και φιλτράρισμα δεδομένων. Περισσότερα — στην αρχική περιγραφή GoF.
Σημαντικά
Strategy — ένα από τα 23 μοτίβα GoF (Gang of Four), που περιγράφεται στο βιβλίο «Design Patterns: Elements of Reusable Object-Oriented Software» (1994). Το μοτίβο λύνει το πρόβλημα της επιλογής αλγορίθμου κατά τον χρόνο εκτέλεσης. Αντί να γράφετε μία κλάση με πολλούς τελεστές υπό όρους (if-else, switch), το Strategy προτείνει να διαχωρίσετε κάθε αλγόριθμο σε ξεχωριστή κλάση με κοινή διεπαφή. Το πλαίσιο (η κλάση που χρησιμοποιεί τη στρατηγική) αποθηκεύει μια αναφορά στη διεπαφή Strategy και αναθέτει την εκτέλεση στη συγκεκριμένη στρατηγική.
Η δομή του μοτίβου περιλαμβάνει τρία στοιχεία: Context (πλαίσιο) περιέχει αναφορά στο Strategy και καλεί τη μέθοδό του; Strategy (διεπαφή) δηλώνει μια κοινή μέθοδο για όλους τους αλγορίθμους; ConcreteStrategy (συγκεκριμένη στρατηγική) υλοποιεί τη διεπαφή και περιέχει τον συγκεκριμένο αλγόριθμο. Ο πελάτης δημιουργεί την απαιτούμενη στρατηγική και τη μεταβιβάζει στο πλαίσιο μέσω κατασκευαστή, setter ή παραμέτρου μεθόδου. Το πλαίσιο δεν γνωρίζει ποια στρατηγική εκτελείται — λειτουργεί μόνο με τη διεπαφή.
| Συστατικό | Ρόλος | Παράδειγμα |
|---|---|---|
| Context | Περιέχει αναφορά στο Strategy | PaymentProcessor, Sorter |
| Strategy | Κοινή διεπαφή για αλγορίθμους | Protocol PaymentStrategy |
| ConcreteStrategy | Συγκεκριμένη υλοποίηση αλγορίθμου | CardPayment, PayPalPayment |
Αρχή Open/Closed — το κύριο πλεονέκτημα του Strategy. Το σύστημα είναι ανοιχτό για επέκταση (μπορεί να προστεθεί νέα στρατηγική) και κλειστό για τροποποίηση (δεν χρειάζεται να αλλάξει ο κώδικας του πλαισίου). Χωρίς το μοτίβο, η προσθήκη νέου αλγορίθμου απαιτεί τροποποίηση της υπάρχουσας κλάσης, πράγμα που παραβιάζει το OCP και αυξάνει τον κίνδυνο σφαλμάτων παλινδρόμησης. Το Strategy μειώνει επίσης το μέγεθος των κλάσεων: αντί για μία κλάση 200 γραμμών με switch-case, έχετε 6 κλάσεις των 20 γραμμών η καθεμία.
Strategy σε Swift υλοποιείται μέσω Protocol (διεπαφή στρατηγικής) και κλάσεων ή δομών στρατηγικής. Τα πρωτόκολλα Swift υποστηρίζουν associated types και generic constraints, που παρέχουν ευελιξία στον σχεδιασμό στρατηγικών. Το πλαίσιο είναι συνήθως μια κλάση ViewModel ή μια υπηρεσία που λαμβάνει τη στρατηγική στο init ή μέσω μιας ιδιότητας. Το μοτίβο χρησιμοποιείται ευρέως σε έργα iOS για χειρισμό συμβάντων, κινούμενα σχέδια, μορφοποίηση δεδομένων και στρατηγικές UI.
// 1. Protocol Strategy
protocol PaymentStrategy {
func pay(amount: Decimal) async throws -> PaymentResult
}
// 2. Συγκεκριμένες στρατηγικές
struct CardPaymentStrategy: PaymentStrategy {
let cardNumber: String
let cvv: String
func pay(amount: Decimal) async throws -> PaymentResult {
// Αποστολή αιτήματος στο τραπεζικό API
return PaymentResult(status: .success, transactionId: "tx_\(UUID())")
}
}
struct PayPalPaymentStrategy: PaymentStrategy {
let email: String
func pay(amount: Decimal) async throws -> PaymentResult {
// Ανακατεύθυνση στο PayPal SDK
return PaymentResult(status: .success, transactionId: "pp_\(UUID())")
}
}
// 3. Context
class PaymentProcessor {
private var strategy: PaymentStrategy
init(strategy: PaymentStrategy) {
self.strategy = strategy
}
func setStrategy(_: PaymentStrategy) {
strategy = strategy
}
func processPayment(amount: Decimal) async throws -> PaymentResult {
return try await strategy.pay(amount: amount)
}
}
// Χρήση
let processor = PaymentProcessor(strategy: CardPaymentStrategy(cardNumber: "4111...", cvv: "123"))
let result = try await processor.processPayment(amount: 99.99)
Strategy σε SwiftUI — το μοτίβο ενσωματώνεται φυσικά με το MVVM. Το ViewModel περιέχει μια ιδιότητα στρατηγικής και καλεί τη μέθοδό της κατά την ενέργεια του χρήστη. Το SwiftUI View λαμβάνει δεδομένα μέσω @Published ή @State — η στρατηγική κρύβει λεπτομέρειες υλοποίησης από το View. Για παράδειγμα, η στρατηγική επικύρωσης κειμένου (emailValidator, phoneValidator) αλλάζει ανάλογα με τον τύπο πεδίου εισόδου. Ο συνδυασμός Strategy με SwiftUI παρέχει ευελιξία χωρίς κληρονομικότητα από το UIKit.
Strategy σε Kotlin χρησιμοποιεί Interface σε επίπεδο γλώσσας και λειτουργικές διεπαφές (SAM) για απλοποίηση. Η Kotlin υποστηρίζει lambdas, που επιτρέπει τη μεταβίβαση αλγορίθμων ως συναρτήσεων χωρίς δήλωση ξεχωριστής κλάσης στρατηγικής. Στο Android το μοτίβο εφαρμόζεται σε ViewModel και Use Cases για απομόνωση αλγορίθμων φόρτωσης δεδομένων, προσωρινής αποθήκευσης και διαχείρισης σφαλμάτων. Έργα Android με Clean Architecture χρησιμοποιούν Strategy για έγχυση διαφορετικών υλοποιήσεων repository ανάλογα με τις σημαίες (mock, real, cache).
// 1. Interface Strategy
interface PaymentStrategy {
suspend fun pay(amount: BigDecimal): PaymentResult
}
// 2. Συγκεκριμένες στρατηγικές
class CardPaymentStrategy(
private val cardNumber: String,
private val cvv: String
) : PaymentStrategy {
override suspend fun pay(amount: BigDecimal): PaymentResult {
// Τραπεζικό API μέσω Retrofit
return PaymentResult(success = true, transactionId = "tx_${UUID.randomUUID()}")
}
}
class PayPalPaymentStrategy(
private val email: String
) : PaymentStrategy {
override suspend fun pay(amount: BigDecimal): PaymentResult {
// PayPal SDK integration
return PaymentResult(success = true, transactionId = "pp_${UUID.randomUUID()}")
}
}
// 3. Context
class PaymentProcessor(
private val strategy: PaymentStrategy
) {
fun setStrategy(strategy: PaymentStrategy): PaymentProcessor {
return PaymentProcessor(strategy)
}
suspend fun processPayment(amount: BigDecimal): PaymentResult {
return strategy.pay(amount)
}
}
// Χρήση στο ViewModel
class CheckoutViewModel : ViewModel() {
private var processor = PaymentProcessor(CardPaymentStrategy("4111...", "123"))
fun payWithCard() {
viewModelScope.launch {
val result = processor.processPayment(BigDecimal("99.99"))
// Επεξεργασία αποτελέσματος
}
}
}
Strategy με Hilt/Dagger — σε έργα Android οι στρατηγικές συχνά εγχέονται μέσω DI. Η Hilt παρέχει τη συγκεκριμένη υλοποίηση του PaymentStrategy μέσω @Binds ή @Provides. Αυτό επιτρέπει την αλλαγή στρατηγικής χωρίς τροποποίηση του κώδικα πλαισίου — αρκεί να αλλάξετε το DI module για διαφορετική έκδοση (debug/release). Για παράδειγμα, για αποσφαλμάτωση εγχέεται MockPaymentStrategy, για παραγωγή — η πραγματική τραπεζική στρατηγική. Ο συνδυασμός Strategy + DI παρέχει μέγιστη ευελιξία.
Strategy vs State — δομικά τα μοτίβα είναι ταυτόσημα: και τα δύο χρησιμοποιούν σύνθεση με διεπαφή και συγκεκριμένες κλάσεις. Η διαφορά είναι στον σκοπό: το Strategy επιλέγει έναν ανεξάρτητο αλγόριθμο, το State διαχειρίζεται τη συμπεριφορά του αντικειμένου ανάλογα με την κατάστασή του. Στο State το πλαίσιο αλλάζει μόνο του τη στρατηγική κατά την αλλαγή κατάστασης, στο Strategy το πλαίσιο δεν ελέγχει την εναλλαγή — ο πελάτης καθορίζει ρητά τον αλγόριθμο. Οι στρατηγικές δεν γνωρίζουν η μία την άλλη, οι καταστάσεις μπορούν να μεταβαίνουν η μία στην άλλη.
Strategy vs Command — το Command ενθυλακώνει μια μεμονωμένη ενέργεια ως αντικείμενο, το Strategy ενθυλακώνει ένα σύνολο εναλλάξιμων αλγορίθμων. Command — «τι να κάνουμε» (μία κλήση execute), Strategy — «πώς να το κάνουμε» (αλγόριθμος πολλών βημάτων). Το Command χρησιμοποιείται για ουρές, καθυστερημένη εκτέλεση, undo/redo. Το Strategy — για επιλογή τρόπου εκτέλεσης εργασίας κατά τον χρόνο εκτέλεσης. Οι εντολές μπορούν να παραμετροποιηθούν με στρατηγικές, συνδυάζοντας και τα δύο μοτίβα.
| Χαρακτηριστικό | Strategy | State | Command | Template Method |
|---|---|---|---|---|
| Σκοπός | Εναλλάξιμοι αλγόριθμοι | Συμπεριφορά από κατάσταση | Ενθυλάκωση αιτήματος | Σκελετός αλγορίθμου |
| Αλλαγή | Ρητά από τον πελάτη | Αυτόματα από το πλαίσιο | Από τον πελάτη ή ουρά | Με κληρονομικότητα |
| Επίπεδο | Αντικείμενο (σύνθεση) | Αντικείμενο (σύνθεση) | Αντικείμενο | Κλάση (κληρονομικότητα) |
Strategy vs Template Method — και τα δύο μοτίβα ορίζουν αλγορίθμους, αλλά με διαφορετικούς τρόπους. Το Template Method χρησιμοποιεί κληρονομικότητα: η βασική κλάση ορίζει τον σκελετό του αλγορίθμου (μέθοδο προτύπου), οι υποκλάσεις παρακάμπτουν μεμονωμένα βήματα. Το Strategy χρησιμοποιεί σύνθεση: ο αλγόριθμος μεταφέρεται εξ ολοκλήρου σε ξεχωριστή κλάση. Το Template Method είναι απλούστερο για περιπτώσεις με σταθερή δομή αλγορίθμου, το Strategy — όταν οι αλγόριθμοι είναι εντελώς διαφορετικοί και μπορούν να αλλάζουν δυναμικά.
Επεξεργασία πληρωμών — το κλασικό παράδειγμα του Strategy. Το καλάθι ενός ηλεκτρονικού καταστήματος περιέχει μια λίστα προϊόντων και ο τρόπος πληρωμής επιλέγεται από τον χρήστη. Κάθε τρόπος (κάρτα, PayPal, Apple Pay, Google Pay, κρυπτονόμισμα) — ξεχωριστή στρατηγική με κοινή υπογραφή pay(amount). Το πλαίσιο PaymentProcessor δεν γνωρίζει πώς ακριβώς γίνεται η πληρωμή — καλεί την κοινή μέθοδο. Η προσθήκη νέου τρόπου πληρωμής δεν απαιτεί τροποποίηση του κώδικα του καλαθιού.
Επικύρωση δεδομένων — το Strategy εφαρμόζεται για διαφορετικούς κανόνες επικύρωσης του ίδιου πεδίου. Οι EmailValidatorStrategy, PhoneValidatorStrategy, AgeValidatorStrategy υλοποιούν την κοινή διεπαφή ValidationStrategy με τη μέθοδο validate(input). Η φόρμα εγγραφής χρησιμοποιεί ένα σύνολο στρατηγικών για τον έλεγχο κάθε πεδίου. Οι στρατηγικές επικύρωσης μπορούν να συνδυαστούν σε αλυσίδα (Chain of Responsibility) ή να εφαρμοστούν όλες ταυτόχρονα σε έναν βρόχο. Αυτό αντικαθιστά τους μακροσκελείς ελέγχους if-else με μια συλλογή πολυμορφικών επικυρωτών.
// Στρατηγική ταξινόμησης
protocol SortingStrategy {
func sort<T>(_ items: [T]) -> [T] where T: Comparable
}
struct QuickSortStrategy: SortingStrategy {
func sort<T>(_ items: [T]) -> [T] { /* quicksort */ items }
}
struct MergeSortStrategy: SortingStrategy {
func sort<T>(_ items: [T]) -> [T] { /* mergesort */ items }
}
class SortedDataSource<T> {
private var strategy: SortingStrategy
func display(_ items: [T]) { let sorted = strategy.sort(items) }
}
Αυθεντικοποίηση — σε εφαρμογές κινητών, οι στρατηγικές αυθεντικοποίησης αλλάζουν ανάλογα με τον πάροχο. Το AuthStrategy με μεθόδους login(), logout(), getToken() υλοποιείται για EmailPasswordAuth, GoogleAuth, AppleAuth, BiometricAuth. Το πλαίσιο AuthManager λαμβάνει τη στρατηγική μέσω DI ή εργοστασίου. Αυτό επιτρέπει την προσθήκη νέων παρόχων αυθεντικοποίησης χωρίς τροποποίηση της οθόνης σύνδεσης. Το μοτίβο Strategy — η βάση για πολλές βιβλιοθήκες OAuth και το Firebase Authentication.
Συχνές Ερωτήσεις
Το Strategy δικαιολογείται όταν έχετε 3+ αλγορίθμους που μπορεί να αλλάξουν ή να επεκταθούν. Εάν οι αλγόριθμοι είναι 2 και σταθεροί — ένα απλό if-else είναι λιγότερο δαπανηρό. Χρησιμοποιήστε το Strategy όταν οι αλγόριθμοι χρησιμοποιούνται σε διαφορετικά μέρη της εφαρμογής, όταν πρέπει να αλλάζετε αλγορίθμους κατά τον χρόνο εκτέλεσης ή όταν κάθε αλγόριθμος απαιτεί δικές του εξαρτήσεις και δοκιμές.
Όχι, είναι διαφορετικά μοτίβα με παρόμοια δομή. Strategy — ο πελάτης επιλέγει ρητά τον αλγόριθμο και οι στρατηγικές είναι ανεξάρτητες. State — το αντικείμενο αλλάζει μόνο του τη συμπεριφορά του κατά την αλλαγή εσωτερικής κατάστασης και οι καταστάσεις μπορούν να μεταβαίνουν η μία στην άλλη. Στο State το πλαίσιο διαχειρίζεται την αλλαγή κατάστασης, στο Strategy — ο κώδικας πελάτη.
Ναι, σε Swift και Kotlin η στρατηγική μπορεί να μεταβιβαστεί ως closure ή lambda. Swift: typealias PaymentHandler = (Decimal) async throws -> PaymentResult. Kotlin: typealias PaymentFun = suspend (BigDecimal) -> PaymentResult. Αυτό απλοποιεί τον κώδικα για απλές περιπτώσεις, αλλά χάνεται η ονομασία και η τεκμηρίωση. Για 1-2 αλγορίθμους — το closure είναι αρκετό, για 4+ — καλύτερες είναι οι ξεχωριστές κλάσεις.
Κάθε στρατηγική δοκιμάζεται με ξεχωριστή δοκιμή μονάδας με ψεύτικες εξαρτήσεις (mock). Το πλαίσιο δοκιμάζεται με ψεύτικη στρατηγική — ελέγχεται ότι το πλαίσιο καλεί τη μέθοδο της στρατηγικής και μεταβιβάζει τις σωστές παραμέτρους. Σε Swift χρησιμοποιήστε XCTest + πρωτόκολλα για mock, σε Kotlin — MockK ή Mockito. Το κύριο πλεονέκτημα: κάθε στρατηγική δοκιμάζεται απομονωμένα χωρίς πολύπλοκη διαμόρφωση.
Ναι, το Strategy (Στρατηγική) — ένα από τα 23 μοτίβα που περιγράφονται στο βιβλίο «Design Patterns: Elements of Reusable Object-Oriented Software» (Gamma, Helm, Johnson, Vlissides, 1994). Ανήκει στην ομάδα των συμπεριφορικών μοτίβων (Behavioral Patterns). Συνώνυμα: Policy (Πολιτική). Ο αρχικός κώδικας παραδείγματος σε Smalltalk-80 είναι διαθέσιμος στην αρχική έκδοση του GoF.
Συμπεράσματα
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης