Spring වල Decorator Pattern – Services Enhance කරමු! SC Code Guide

Decorator Pattern එකෙන් Spring Services Enhance කරමු: A SC Guide
කොහොමද යාළුවනේ? අද අපි කතා කරන්න හදන්නේ Software Engineering ලෝකයේ ගොඩක් වැදගත් වෙන Design Pattern එකක් ගැන. ඒ තමයි Decorator Pattern එක. විශේෂයෙන්ම Spring Framework එකත් එක්ක වැඩ කරන අයට මේකෙන් ගන්න පුළුවන් ප්රයෝජන ගොඩක් තියෙනවා. අපි මේක පියවරෙන් පියවර, සරලව තේරුම් ගමු, practical example එකක් එක්කම!
සාමාන්යයෙන් අපේ applications වලට අලුත් functionality එකක් add කරන්න ඕන වුණාම, අපි හිතන්නේ existing code එක modify කරන්න, එහෙම නැත්නම් ඒ class එක extend කරන්න. හැබැයි, හැම වෙලේම ඒක හොඳ විසඳුමක් නෙවෙයි. සමහර වෙලාවට ඒකෙන් code base එක අවුල් වෙන්න පුළුවන්, maintain කරන්න අමාරු වෙන්න පුළුවන්. Decorator Pattern එක එන්නේ මෙන්න මේ වගේ වෙලාවට උදව්වට.
හිතන්න ඔයාට coffee shop application එකක් හදනවා කියලා. සාමාන්ය coffee එකක් තියෙනවා, ඒකට extra ingredients (milk, sugar, chocolate syrup) add කරන්න පුළුවන්. හැම extra ingredient එකකටම අලුත් class එකක් හදන්න ගියොත් වැඩේ ගොඩක් complicate වෙනවා නේද? Decorator Pattern එකෙන් මේ වගේ තත්ත්වයක් ලස්සනට handle කරන්න පුළුවන්. අපි බලමු ඒක Spring Services වලට කොහොම ගලපගන්නද කියලා.
Decorator Pattern මොකක්ද මේ? (සරලව තේරුම් ගනිමු)
Decorator Pattern එක කියන්නේ Structural Design Pattern එකක්. ඒකේ ප්රධාන අරමුණ තමයි, object එකක behaviour එක run-time එකේදී dynamically improve කරන එක. ඒ කියන්නේ, අපිට ඕන වෙලාවට අලුත් functionality එකක් existing object එකකට එකතු කරන්න පුළුවන්, original class එකට කිසිම හානියක් නොකර.
මේක තවත් සරලව කිව්වොත්, ඔයාට තියෙනවා core functionality එකක් (Base Service එකක්). ඊට පස්සේ ඔයාට ඕන මේ functionality එකට අමතර දේවල් (logging, caching, security checks වගේ) එකතු කරන්න. Decorator Pattern එකේදී අපි කරන්නේ, මේ අමතර දේවල් වෙන වෙනම classes විදියට හදන එක. මේ classes හැම එකක්ම original service එකේ interface එක implement කරනවා, ඒ වගේම original service එකේ instance එකක් reference කරගන්නවා. ඒක හරියට තියෙන වස්තුවකට 'wrappers' දානවා වගේ දෙයක්. හරියට ඔයාගේ තියෙන phone එකට case එකක්, screen protector එකක්, camera lens protector එකක් වගේ දේවල් add කරනවා වගේ. Phone එකේ core functionality එකට හානියක් වෙන්නේ නෑ, හැබැයි extra features සහ ආරක්ෂාව එකතු වෙනවා.
මේකෙන් වෙන වාසිය තමයි, අපිට ඕන නම් එක service එකකට decorators කිහිපයක් එකතු කරන්න පුළුවන්. උදාහරණයක් විදියට, payment processing service එකකට මුලින්ම logging decorator එකක් දාලා, ඊට පස්සේ caching decorator එකක් දාලා, අන්තිමට security decorator එකක් දාන්න පුළුවන්. මේ හැම එකක්ම independent විදියට වැඩ කරන නිසා, code එක maintain කරන්නත් පහසුයි, extend කරන්නත් පහසුයි. වැඩේ ගොඩ නේද?
Decorator Pattern එකේ ප්රධාන Components:
- Component Interface: මේක තමයි අපේ base service එකේ functionality එක define කරන interface එක. Decorator එකත් මේ interface එකම implement කරන්න ඕනේ.
- Concrete Component: මේක තමයි base functionality එක implement කරන original class එක.
- Decorator: මේක abstract class එකක් හෝ interface එකක් වෙන්න පුළුවන්. මේකත් Component Interface එක implement කරනවා, ඒ වගේම Component instance එකක් reference කරගන්නවා.
- Concrete Decorators: මේවා තමයි අපිට add කරන්න ඕන අමතර functionalities implement කරන actual decorator classes.
Spring වලට Decorator Pattern ඇයි? (Advantages)
Spring Framework එක dependency injection (DI) සහ inversion of control (IoC) වගේ concept වලට ගොඩක් අවධානය දෙනවා. Decorator Pattern එකත් මේ concept වලට හොඳට ගැලපෙනවා. Spring application එකකදී Decorator Pattern එක පාවිච්චි කරන එකෙන් අපිට ගොඩක් වාසි ගන්න පුළුවන්:
- Open/Closed Principle: මේක Object-Oriented Design (OOD) වල වැදගත්ම principle එකක්. ඒ කියන්නේ, "software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification". Decorator Pattern එකෙන් මේක ලස්සනට implement කරන්න පුළුවන්. අපිට අලුත් functionality එකක් add කරන්න ඕන වුණාම, existing classes වෙනස් කරන්න ඕනේ නෑ. අලුත් Decorator එකක් ලිව්වාම ඇති.
- Single Responsibility Principle: Decorator Pattern එකේදී හැම decorator එකකටම තියෙන්නේ එකම responsibility එකක්. උදාහරණයක් විදියට, Logging Decorator එකක වැඩේ Logging විතරයි. Caching Decorator එකක වැඩේ Caching විතරයි. මේකෙන් code එක clean වෙනවා වගේම, maintain කරන්නත් පහසුයි.
- Flexibility: Run-time එකේදී අපිට ඕන විදියට services වලට functionalities add කරන්න, remove කරන්න පුළුවන්. compile-time එකේදී මේවා hardcode කරන්න ඕනේ නෑ.
- Avoiding Subclassing Explosion: සාමාන්යයෙන් අපිට functionality එකක් add කරන්න ඕන වුණාම අපි inheritance (subclassing) පාවිච්චි කරනවා. හැබැයි, එක service එකකට functionalities ගොඩක් add කරන්න ඕන වුණොත්, අපිට classes ගොඩක් හදන්න වෙනවා, ඒක maintain කරන්න අමාරු වෙනවා (e.g.,
LoggedCachedSecuredPaymentService
). Decorator Pattern එකෙන් මේ ගැටලුව මඟහරවා ගන්න පුළුවන්. - Modularity: හැම Decorator එකක්ම separate module එකක් විදියට පවතින නිසා, code reusability වැඩි වෙනවා.
මේ වාසි නිසා Spring application වල Decorator Pattern එක ගොඩක් වැදගත් වෙනවා. අපි දැන් බලමු practical example එකක් එක්ක මේක කොහොමද implement කරන්නේ කියලා.
Spring වල Decorator Pattern Implement කරමු: Payment Gateway Example
අපි හිතමු අපිට PaymentGateway
service එකක් තියෙනවා කියලා. මේකෙන් payment process කරනවා. අපිට ඕන මේ payment process එකට logging, caching, සහ security check වගේ functionalities add කරන්න. අපි මේක Decorator Pattern එක පාවිච්චි කරලා කොහොමද කරන්නේ කියලා බලමු. මේ exercise එකෙන් ඔයාට මේ pattern එක හොඳට තේරෙයි.
1. Component Interface එක හදමු:
මුලින්ම, අපේ PaymentGateway
service එකේ common operations define කරන interface එක හදමු.
package com.sc.guide.decorator.service;
public interface PaymentGateway {
String processPayment(double amount, String currency);
}
2. Concrete Component එක හදමු:
දැන්, අපේ base PaymentGateway
functionality එක implement කරන class එක හදමු. මේකෙන් තමයි actual payment process එක සිද්ධ වෙන්නේ. Spring bean එකක් විදියට register කරන්න අපි @Service
annotation එක පාවිච්චි කරනවා. @Service("basicPaymentGateway")
කියලා අපි මේ bean එකට නමක් දුන්නා, ඒක @Qualifier
එකෙන් refer කරන්න පහසු වෙන්න.
package com.sc.guide.decorator.service.impl;
import com.sc.guide.decorator.service.PaymentGateway;
import org.springframework.stereotype.Service;
@Service("basicPaymentGateway")
public class BasicPaymentGateway implements PaymentGateway {
@Override
public String processPayment(double amount, String currency) {
System.out.println("Processing " + amount + " " + currency + " using BasicPaymentGateway.");
// Simulate actual payment processing logic
if (amount > 0) {
return "Payment successful for " + amount + " " + currency;
} else {
return "Payment failed: Invalid amount";
}
}
}
3. Abstract Decorator Class එක හදමු:
දැන් අපි Decorator එක සඳහා abstract class එකක් හදමු. මේකත් PaymentGateway
interface එක implement කරනවා, ඒ වගේම PaymentGateway
instance එකක් reference කරගන්නවා. මේක තමයි හැම decorator එකකටම පොදු structural foundation එක.
package com.sc.guide.decorator.decorator;
import com.sc.guide.decorator.service.PaymentGateway;
public abstract class PaymentGatewayDecorator implements PaymentGateway {
protected PaymentGateway decoratedGateway;
public PaymentGatewayDecorator(PaymentGateway decoratedGateway) {
this.decoratedGateway = decoratedGateway;
}
@Override
public String processPayment(double amount, String currency) {
return decoratedGateway.processPayment(amount, currency); // Delegate to the wrapped object
}
}
4. Concrete Decorators හදමු:
දැන් අපි අපිට ඕන අමතර functionalities (logging, caching, security) සඳහා concrete decorator classes හදමු. මේ හැම decorator එකක්ම PaymentGatewayDecorator
එක extend කරනවා. Spring IOC Container එකට bean එකක් විදියට හඳුන්වා දෙන්න @Component
භාවිතා කරනවා. constructor එකට එන PaymentGateway
එක @Qualifier
කරලා අපිට ඕන specific bean එක inject කරගන්න පුළුවන්.
a) Logging Decorator
මේ decorator එකෙන් payment process එකට කලින් සහ පස්සේ console එකේ messages print කරනවා.
package com.sc.guide.decorator.decorator;
import com.sc.guide.decorator.service.PaymentGateway;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component("loggingPaymentGateway")
public class LoggingPaymentGatewayDecorator extends PaymentGatewayDecorator {
// 'basicPaymentGateway' කියන bean එක මේකට inject වෙනවා.
public LoggingPaymentGatewayDecorator(@Qualifier("basicPaymentGateway") PaymentGateway decoratedGateway) {
super(decoratedGateway);
}
@Override
public String processPayment(double amount, String currency) {
System.out.println("LOGGING: Initiating payment process for " + amount + " " + currency);
String result = super.processPayment(amount, currency); // Call the wrapped object's method
System.out.println("LOGGING: Payment process completed with result: " + result);
return result;
}
}
b) Caching Decorator
මේ decorator එකෙන් payment results cache කරනවා. එකම amount සහ currency එකකට දෙපාරක් payment process කළොත්, දෙවෙනි පාරට actual service එක call කරන්නේ නැතුව cache එකෙන් result එක දෙනවා. අපි මෙතනදී HashMap
එකක් simple cache එකක් විදියට පාවිච්චි කරනවා. මෙතනදී අපි @Qualifier("loggingPaymentGateway")
පාවිච්චි කරලා Logging Decorator එක මේකට chain කරනවා.
package com.sc.guide.decorator.decorator;
import com.sc.guide.decorator.service.PaymentGateway;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component("cachingPaymentGateway")
public class CachingPaymentGatewayDecorator extends PaymentGatewayDecorator {
private static final Map<String, String> cache = new HashMap<>(); // Simple in-memory cache
// 'loggingPaymentGateway' කියන bean එක මේකට inject වෙනවා, ඒ නිසා logging functionality එකත් එක්කම cache වෙනවා.
public CachingPaymentGatewayDecorator(@Qualifier("loggingPaymentGateway") PaymentGateway decoratedGateway) {
super(decoratedGateway);
}
@Override
public String processPayment(double amount, String currency) {
String cacheKey = amount + "_" + currency;
if (cache.containsKey(cacheKey)) {
System.out.println("CACHING: Retrieving from cache for " + cacheKey);
return cache.get(cacheKey);
} else {
System.out.println("CACHING: Not found in cache. Processing...");
String result = super.processPayment(amount, currency); // Call the wrapped object (which is Logging Decorator)
cache.put(cacheKey, result);
return result;
}
}
}
c) Security Decorator
මේ decorator එකෙන් payment එක process කරන්න කලින් security check එකක් කරනවා. අපි මෙතනදී dummy check එකක් පාවිච්චි කරනවා. මෙතනදී අපි @Qualifier("cachingPaymentGateway")
පාවිච්චි කරලා Caching Decorator එක මේකට chain කරනවා.
package com.sc.guide.decorator.decorator;
import com.sc.guide.decorator.service.PaymentGateway;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component("securityPaymentGateway")
public class SecurityPaymentGatewayDecorator extends PaymentGatewayDecorator {
// 'cachingPaymentGateway' කියන bean එක මේකට inject වෙනවා.
public SecurityPaymentGatewayDecorator(@Qualifier("cachingPaymentGateway") PaymentGateway decoratedGateway) {
super(decoratedGateway);
}
@Override
public String processPayment(double amount, String currency) {
if (!isSecure(amount, currency)) {
System.out.println("SECURITY: Security check failed for payment of " + amount + " " + currency);
return "Payment failed: Security check failed";
}
System.out.println("SECURITY: Security check passed for payment of " + amount + " " + currency);
return super.processPayment(amount, currency); // Call the wrapped object (which is Caching Decorator)
}
private boolean isSecure(double amount, String currency) {
// Simulate a security check, e.g., check for suspicious amounts or currencies
// Dummy check: amount must be less than or equal to 1,000,000 and currency not "XYZ"
return amount <= 1000000 && !currency.equalsIgnoreCase("XYZ");
}
}
5. Spring Configuration සහ Usage:
Spring Boot Application එකකදී මේ decorators ටික Beans විදියට configure කරලා Inject කරගන්න පුළුවන්. Spring framework එකේ dependency injection mechanisms පාවිච්චි කරලා අපිට මේ decorators chain කරන්න පුළුවන්. උඩ code එකේ @Component
annotation එකයි @Qualifier
annotation එකයි පාවිච්චි කරලා අපි Spring IOC Container එකට මේවා beans විදියට add කළා.
අපි CachingPaymentGatewayDecorator
එකට @Qualifier("loggingPaymentGateway")
කියල දුන්නේ LoggingPaymentGatewayDecorator
එක තමයි මේකට wrap වෙන්න ඕනේ කියලා. ඒ වගේම SecurityPaymentGatewayDecorator
එකට @Qualifier("cachingPaymentGateway")
දුන්නේ CachingPaymentGatewayDecorator
එක wrap වෙන්න කියලා. මේ විදියට අපිට කැමති විදියට decorators chain කරන්න පුළුවන්. මේකෙන් code එකට ලස්සන modularity එකක් ලැබෙනවා.
අපි දැන් Controller එකක් හදමු මේ service එක use කරන විදිය බලන්න.
package com.sc.guide.decorator.controller;
import com.sc.guide.decorator.service.PaymentGateway;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/payments")
public class PaymentController {
private final PaymentGateway paymentGateway;
// Inject the final decorator in the chain (SecurityPaymentGatewayDecorator)
// Spring automatically resolves the chain of dependencies through the @Qualifier annotations
public PaymentController(@Qualifier("securityPaymentGateway") PaymentGateway paymentGateway) {
this.paymentGateway = paymentGateway;
}
@GetMapping("/process")
public String processPayment(@RequestParam double amount, @RequestParam String currency) {
return paymentGateway.processPayment(amount, currency);
}
}
දැන් මේ application එක run කරලා http://localhost:8080/payments/process?amount=500¤cy=USD
වගේ URL එකකට request එකක් යවලා බලන්න. ඔයාට console එකේ Logging, Caching, Security decorator layers ඔක්කොම ක්රියාත්මක වෙනවා දකින්න පුළුවන්. දෙවෙනි පාරටත් එකම request එක යැව්වොත් Caching decorator එකෙන් cache එකෙන් data අරන් දෙනවා. Security check fail වෙන්න ඕන නම් amount=2000000
වගේ වැඩි මුදලක් දීලා බලන්න.
මේ වගේම, අපිට ඕන නම් Spring configuration class එකක් හරහාත් මේවා define කරන්න පුළුවන්. මේක වඩාත් clarity දෙනවා. මෙතනදී අපි @Primary
annotation එක පාවිච්චි කරන්නේ PaymentGateway
type එකක් request කරද්දී default විදියට inject වෙන්න ඕන bean එක specify කරන්න.
package com.sc.guide.decorator.config;
import com.sc.guide.decorator.decorator.CachingPaymentGatewayDecorator;
import com.sc.guide.decorator.decorator.LoggingPaymentGatewayDecorator;
import com.sc.guide.decorator.decorator.SecurityPaymentGatewayDecorator;
import com.sc.guide.decorator.service.PaymentGateway;
import com.sc.guide.decorator.service.impl.BasicPaymentGateway;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration
public class PaymentGatewayConfig {
@Bean
public PaymentGateway basicPaymentGateway() {
return new BasicPaymentGateway();
}
@Bean
public PaymentGateway loggingPaymentGateway(PaymentGateway basicPaymentGateway) {
// LoggingDecorator wraps BasicPaymentGateway
return new LoggingPaymentGatewayDecorator(basicPaymentGateway);
}
@Bean
public PaymentGateway cachingPaymentGateway(PaymentGateway loggingPaymentGateway) {
// CachingDecorator wraps LoggingPaymentGateway (which wraps BasicPaymentGateway)
return new CachingPaymentGatewayDecorator(loggingPaymentGateway);
}
@Bean
@Primary // This bean will be injected by default when PaymentGateway is requested
public PaymentGateway securityPaymentGateway(PaymentGateway cachingPaymentGateway) {
// SecurityDecorator wraps CachingPaymentGateway (which wraps Logging, which wraps Basic)
return new SecurityPaymentGatewayDecorator(cachingPaymentGateway);
}
}
මේ විදියට @Configuration
class එකක් පාවිච්චි කරන එක වඩාත් හොඳයි, මොකද Spring Ecosystem එකට ගොඩක් හොඳට ගැලපෙනවා. @Primary
annotation එක දැම්මම, PaymentGateway
type එක request කරද්දී මේ securityPaymentGateway
bean එක තමයි default විදියට inject වෙන්නේ. මේකෙන් අපිට dependency injection පහසු වෙනවා. @Component
පාවිච්චි කරද්දි @Qualifier
නම් වලින් chain කරනවා වගේම, මේ @Bean
method වල parameter names වලින් ස්වයංක්රීයව chain එක හැදෙනවා.
Decorator Pattern එකේ වාසි සහ අවාසි (Pros and Cons)
වාසි (Pros):
- Increased Flexibility: Run-time එකේදී අලුත් functionalities එකතු කරන්න පුළුවන්.
- Avoids Subclassing Explosion: Classes ගොඩක් හදන එක වළක්වනවා, hierarchy එක simplify කරනවා.
- Single Responsibility Principle: හැම decorator එකකටම තියෙන්නේ එකම responsibility එකක්.
- Better Code Organization: Code එක cleaner, maintain කරන්න පහසුයි.
- Adheres to Open/Closed Principle: Existing code modify නොකර extend කරන්න පුළුවන්.
අවාසි (Cons):
- Increased Complexity: Simple cases වලට Decorator Pattern එක පාවිච්චි කළොත්, code base එක unnecessarily complicate වෙන්න පුළුවන්.
- Debugging Challenges: Layered structure එක නිසා debugging කරන එක ටිකක් අමාරු වෙන්න පුළුවන්, මොකද call stack එක ගැඹුරු වෙන නිසා.
- Many Small Objects: Decorator pattern එකේදී objects ගොඩක් හදන්න වෙනවා, හැබැයි Spring වගේ IoC container එකක් එක්ක මේක manage කරන එක එතරම් අමාරු දෙයක් නෙවෙයි.
මේ අවාසි තිබුණත්, නිවැරදි අවස්ථාවලදී Decorator Pattern එක පාවිච්චි කරන එකෙන් අපේ applications වල maintainability, flexibility, සහ extensibility ගොඩක් වැඩි කරගන්න පුළුවන්.
අවසන් වශයෙන්
ඉතින් යාළුවනේ, මේ blog post එකෙන් ඔයාලට Decorator Pattern එක ගැනත්, ඒක Spring Framework එකත් එක්ක කොහොමද use කරන්නේ කියන එක ගැනත් හොඳ අවබෝධයක් ලැබෙන්න ඇති කියලා හිතනවා. Design Patterns කියන්නේ Software Engineering වල හරිම වැදගත් කොටසක්, මොකද ඒකෙන් අපිට common problems වලට time-tested solutions ලබා දෙනවා.
මේ Pattern එක ඔයාලගේ projects වල use කරලා බලන්න. විශේෂයෙන්ම ඔයාලට තියෙන service එකකට extra behaviors add කරන්න ඕන නම්, subclassing කරන්නේ නැතිව, Decorator Pattern එක හොඳම විසඳුමක් වෙන්න පුළුවන්. මේ code snippets ඔයාගේ project එකකට දාලා ට්රයි කරලාම බලන්න. එතකොට වැඩේ හොඳටම තේරෙයි.
මේ ගැන ඔයාලට තව මොනවා හරි දැනගන්න තියෙනවා නම්, එහෙම නැත්නම් ඔයාලගේ අදහස්, මේ pattern එක පාවිච්චි කරපු experiences comment section එකේ share කරන්න. මම හිතනවා මේ guide එක ඔයාලට ගොඩක් වැදගත් වෙයි කියලා. එහෙනම් තවත් අලුත් Design Pattern එකක් නැත්නම් tech topic එකක් අරගෙන එනකම්, ඔයාලට සුභ දවසක්!