Naarmate softwareprojecten groeien, wordt het steeds belangrijker om je code georganiseerd, onderhoudbaar en schaalbaar te houden. Hier komen ontwerppatronen om de hoek kijken. Ontwerppatronen bieden bewezen, herbruikbare oplossingen voor veelvoorkomende software-ontwerpvraagstukken, waardoor je code efficiënter wordt en gemakkelijker te beheren.
In deze gids duiken we dieper in op enkele van de meest populaire ontwerppatronen en laten we zien hoe je ze kunt implementeren in Spring Boot. Tegen het einde zul je niet alleen deze patronen conceptueel begrijpen, maar ze ook met vertrouwen kunnen toepassen in je eigen projecten.
Inhoudsopgave
Introductie tot Ontwerppatronen
Ontwerppatronen zijn herbruikbare oplossingen voor veelvoorkomende software-ontwerpproblemen. Denk aan hen als best practices gedestilleerd in sjablonen die kunnen worden toegepast om specifieke uitdagingen in uw code op te lossen. Ze zijn niet specifiek voor een taal, maar ze kunnen bijzonder krachtig zijn in Java vanwege de objectgeoriënteerde aard ervan.
In deze handleiding behandelen we:
-
Singleton-patroon: Zorgen dat een klasse slechts één instantie heeft.
-
Factory-patroon: Objecten maken zonder de exacte klasse te specificeren.
-
Strategiepatroon: Toestaan dat algoritmen tijdens runtime worden geselecteerd.
-
Observer-patroon: Opzetten van een publiceer-abonneerrelatie.
We behandelen niet alleen hoe deze patronen werken, maar verkennen ook hoe ze kunnen worden toegepast in Spring Boot voor real-world toepassingen.
Hoe u uw Spring Boot-project instelt
Voordat we ingaan op de patronen, laten we een Spring Boot-project opzetten:
Vereisten
Zorg ervoor dat u heeft:
-
Java 11+
-
Maven
-
Spring Boot CLI (optioneel)
-
Postman of curl (voor testen)
Projectinitialisatie
U kunt snel een Spring Boot-project maken met Spring Initializr:
curl https://start.spring.io/starter.zip \
-d dependencies=web \
-d name=DesignPatternsDemo \
-d javaVersion=11 -o design-patterns-demo.zip
unzip design-patterns-demo.zip
cd design-patterns-demo
Wat is het Singleton-patroon?
Het Singleton-patroon zorgt ervoor dat een klasse slechts één instantie heeft en biedt een wereldwijd toegangspunt ernaar. Dit patroon wordt vaak gebruikt voor services zoals logging, configuratiebeheer of databaseverbindingen.
Hoe implementeer je het Singleton-patroon in Spring Boot
Spring Boot-beans zijn standaard singletons, wat betekent dat Spring automatisch het levenscyclus van deze beans beheert om ervoor te zorgen dat slechts één instantie bestaat. Het is echter belangrijk om te begrijpen hoe het Singleton-patroon onder de motorkap werkt, vooral wanneer je geen door Spring beheerde beans gebruikt of meer controle nodig hebt over instantiebeheer.
Laten we een handmatige implementatie van het Singleton-patroon doornemen om te demonstreren hoe je de creatie van een enkele instantie binnen je applicatie kunt beheren.
Stap 1: Maak een LoggerService
Klasse
In dit voorbeeld zullen we een eenvoudige logging-service maken met het Singleton-patroon. Het doel is om ervoor te zorgen dat alle delen van de applicatie dezelfde logging-instantie gebruiken.
public class LoggerService {
// De statische variabele om de enkele instantie vast te houden
private static LoggerService instance;
// Private constructor om instantiatie van buitenaf te voorkomen
private LoggerService() {
// Deze constructor is opzettelijk leeg om te voorkomen dat andere klassen instanties maken
}
// Openbare methode om toegang te bieden tot de enkele instantie
public static synchronized LoggerService getInstance() {
if (instance == null) {
instance = new LoggerService();
}
return instance;
}
// Voorbeeld van een logmethode
public void log(String message) {
System.out.println("[LOG] " + message);
}
}
-
Statische Variabele (
instantie
): Dit houdt de enkele instantie vanLoggerService
vast. -
Private Constructor: De constructor is privé gemarkeerd om te voorkomen dat andere klassen rechtstreeks nieuwe instanties maken.
-
Gesynchroniseerde
getInstance()
Methode: De methode is gesynchroniseerd om deze thread-safe te maken, zodat slechts één instantie wordt gemaakt, zelfs als meerdere threads er tegelijkertijd toegang toe proberen te krijgen. -
Lui initialiseren: Het exemplaar wordt alleen gemaakt wanneer het voor het eerst wordt opgevraagd (
lui initialiseren
), wat efficiënt is qua geheugengebruik.
Praktisch gebruik: Dit patroon wordt vaak gebruikt voor gedeelde resources, zoals logging, configuratie-instellingen of het beheren van databaseverbindingen, waarbij u de toegang wilt controleren en ervoor wilt zorgen dat slechts één exemplaar wordt gebruikt in uw toepassing.
Stap 2: Gebruik de Singleton in een Spring Boot-controller
Latenn we nu zien hoe we onze LoggerService
Singleton kunnen gebruiken binnen een Spring Boot-controller. Deze controller zal een eindpunt blootstellen dat een bericht logt telkens wanneer het wordt benaderd.
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LogController {
@GetMapping("/log")
public ResponseEntity<String> logMessage() {
// Toegang tot het Singleton-exemplaar van LoggerService
LoggerService logger = LoggerService.getInstance();
logger.log("This is a log message!");
return ResponseEntity.ok("Message logged successfully");
}
}
-
GET-eindpunt: We hebben een
/log
eindpunt gemaakt dat, wanneer benaderd, een bericht logt met behulp van deLoggerService
. -
Singleton-gebruik: In plaats van een nieuw exemplaar van
LoggerService
te maken, roepen wegetInstance()
aan om ervoor te zorgen dat we elke keer hetzelfde exemplaar gebruiken. -
Reactie: Na het loggen, retourneert het eindpunt een reactie die succes aangeeft.
Stap 3: Het Singleton Patroon Testen
Nu, laten we dit eindpunt testen met behulp van Postman of je browser:
GET http://localhost:8080/log
Verwachte Uitvoer:
-
Console log:
[LOG] Dit is een logbericht!
-
HTTP Reactie:
Bericht succesvol gelogd
Je kunt het eindpunt meerdere keren aanroepen en je zult zien dat dezelfde instantie van LoggerService
wordt gebruikt, zoals aangegeven door de consistente loguitvoer.
Gebruiksscenario’s voor het Singleton Patroon in de Echte Wereld
Hier is wanneer je het Singleton-patroon zou willen gebruiken in real-world toepassingen:
-
Configuratiebeheer: Zorg ervoor dat jouw applicatie een consistente reeks configuratie-instellingen gebruikt, vooral wanneer die instellingen worden geladen vanuit bestanden of databases.
-
Database Verbinding Pools: Beheer toegang tot een beperkt aantal databaseverbindingen, zorg ervoor dat hetzelfde pool gedeeld wordt over de applicatie.
-
Caching: Behoud een enkele cache-instantie om inconsistente gegevens te vermijden.
-
Logdiensten: Zoals getoond in dit voorbeeld, gebruik een enkele logdienst om loguitvoer te centraliseren over verschillende modules van je applicatie.
Belangrijkste punten
-
Het Singleton-patroon is een eenvoudige manier om ervoor te zorgen dat slechts één instantie van een klasse wordt aangemaakt.
-
Threadveiligheid is cruciaal als meerdere threads toegang hebben tot de Singleton, daarom hebben we
synchronized
gebruikt in ons voorbeeld. -
Spring Boot-beans zijn standaard al singletons, maar het begrijpen van hoe je het handmatig implementeert helpt je meer controle te krijgen wanneer dat nodig is.
Dit omvat de implementatie en het gebruik van het Singleton-patroon. Vervolgens zullen we het Factory-patroon verkennen om te zien hoe het kan helpen bij het stroomlijnen van objectcreatie.
Wat is het Factory-patroon?
Het Factory-patroon stelt je in staat om objecten te maken zonder de exacte klasse te specificeren. Dit patroon is handig wanneer je verschillende soorten objecten hebt die moeten worden geïnstantieerd op basis van bepaalde invoer.
Hoe implementeer je een Factory in Spring Boot
Het Factory-patroon is ongelooflijk nuttig wanneer je objecten moet maken op basis van bepaalde criteria, maar de objectcreatie wilt ontkoppelen van de logica van je hoofdtoepassing.
In deze sectie zullen we een NotificationFactory
bouwen om meldingen te versturen via e-mail of sms. Dit is vooral handig als je verwacht in de toekomst meer meldingstypen toe te voegen, zoals pushmeldingen of in-appmeldingen, zonder je bestaande code te wijzigen.
Stap 1: Maak de Notification
Interface
De eerste stap is het definiëren van een gemeenschappelijke interface die alle typen meldingen zullen implementeren. Dit zorgt ervoor dat elk type melding (e-mail, sms, enzovoort) een consistente send()
-methode heeft.
public interface Notification {
void send(String message);
}
-
Doel: De
Notification
-interface definieert het contract voor het versturen van meldingen. Elke klasse die deze interface implementeert, moet een implementatie bieden voor desend()
-methode. -
Schaalbaarheid: Door een interface te gebruiken, kunt u in de toekomst eenvoudig uw toepassing uitbreiden om andere soorten meldingen op te nemen zonder bestaande code te wijzigen.
Stap 2: Implementeer EmailNotification
en SMSNotification
Latennwe nu twee concrete klassen implementeren, een voor het verzenden van e-mails en een andere voor het verzenden van SMS-berichten.
public class EmailNotification implements Notification {
@Override
public void send(String message) {
System.out.println("Sending Email: " + message);
}
}
public class SMSNotification implements Notification {
@Override
public void send(String message) {
System.out.println("Sending SMS: " + message);
}
}
Stap 3: Maak een NotificationFactory
De NotificationFactory
klasse is verantwoordelijk voor het maken van instanties van Notification
op basis van het opgegeven type. Dit ontwerp zorgt ervoor dat de NotificationController
niet op de hoogte hoeft te zijn van de details van objectcreatie.
public class NotificationFactory {
public static Notification createNotification(String type) {
switch (type.toUpperCase()) {
case "EMAIL":
return new EmailNotification();
case "SMS":
return new SMSNotification();
default:
throw new IllegalArgumentException("Unknown notification type: " + type);
}
}
}
Factory Methode (createNotification()
):
-
De factory-methode neemt een string (
type
) als invoer en retourneert een instantie van de overeenkomstige meldingsklasse. -
Switch Statement: De switch-instructie selecteert het juiste meldingstype op basis van de invoer.
-
Foutafhandeling: Als het opgegeven type niet wordt herkend, wordt er een
IllegalArgumentException
opgegooid. Dit zorgt ervoor dat ongeldige types vroegtijdig worden opgemerkt.
Waarom een Factory Gebruiken?
-
Decoupling: Het factory-patroon ontkoppelt de objectcreatie van de bedrijfslogica. Dit maakt je code modulairder en gemakkelijker te onderhouden.
-
Uitbreidbaarheid: Als je een nieuw notificatietype wilt toevoegen, hoef je alleen de factory bij te werken zonder de controllerlogica te wijzigen.
Stap 4: Gebruik de Factory in een Spring Boot Controller
Nu gaan we alles samenvoegen door een Spring Boot-controller te creëren die de NotificationFactory
gebruikt om notificaties te verzenden op basis van het verzoek van de gebruiker.
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class NotificationController {
@GetMapping("/notify")
public ResponseEntity<String> notify(@RequestParam String type, @RequestParam String message) {
try {
// Maak het juiste Notification-object aan met behulp van de factory
Notification notification = NotificationFactory.createNotification(type);
notification.send(message);
return ResponseEntity.ok("Notification sent successfully!");
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
}
}
GET Endpoint (/notify
):
-
De controller exposeert een
/notify
eindpunt dat twee query parameters accepteert:type
(ofwel “EMAIL” of “SMS”) enmessage
. -
Het gebruikt de
NotificationFactory
om het juiste meldingstype te maken en verzendt het bericht. -
Foutafhandeling: Als er een ongeldig meldingstype wordt opgegeven, vangt de controller de
IllegalArgumentException
op en geeft een400 Bad Request
reactie terug.
Stap 5: Het testen van het Fabriekspatroon
Lat de eindpunt testen met Postman of een browser:
-
Verstuur een E-mailmelding:
GET http://localhost:8080/notify?type=email&message=Hello%20Email
Output:
E-mail verzenden: Hallo Email
-
Stuur een sms-melding:
GET http://localhost:8080/notify?type=sms&message=Hallo%20SMS
Uitvoer:
SMS verzenden: Hallo SMS
-
Testen met een ongeldig type:
GET http://localhost:8080/notify?type=unknown&message=Test
Uitvoer:
Fout verzoek: Onbekend meldingstype: unknown
Praktijkgevallen voor het fabriekspatroon
Het fabriekspatroon is bijzonder nuttig in scenario’s waarin:
-
Dynamische objectcreatie: Wanneer u objecten moet maken op basis van gebruikersinvoer, zoals verschillende soorten meldingen verzenden, rapporten genereren in verschillende formaten of verschillende betaalmethoden verwerken.
-
Afkoppeling van Objectcreatie: Door een fabriek te gebruiken, kunt u uw belangrijkste bedrijfslogica gescheiden houden van objectcreatie, waardoor uw code beter onderhoudbaar wordt.
-
Schaalbaarheid: Breid uw toepassing eenvoudig uit om nieuwe soorten meldingen te ondersteunen zonder bestaande code te wijzigen. Voeg eenvoudig een nieuwe klasse toe die de
Melding
-interface implementeert en werk de fabriek bij.
Wat is het Strategy-patroon?
Het Strategy-patroon is perfect wanneer u dynamisch moet schakelen tussen meerdere algoritmes of gedragingen. Het stelt u in staat om een reeks algoritmes te definiëren, elk binnen afzonderlijke klassen te encapsuleren en ze gemakkelijk uitwisselbaar te maken op runtime. Dit is vooral handig voor het selecteren van een algoritme op basis van specifieke voorwaarden, waardoor uw code schoon, modulair en flexibel blijft.
Gebruik in de echte wereld: Stel je een e-commerce systeem voor dat meerdere betaalopties moet ondersteunen, zoals creditcards, PayPal of bankoverschrijvingen. Door het Strategiepatroon te gebruiken, kun je eenvoudig betaalmethoden toevoegen of aanpassen zonder de bestaande code te wijzigen. Deze aanpak zorgt ervoor dat je toepassing schaalbaar en onderhoudbaar blijft wanneer je nieuwe functies introduceert of bestaande bijwerkt.
We zullen dit patroon demonstreren met een voorbeeld van Spring Boot dat betalingen afhandelt met behulp van een creditcard- of PayPal-strategie.
Stap 1: Definieer een PaymentStrategy
Interface
We beginnen met het maken van een gemeenschappelijke interface die alle betaalstrategieën zullen implementeren:
public interface PaymentStrategy {
void pay(double amount);
}
De interface definieert een contract voor alle betaalmethoden, waardoor consistentie wordt gegarandeerd bij implementaties.
Stap 2: Implementeer Betaalstrategieën
Maak concrete klassen voor creditcard- en PayPal-betalingen.
public class CreditCardPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " with Credit Card");
}
}
public class PayPalPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " via PayPal");
}
}
Elke klasse implementeert de betaling()
methode met zijn specifieke gedrag.
Stap 3: Gebruik de Strategie in een Controller
Maak een controller om dynamisch een betaalstrategie te selecteren op basis van gebruikersinvoer:
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PaymentController {
@GetMapping("/pay")
public ResponseEntity<String> processPayment(@RequestParam String method, @RequestParam double amount) {
PaymentStrategy strategy = selectPaymentStrategy(method);
if (strategy == null) {
return ResponseEntity.badRequest().body("Invalid payment method");
}
strategy.pay(amount);
return ResponseEntity.ok("Payment processed successfully!");
}
private PaymentStrategy selectPaymentStrategy(String method) {
switch (method.toUpperCase()) {
case "CREDIT": return new CreditCardPayment();
case "PAYPAL": return new PayPalPayment();
default: return null;
}
}
}
De eindpunt accepteert methode
en bedrag
als queryparameters en verwerkt de betaling met de juiste strategie.
Stap 4: Testen van het Eindpunt
-
Kredietkaartbetaling:
GET http://localhost:8080/betalen?methode=credit&bedrag=100
Uitvoer:
$100,0 betaald met Creditcard
-
PayPal-betaling:
GET http://localhost:8080/betalen?methode=paypal&bedrag=50
Uitvoer:
$50,0 betaald via PayPal
-
Ongeldige methode:
GET http://localhost:8080/betalen?methode=bitcoin&bedrag=25
Uitvoer:
Ongeldige betaalmethode
Gebruiksscenario’s voor het Strategy-patroon
-
Betaling verwerken: Schakel dynamisch tussen verschillende betalingsgateways.
-
Sorteeralgoritmes: Kies de beste sorteermethode op basis van gegevensgrootte.
-
Bestanden exporteren: Exporteer rapporten in diverse formaten (PDF, Excel, CSV).
Belangrijkste punten
-
Het Strategiepatroon houdt uw code modulair en volgt het Open/Closed-principe.
-
Het toevoegen van nieuwe strategieën is eenvoudig—maak gewoon een nieuwe klasse die de
PaymentStrategy
-interface implementeert. -
Het is ideaal voor situaties waarin u flexibele algoritmekeuze nodig hebt op runtime.
Hierna zullen we het Observerpatroon verkennen, perfect voor het afhandelen van op gebeurtenissen gebaseerde architecturen.
Wat is het Observerpatroon?
Het Observer-patroon is ideaal wanneer je één object hebt (het onderwerp) dat meerdere andere objecten (waarnemers) moet informeren over veranderingen in zijn toestand. Het is perfect voor op gebeurtenissen gebaseerde systemen waar updates naar verschillende componenten moeten worden gestuurd zonder dat er een sterke koppeling tussen hen ontstaat. Dit patroon stelt je in staat om een schone architectuur te behouden, vooral wanneer verschillende delen van je systeem onafhankelijk op veranderingen moeten reageren.
Gebruik in de echte wereld: Dit patroon wordt veel gebruikt in systemen die meldingen of waarschuwingen versturen, zoals chattoepassingen of aandelenkoersvolgers, waar updates in realtime naar gebruikers moeten worden gestuurd. Door het Observer-patroon te gebruiken, kun je eenvoudig meldingstypen toevoegen of verwijderen zonder de kernlogica te veranderen.
We zullen demonstreren hoe dit patroon te implementeren in Spring Boot door een eenvoudig meldingssysteem te bouwen waarbij zowel e-mail- als sms-meldingen worden verzonden wanneer een gebruiker zich registreert.
Stap 1: Maak een Observer
-interface
We beginnen met het definiëren van een gemeenschappelijke interface die alle waarnemers zullen implementeren:
public interface Observer {
void update(String event);
}
De interface legt een contract vast waarin alle waarnemers de update()
-methode moeten implementeren, die wordt geactiveerd telkens wanneer het onderwerp verandert.
Stap 2: Implementeer EmailObserver
en SMSObserver
Vervolgens creëren we twee concrete implementaties van de Observer
-interface om e-mail- en sms-meldingen te verwerken.
EmailObserver
-klasse
public class EmailObserver implements Observer {
@Override
public void update(String event) {
System.out.println("Email sent for event: " + event);
}
}
De EmailObserver
handelt het verzenden van e-mailmeldingen af telkens wanneer het op de hoogte wordt gebracht van een gebeurtenis.
Klasse SMSObserver
public class SMSObserver implements Observer {
@Override
public void update(String event) {
System.out.println("SMS sent for event: " + event);
}
}
De SMSObserver
behandelt het verzenden van SMS-meldingen telkens wanneer het wordt geïnformeerd.
Stap 3: Maak een klasse UserService
(Het Onderwerp)
We zullen nu een klasse UserService
maken die fungeert als het onderwerp en zijn geregistreerde waarnemers op de hoogte stelt telkens wanneer een gebruiker zich registreert.
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class UserService {
private List<Observer> observers = new ArrayList<>();
// Methode om waarnemers te registreren
public void registerObserver(Observer observer) {
observers.add(observer);
}
// Methode om alle geregistreerde waarnemers van een gebeurtenis op de hoogte te stellen
public void notifyObservers(String event) {
for (Observer observer : observers) {
observer.update(event);
}
}
// Methode om een nieuwe gebruiker te registreren en waarnemers op de hoogte te stellen
public void registerUser(String username) {
System.out.println("User registered: " + username);
notifyObservers("User Registration");
}
}
-
Waarnemerslijst: Houdt bij alle geregistreerde waarnemers bij.
-
registerObserver()
Methode: Voegt nieuwe waarnemers toe aan de lijst. -
notifyObservers()
Methode: Stelt alle geregistreerde waarnemers op de hoogte wanneer een gebeurtenis plaatsvindt. -
registerUser()
Methode: Registreert een nieuwe gebruiker en activeert meldingen naar alle waarnemers.
Stap 4: Gebruik het Observer-patroon in een Controller
Tenslotte zullen we een Spring Boot-controller maken om een eindpunt bloot te leggen voor gebruikersregistratie. Deze controller zal zowel de EmailObserver
als de SMSObserver
registreren bij de UserService
.
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class UserController {
private final UserService userService;
public UserController() {
this.userService = new UserService();
// Observers registreren
userService.registerObserver(new EmailObserver());
userService.registerObserver(new SMSObserver());
}
@PostMapping("/register")
public ResponseEntity<String> registerUser(@RequestParam String username) {
userService.registerUser(username);
return ResponseEntity.ok("User registered and notifications sent!");
}
}
-
Eindpunt (
/register
): Accepteert eengebruikersnaam
-parameter en registreert de gebruiker, waarbij meldingen naar alle waarnemers worden gestuurd. -
Waarnemers: Zowel
EmailObserver
alsSMSObserver
zijn geregistreerd bijUserService
, zodat ze worden geïnformeerd wanneer een gebruiker zich registreert.
Testen van het Observer-patroon
Nu gaan we onze implementatie testen met behulp van Postman of een browser:
POST http://localhost:8080/api/register?username=JohnDoe
Verwachte uitvoer in Console:
User registered: JohnDoe
Email sent for event: User Registration
SMS sent for event: User Registration
Het systeem registreert de gebruiker en informeert zowel de Email- als SMS-waarnemers, waarbij de flexibiliteit van het Observer-patroon wordt gedemonstreerd.
Praktische toepassingen van het Observer-patroon
-
Meldingssystemen: Updates naar gebruikers sturen via verschillende kanalen (e-mail, SMS, pushmeldingen) wanneer bepaalde gebeurtenissen plaatsvinden.
-
Gebeurtenisgestuurde architecturen: Het op de hoogte brengen van meerdere subsystemen wanneer specifieke acties plaatsvinden, zoals gebruikersactiviteiten of systeemwaarschuwingen.
-
Gegevensstreaming: Het uitzenden van gegevenswijzigingen naar diverse gebruikers in realtime (bijvoorbeeld live aandelenkoersen of sociale mediafeeds).
Hoe Spring Boot’s Dependency Injection te gebruiken
Tot nu toe hebben we handmatig objecten gemaakt om ontwerppatronen te demonstreren. In echte Spring Boot-toepassingen is Dependency Injection (DI) echter de voorkeursmethode om objectcreatie te beheren. DI stelt Spring in staat om automatisch de instantiëring en bedrading van uw klassen te beheren, waardoor uw code meer modulair, testbaar en onderhoudbaar wordt.
Laten we ons Strategy-patroonvoorbeeld herschrijven om te profiteren van de krachtige DI-mogelijkheden van Spring Boot. Hiermee kunnen we dynamisch schakelen tussen betalingsstrategieën, met behulp van Spring-annotaties om afhankelijkheden te beheren.
Bijgewerkt Strategy-patroon met gebruik van Spring Boot’s DI
In ons gerefactoreerde voorbeeld zullen we gebruikmaken van Spring’s annotaties zoals @Component
, @Service
en @Autowired
om het proces van het injecteren van afhankelijkheden te stroomlijnen.
Stap 1: Markeer Betaalstrategieën met @Component
Allereerst zullen we onze strategie-implementaties markeren met de @Component
-annotatie zodat Spring ze automatisch kan detecteren en beheren.
@Component("creditCardPayment")
public class CreditCardPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " with Credit Card");
}
}
@Component("payPalPayment")
public class PayPalPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " using PayPal");
}
}
-
@Component
-annotatie: Door@Component
toe te voegen, vertellen we Spring om deze klassen te behandelen als door Spring beheerde beans. De stringwaarde ("creditCardPayment"
en"payPalPayment"
) fungeert als de bean-identificatie. -
Flexibiliteit: Met deze opstelling kunnen we schakelen tussen strategieën door de juiste bean-identificatie te gebruiken.
Stap 2: Refactor de PaymentService
om Afhankelijkheidsinjectie te Gebruiken
Vervolgens zullen we de PaymentService
aanpassen om een specifieke betaalstrategie in te voegen met behulp van @Autowired
en @Qualifier
.
@Service
public class PaymentService {
private final PaymentStrategy paymentStrategy;
@Autowired
public PaymentService(@Qualifier("payPalPayment") PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void processPayment(double amount) {
paymentStrategy.pay(amount);
}
}
-
@Service
-annotatie: MarkeertPaymentService
als een door Spring beheerde service bean. -
@Autowired
: Spring injecteert automatisch de vereiste afhankelijkheid. -
@Qualifier
: Specificeert welke implementatie vanPaymentStrategy
moet worden geïnjecteerd. In dit voorbeeld gebruiken we"payPalPayment"
. -
Gemak van configuratie: Door eenvoudigweg de waarde van
@Qualifier
te wijzigen, kunt u de betaalstrategie wijzigen zonder enige bedrijfslogica te veranderen.
Stap 3: Het geherstructureerde Service gebruiken in een Controller
Om de voordelen van deze herstructurering te zien, laten we de controller bijwerken om onze PaymentService
te gebruiken:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class PaymentController {
private final PaymentService paymentService;
@Autowired
public PaymentController(PaymentService paymentService) {
this.paymentService = paymentService;
}
@GetMapping("/pay")
public String makePayment(@RequestParam double amount) {
paymentService.processPayment(amount);
return "Payment processed using the current strategy!";
}
}
-
@Autowired
: De controller ontvangt automatisch dePaymentService
met de geïnjecteerde betaalstrategie. -
GET-eindpunt (
/betalen
): Wanneer toegankelijk, verwerkt het een betaling met behulp van de momenteel geconfigureerde strategie (PayPal in dit voorbeeld).
Het testen van het opnieuw ontworpen strategiepatroon met DI
Nu gaan we de nieuwe implementatie testen met behulp van Postman of een browser:
GET http://localhost:8080/api/pay?amount=100
Verwachte uitvoer:
Paid $100.0 using PayPal
Als je de kwalificatie in PaymentService
wijzigt naar "creditCardPayment"
, zal de uitvoer dienovereenkomstig veranderen:
Paid $100.0 with Credit Card
Voordelen van het gebruik van Dependency Injection
-
Losse koppeling: De service en de controller hoeven niet te weten hoe een betaling wordt verwerkt. Ze vertrouwen eenvoudigweg op Spring om de juiste implementatie in te voegen.
-
Modulariteit: U kunt eenvoudig nieuwe betaalmethoden toevoegen (bijvoorbeeld
BankTransferPayment
,CryptoPayment
) door nieuwe klassen aan te maken die zijn geannoteerd met@Component
en de@Qualifier
aan te passen. - Configureerbaarheid: Door gebruik te maken van Spring-profielen, kunt u strategieën wijzigen op basis van de omgeving (bijvoorbeeld ontwikkeling vs. productie).
Voorbeeld: U kunt @Profiel
gebruiken om automatisch verschillende strategieën in te voegen op basis van het actieve profiel:
@Component
@Profile("dev")
public class DevPaymentStrategy implements PaymentStrategy { /* ... */ }
@Component
@Profile("prod")
public class ProdPaymentStrategy implements PaymentStrategy { /* ... */ }
Belangrijkste punten
-
Door het gebruik van Spring Boot’s DI kunt u objectcreatie vereenvoudigen en de flexibiliteit van uw code verbeteren.
-
Het Strategiepatroon gecombineerd met DI stelt u in staat om eenvoudig te schakelen tussen verschillende strategieën zonder uw kernbedrijfslogica te wijzigen.
-
Het gebruik van
@Kwalificeer
en Spring Profiles geeft u de flexibiliteit om uw toepassing te configureren op basis van verschillende omgevingen of vereisten.
Deze aanpak maakt niet alleen uw code schoner, maar bereidt deze ook voor op meer geavanceerde configuraties en schaalvergroting in de toekomst. In de volgende sectie zullen we Best Practices en Optimalisatietips verkennen om uw Spring Boot-toepassingen naar een hoger niveau te tillen.
Best Practices en Optimalisatietips
Algemene Beste Praktijken
-
Overschrijd patronen niet: Gebruik ze alleen wanneer nodig. Overengineering kan je code moeilijker te onderhouden maken.
-
Geef de voorkeur aan compositie boven overerving: Patronen zoals Strategie en Observer zijn goede voorbeelden van dit principe.
-
Houd je patronen flexibel: Maak gebruik van interfaces om je code losgekoppeld te houden.
Overwegingen voor Prestaties
-
Singleton Patroon: Zorg voor thread-veiligheid door
synchronized
te gebruiken of hetBill Pugh Singleton Design
. -
Factory Patroon: Caché objecten als ze duur zijn om te maken.
-
Observer Patroon: Gebruik asynchrone verwerking als je veel waarnemers hebt om blokkering te voorkomen.
Geavanceerde Onderwerpen
-
Gebruik Reflectie met het Factory-patroon voor dynamisch laden van klassen.
-
Benut Spring Profielen om strategieën te wisselen op basis van de omgeving.
-
Voeg Swagger Documentatie toe voor je API-eindpunten.
Conclusie en Belangrijkste Leerpunten
In deze tutorial hebben we enkele van de krachtigste ontwerppatronen verkend—Singleton, Factory, Strategy en Observer—en laten zien hoe je ze kunt implementeren in Spring Boot. Laten we elk patroon kort samenvatten en benadrukken waarvoor het het beste geschikt is:
Singleton Patroon:
-
Samenvatting: Zorgt ervoor dat een klasse slechts één instantie heeft en biedt een wereldwijd toegangspunt daarvoor.
-
Het Beste Voor: Beheren van gedeelde bronnen zoals configuratie-instellingen, databaseverbindingen of logservices. Het is ideaal wanneer je de toegang tot een gedeelde instantie in je hele applicatie wilt beheersen.
Factory Pattern:
-
Samenvatting: Biedt een manier om objecten te maken zonder de exacte klasse te specificeren die moet worden geïnstantieerd. Dit patroon ontkoppelt objectcreatie van de bedrijfslogica.
-
Beste Voor: Scenario’s waarin u verschillende soorten objecten moet maken op basis van invoercondities, zoals het verzenden van meldingen via e-mail, sms of pushmeldingen. Het is geweldig om uw code modulair en uitbreidbaar te maken.
Strategiepatroon:
-
Samenvatting: Hiermee kunt u een reeks algoritmen definiëren, elk daarvan encapsuleren en ze uitwisselbaar maken. Dit patroon helpt u een algoritme op runtime te kiezen.
-
Beste Voor: Wanneer u dynamisch wilt schakelen tussen verschillende gedragingen of algoritmen, zoals het verwerken van verschillende betalingsmethoden in een e-commerce toepassing. Het houdt uw code flexibel en voldoet aan het Open/Closed-Principe.
Observer Patroon:
-
Samenvatting: Definieert een één-op-veel afhankelijkheid tussen objecten zodat wanneer één object van staat verandert, al zijn afhankelijken automatisch op de hoogte worden gesteld.
-
Beste voor: Gebeurtenisgestuurde systemen zoals meldingsdiensten, real-time updates in chat-apps, of systemen die moeten reageren op veranderingen in gegevens. Het is ideaal voor het ontkoppelen van componenten en het maken van je systeem schaalbaarder.
Wat nu?
Nu je deze essentiële ontwerppatronen hebt geleerd, probeer ze te integreren in je bestaande projecten om te zien hoe ze de structuur en schaalbaarheid van je code kunnen verbeteren. Hier zijn een paar suggesties voor verder onderzoek:
-
Experimenteer: Probeer andere ontwerppatronen te implementeren zoals Decorator, Proxy en Builder om je toolkit uit te breiden.
-
Praktijk: Gebruik deze patronen om bestaande projecten te refactoren en hun onderhoudbaarheid te verbeteren.
-
Deel: Als je vragen hebt of je ervaring wilt delen, voel je vrij om contact op te nemen!
Ik hoop dat deze gids je heeft geholpen te begrijpen hoe je effectief ontwerppatronen kunt gebruiken in Java. Blijf experimenteren en veel codeplezier!
Source:
https://www.freecodecamp.org/news/how-to-use-design-patterns-in-java-with-spring-boot/