Policy
The Policy pattern encapsulates a set of business rules or logic into separate classes, allowing for greater flexibility and maintainability. It defines a family of algorithms (policies) and makes them interchangeable, enabling the selection of the appropriate algorithm at runtime based on context. Rather than hardcoding the logic within a single class or method, the Policy pattern promotes loose coupling and easier modification of behavior without altering the core client code.
Usage
The Policy pattern is commonly used in scenarios where business rules are complex and subject to change, or when different users or contexts require different behavior. Specific usage examples include:
- Access Control: Determining whether a user has permission to perform a certain action based on their role and other factors.
- Pricing Rules: Applying different pricing calculations depending on customer type, location, or purchase volume.
- Validation Logic: Implementing varied validation rules based on input data source or user preferences.
- Workflow Management: Executing different steps in a workflow based on the current state of the process.
- Gaming AI: Modifying AI behavior (e.g., aggression level) based on game difficulty or player actions.
Examples
-
Spring Security (Java): Spring Security utilizes policies to define access control rules.
AccessDecisionManagerinterfaces andVoteBasedorAffirmativeBasedaccess control strategies allow developers to define multipleAccessDecisionVoterimplementations, each representing a specific policy (e.g., role-based, IP address-based). These voters are then dynamically combined to determine access. -
GraphQL Authorization (JavaScript/Node.js): Many GraphQL server libraries offer mechanisms for implementing authorization policies. For instance, Apollo Server allows you to define resolver functions with rules that dictate which users can access specific fields or data. These rules can be implemented as separate policy classes or functions, promoting modularity and reusability. A policy might check user roles, ownership of data, or other contextual information before granting access.
-
Kubernetes Admission Controllers: Kubernetes uses admission controllers which can be implemented as policies to enforce specific constraints on resources before they are persisted. These policies can cover security, resource limits, and compliance requirements, ensuring that the cluster operates according to defined rules.
Specimens
15 implementationsThe Policy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It allows you to change the algorithm used at runtime without modifying the client code. This is achieved by defining a common interface for all algorithms (the “policy”) and then having concrete policy classes implement that interface. The context object holds a reference to the policy and delegates the execution of the algorithm to it.
In this Dart example, we have a DiscountPolicy interface and concrete policies like FixedAmountDiscount, and PercentageDiscount. The ShoppingCart class (the context) accepts a DiscountPolicy during construction and uses it to calculate the final price. This demonstrates Dart’s use of interfaces and dependency injection for flexible algorithm selection.
// Define the policy interface
abstract class DiscountPolicy {
double applyDiscount(double originalPrice);
}
// Concrete policy: Fixed amount discount
class FixedAmountDiscount implements DiscountPolicy {
final double amount;
FixedAmountDiscount(this.amount);
@override
double applyDiscount(double originalPrice) {
return originalPrice - amount;
}
}
// Concrete policy: Percentage discount
class PercentageDiscount implements DiscountPolicy {
final double percentage;
PercentageDiscount(this.percentage);
@override
double applyDiscount(double originalPrice) {
return originalPrice * (1 - percentage / 100);
}
}
// Context: Shopping Cart
class ShoppingCart {
final DiscountPolicy discountPolicy;
final List<double> items;
ShoppingCart(this.discountPolicy, this.items);
double calculateTotalPrice() {
double total = items.fold(0.0, (sum, item) => sum + item);
return discountPolicy.applyDiscount(total);
}
}
void main() {
// Example usage with different policies
final cart1 = ShoppingCart(FixedAmountDiscount(10.0), [100.0, 50.0, 25.0]);
print('Cart 1 Total: ${cart1.calculateTotalPrice()}'); // Output: Cart 1 Total: 165.0
final cart2 = ShoppingCart(PercentageDiscount(20.0), [100.0, 50.0, 25.0]);
print('Cart 2 Total: ${cart2.calculateTotalPrice()}'); // Output: Cart 2 Total: 130.0
}
The Policy pattern allows you to encapsulate a set of business rules or constraints into separate, reusable policy objects. This promotes separation of concerns and makes it easier to modify or extend the rules without affecting the core logic. The code defines a ValidationPolicy trait with a validate method. Concrete policies implement this trait to define specific validation rules. A Validator class takes a list of ValidationPolicy instances and applies them sequentially to an input. This approach is idiomatic Scala due to its use of traits for defining interfaces, higher-order functions for composition, and immutable data structures for representing policies.
trait ValidationPolicy {
def validate(input: String): Option[String]
}
class LengthPolicy(maxLength: Int) extends ValidationPolicy {
override def validate(input: String): Option[String] = {
if (input.length > maxLength) Some(s"Input length exceeds $maxLength") else None
}
}
class CharacterPolicy(allowedChars: Set[Char]) extends ValidationPolicy {
override def validate(input: String): Option[String] = {
if (input.exists(!allowedChars.contains(_))) Some("Invalid characters found") else None
}
}
class Validator(policies: List[ValidationPolicy]) {
def isValid(input: String): Boolean = {
policies.forall(policy => policy.validate(input).isEmpty)
}
def validate(input: String): Option[String] = {
policies.find(policy => policy.validate(input).isDefined).flatMap(_.validate(input))
}
}
object PolicyExample extends App {
val allowed = Set('a' to 'z', 'A' to 'Z', '0' to '9')
val lengthPolicy = new LengthPolicy(10)
val charPolicy = new CharacterPolicy(allowed)
val validator = new Validator(List(lengthPolicy, charPolicy))
val input1 = "validInput1"
val input2 = "tooLongInput1234567890"
val input3 = "invalid!Input"
println(s"$input1 is valid: ${validator.isValid(input1)}")
println(s"$input2 is valid: ${validator.isValid(input2)}")
println(s"$input3 is valid: ${validator.isValid(input3)}")
println(s"Validation error for $input2: ${validator.validate(input2)}")
println(s"Validation error for $input3: ${validator.validate(input3)}")
}
The Policy pattern encapsulates complex business logic or rules into separate classes, allowing for flexibility and maintainability. Instead of embedding these rules directly within an object, the object delegates the decision-making to a Policy object. This promotes the Single Responsibility Principle and makes it easier to modify or add new policies without altering the core object’s code.
In this PHP example, a DiscountPolicy interface defines the contract for applying discounts. Concrete policies like FixedAmountDiscountPolicy and PercentageDiscountPolicy implement this interface. The ShoppingCart class doesn’t know how discounts are calculated; it simply uses a DiscountPolicy to determine the final price. This adheres to PHP’s interface-based programming and dependency injection principles, making the code testable and extensible.
<?php
interface DiscountPolicy {
public function calculateDiscount(float $total): float;
}
class FixedAmountDiscountPolicy implements DiscountPolicy {
private float $discountAmount;
public function __construct(float $discountAmount) {
$this->discountAmount = $discountAmount;
}
public function calculateDiscount(float $total): float {
return min($this->discountAmount, $total);
}
}
class PercentageDiscountPolicy implements DiscountPolicy {
private float $discountPercentage;
public function __construct(float $discountPercentage) {
$this->discountPercentage = $discountPercentage;
}
public function calculateDiscount(float $total): float {
return $total * ($this->discountPercentage / 100);
}
}
class ShoppingCart {
private array $items;
private DiscountPolicy $discountPolicy;
public function __construct(DiscountPolicy $discountPolicy) {
$this->items = [];
$this->discountPolicy = $discountPolicy;
}
public function addItem(string $item, float $price): void {
$this->items[] = ['item' => $item, 'price' => $price];
}
public function calculateTotal(): float {
$total = array_sum(array_column($this->items, 'price'));
$discount = $this->discountPolicy->calculateDiscount($total);
return $total - $discount;
}
}
// Example Usage
$fixedDiscount = new FixedAmountDiscountPolicy(10.0);
$cart1 = new ShoppingCart($fixedDiscount);
$cart1->addItem("Shirt", 25.0);
$cart1->addItem("Pants", 40.0);
echo "Cart 1 Total: " . $cart1->calculateTotal() . PHP_EOL; // Output: Cart 1 Total: 55
$percentageDiscount = new PercentageDiscountPolicy(20.0);
$cart2 = new ShoppingCart($percentageDiscount);
$cart2->addItem("Shoes", 80.0);
$cart2->addItem("Hat", 20.0);
echo "Cart 2 Total: " . $cart2->calculateTotal() . PHP_EOL; // Output: Cart 2 Total: 80
The Policy pattern encapsulates business rules and logic related to permissions and authorization within dedicated policy objects. This promotes separation of concerns, making the code more maintainable and testable. Instead of scattering authorization checks throughout the application, a central policy determines if an action is permitted based on the user and the resource.
This Ruby implementation defines a Document class and a DocumentPolicy. The policy has a can_edit? method that checks if a user has permission to edit a document, based on the user’s role. The Document#can_edit?(user) method delegates to the policy, keeping the document model clean of authorization logic. This approach is idiomatic Ruby due to its emphasis on object-oriented design and the principle of “Don’t Repeat Yourself” (DRY). Using a dedicated class for policy makes testing and modification of permissions straightforward.
# app/models/document.rb
class Document
attr_reader :title, :content, :owner
def initialize(title, content, owner)
@title = title
@content = content
@owner = owner
end
def can_edit?(user)
DocumentPolicy.new(user, self).can_edit?
end
end
# app/policies/document_policy.rb
class DocumentPolicy
def initialize(user, document)
@user = user
@document = document
end
def can_edit?
@user.role == 'admin' || @user.id == @document.owner
end
end
# Example Usage
class User
attr_reader :role, :id
def initialize(role, id)
@role = role
@id = id
end
end
document = Document.new("My Report", "Some data", 123)
admin = User.new("admin", 456)
owner = User.new("user", 123)
regular_user = User.new("user", 789)
puts "Admin can edit: #{document.can_edit?(admin)}" # true
puts "Owner can edit: #{document.can_edit?(owner)}" # true
puts "Regular user can edit: #{document.can_edit?(regular_user)}" # false
The Policy pattern allows you to encapsulate a complex set of business rules into separate, interchangeable policy objects. This promotes flexibility and maintainability by avoiding monolithic conditional logic. Instead of a single class making decisions based on numerous if/else statements, different policies can be applied to a context, each handling a specific aspect of the decision-making process.
This Swift implementation defines a ValidationPolicy protocol with a isValid(for:) method. Concrete policies like LengthPolicy and CharacterPolicy conform to this protocol, each implementing its own validation rule. A Validator class takes an array of ValidationPolicy instances and applies them sequentially to a given string. The use of a protocol and composition aligns with Swift’s emphasis on protocol-oriented programming and avoids tight coupling.
// ValidationPolicy.swift
protocol ValidationPolicy {
func isValid(for input: String) -> Bool
}
// LengthPolicy.swift
struct LengthPolicy: ValidationPolicy {
private let minLength: Int
private let maxLength: Int
init(minLength: Int, maxLength: Int) {
self.minLength = minLength
self.maxLength = maxLength
}
func isValid(for input: String) -> Bool {
return input.count >= minLength && input.count <= maxLength
}
}
// CharacterPolicy.swift
struct CharacterPolicy: ValidationPolicy {
private let allowedCharacters: CharacterSet
init(allowedCharacters: CharacterSet) {
self.allowedCharacters = allowedCharacters
}
func isValid(for input: String) -> Bool {
return allowedCharacters.isSuperset(of: input.unicodeScalars)
}
}
// Validator.swift
class Validator {
private let policies: [ValidationPolicy]
init(policies: [ValidationPolicy]) {
self.policies = policies
}
func validate(input: String) -> Bool {
for policy in policies {
if !policy.isValid(for: input) {
return false
}
}
return true
}
}
// Example Usage
let allowedChars = CharacterSet.alphanumerics
let lengthPolicy = LengthPolicy(minLength: 8, maxLength: 20)
let charPolicy = CharacterPolicy(allowedCharacters: allowedChars)
let validator = Validator(policies: [lengthPolicy, charPolicy])
let validString = "SwiftPolicy123"
let invalidStringLength = "Short"
let invalidStringChars = "SwiftPolicy!@#"
print("Valid string: \(validator.validate(input: validString))") // true
print("Invalid string (length): \(validator.validate(input: invalidStringLength))") // false
print("Invalid string (characters): \(validator.validate(input: invalidStringChars))") // false
The Policy pattern allows you to encapsulate a non-functional requirement (like security, logging, or caching) into a separate, reusable component. This avoids scattering such logic throughout your core business logic, promoting separation of concerns and making it easier to modify or extend these policies without impacting the main functionality.
This Kotlin example demonstrates a simple LoggingPolicy that can be applied to any OrderProcessor. The OrderProcessor accepts a LoggingPolicy instance in its constructor and delegates the logging operation to it. This adheres to Kotlin’s principles of dependency injection and using interfaces for loose coupling. The use of a functional interface (LoggingPolicy) is also idiomatic for Kotlin, allowing for concise lambda expressions when defining specific logging behaviors.
// Define the Policy interface
interface LoggingPolicy {
fun log(message: String)
}
// Concrete implementation: Console Logging
class ConsoleLoggingPolicy : LoggingPolicy {
override fun log(message: String) {
println("LOG: $message")
}
}
// Concrete implementation: File Logging (example)
class FileLoggingPolicy(private val filePath: String) : LoggingPolicy {
override fun log(message: String) {
// In a real implementation, write to the file
println("FILE LOG: $message (would be written to $filePath)")
}
}
// The core business logic component
class OrderProcessor(private val loggingPolicy: LoggingPolicy) {
fun processOrder(orderId: String, amount: Double) {
loggingPolicy.log("Processing order $orderId with amount $amount")
// ... actual order processing logic ...
loggingPolicy.log("Order $orderId processed successfully")
}
}
// Usage
fun main() {
val consoleLogger = ConsoleLoggingPolicy()
val orderProcessorWithConsole = OrderProcessor(consoleLogger)
orderProcessorWithConsole.processOrder("123", 50.0)
val fileLogger = FileLoggingPolicy("order.log")
val orderProcessorWithFile = OrderProcessor(fileLogger)
orderProcessorWithFile.processOrder("456", 100.0)
}
The Policy pattern allows you to decouple the logic that determines if an operation is allowed from the operation itself. It achieves this by defining a “policy” – a trait or interface – that encapsulates the authorization rules. Different policies can be swapped in at runtime to change the behavior without modifying the core operation.
This Rust implementation defines a Policy trait with a check() method. A concrete AdminPolicy and ReadOnlyPolicy implement this trait, representing different access levels. The perform_action function takes a Policy as input, allowing it to execute the action only if the policy permits it. This is idiomatic Rust due to its use of traits for polymorphism and ownership/borrowing to ensure safe access control. The use of a trait object allows for flexible policy selection at runtime.
// Define the Policy trait
trait Policy {
fn check(&self, user_role: &str) -> bool;
}
// Concrete Policy: AdminPolicy
struct AdminPolicy;
impl Policy for AdminPolicy {
fn check(&self, user_role: &str) -> bool {
user_role == "admin"
}
}
// Concrete Policy: ReadOnlyPolicy
struct ReadOnlyPolicy;
impl Policy for ReadOnlyPolicy {
fn check(&self, user_role: &str) -> bool {
user_role == "admin" || user_role == "user"
}
}
// The operation that uses the policy
fn perform_action(policy: &dyn Policy, user_role: &str) {
if policy.check(user_role) {
println!("Action performed successfully by user with role: {}", user_role);
} else {
println!("Permission denied for user with role: {}", user_role);
}
}
fn main() {
let admin_policy = AdminPolicy;
let read_only_policy = ReadOnlyPolicy;
perform_action(&admin_policy, "admin"); // Action performed
perform_action(&admin_policy, "user"); // Permission denied
perform_action(&read_only_policy, "user"); // Action performed
perform_action(&read_only_policy, "guest"); // Permission denied
}
The Policy pattern allows runtime modification of an object’s behavior without altering its core code. It achieves this by defining a set of policies (typically interfaces) that encapsulate different behaviors. An object (the context) can then accept and apply these policies dynamically. This promotes flexibility and separation of concerns.
This Go implementation defines a Policy interface representing different actions. Concrete policies like DiscountPolicy and TaxPolicy implement this interface. The Product struct represents the context, holding a slice of Policy interfaces. The Apply method iterates through the policies and applies them to the product’s price. This is idiomatic Go due to its use of interfaces for abstraction and composition over inheritance, enabling dynamic behavior modification.
// policy.go
package main
import "fmt"
// Policy interface defines the behavior that can be applied.
type Policy interface {
Apply(price float64) float64
}
// DiscountPolicy applies a discount to the price.
type DiscountPolicy struct {
DiscountRate float64
}
func (d *DiscountPolicy) Apply(price float64) float64 {
return price * (1 - d.DiscountRate)
}
// TaxPolicy applies tax to the price.
type TaxPolicy struct {
TaxRate float64
}
func (t *TaxPolicy) Apply(price float64) float64 {
return price * (1 + t.TaxRate)
}
// Product represents the context that uses policies.
type Product struct {
Name string
Price float64
Policies []Policy
}
// ApplyPolicies applies all registered policies to the product's price.
func (p *Product) ApplyPolicies(price float64) float64 {
for _, policy := range p.Policies {
price = policy.Apply(price)
}
return price
}
func main() {
product := &Product{
Name: "Laptop",
Price: 1000.0,
Policies: []Policy{
&DiscountPolicy{DiscountRate: 0.1},
&TaxPolicy{TaxRate: 0.08},
},
}
finalPrice := product.ApplyPolicies(product.Price)
fmt.Printf("Final price of %s: %.2f\n", product.Name, finalPrice)
//Demonstrate dynamic policy addition
product.Policies = append(product.Policies, &DiscountPolicy{DiscountRate: 0.05})
finalPrice = product.ApplyPolicies(product.Price)
fmt.Printf("Final price of %s with additional discount: %.2f\n", product.Name, finalPrice)
}
The Policy pattern allows you to define a family of algorithms (policies) and encapsulate each one into a separate class. It then allows the client to choose which algorithm to use at runtime without knowing the details of its implementation. This promotes loose coupling and flexibility.
Here, we define a data_t structure and a process_data function that takes a pointer to this structure and a policy function pointer. Different policies (e.g., square_policy, double_policy) are implemented as functions that conform to the data_policy type. The client code can then select and apply a policy to the data without modifying the core processing logic. This approach is idiomatic C as it leverages function pointers to achieve polymorphism and avoids the complexities of C++ style virtual functions.
#include <stdio.h>
// The data structure to be processed
typedef struct {
int value;
} data_t;
// Define a function pointer type for the policy
typedef int (*data_policy)(const data_t *data);
// Policy 1: Square the value
int square_policy(const data_t *data) {
return data->value * data->value;
}
// Policy 2: Double the value
int double_policy(const data_t *data) {
return data->value * 2;
}
// Function to process the data using a given policy
int process_data(const data_t *data, data_policy policy) {
return policy(data);
}
int main() {
data_t my_data = {5};
// Apply the square policy
int squared_value = process_data(&my_data, square_policy);
printf("Squared value: %d\n", squared_value);
// Apply the double policy
int doubled_value = process_data(&my_data, double_policy);
printf("Doubled value: %d\n", doubled_value);
return 0;
}
The Policy pattern allows you to decouple algorithms from the specific ways they perform certain steps (policies). These policies are then injected into the algorithm, enabling runtime variation of behavior without modifying the algorithm itself. This promotes flexibility and reusability.
The C++ example demonstrates a DataProcessor class that takes a LoggingPolicy as a parameter in its constructor. The LoggingPolicy is an abstract class with a single method, logMessage(). Concrete policies like ConsoleLogger and FileLogger implement this method to log messages to the console or a file, respectively. The DataProcessor uses the injected policy to log data processing events. This is idiomatic C++ due to its use of polymorphism via abstract classes and dependency injection, avoiding tight coupling and enabling easy extension with new logging mechanisms.
#include <iostream>
#include <fstream>
#include <string>
// Policy Interface
class LoggingPolicy {
public:
virtual void logMessage(const std::string& message) = 0;
virtual ~LoggingPolicy() = default;
};
// Concrete Policy 1: Console Logger
class ConsoleLogger : public LoggingPolicy {
public:
void logMessage(const std::string& message) override {
std::cout << "[Console] " << message << std::endl;
}
};
// Concrete Policy 2: File Logger
class FileLogger : public LoggingPolicy {
private:
std::ofstream logFile;
public:
FileLogger(const std::string& filename) : logFile(filename, std::ios::app) {}
void logMessage(const std::string& message) override {
logFile << "[File] " << message << std::endl;
}
~FileLogger() {
logFile.close();
}
};
// Context Class (Algorithm)
class DataProcessor {
private:
LoggingPolicy* loggingPolicy;
public:
DataProcessor(LoggingPolicy* policy) : loggingPolicy(policy) {}
void processData(const std::string& data) {
loggingPolicy->logMessage("Processing data: " + data);
// Simulate data processing
std::cout << "Data processed: " << data << std::endl;
}
~DataProcessor() {
delete loggingPolicy;
}
};
int main() {
// Use Console Logger
DataProcessor processor1(new ConsoleLogger());
processor1.processData("Sample Data 1");
// Use File Logger
DataProcessor processor2(new FileLogger("data_log.txt"));
processor2.processData("Sample Data 2");
return 0;
}
The Policy pattern allows you to encapsulate a changeable algorithm or set of rules into a separate object. This promotes flexibility as you can swap out different policies at runtime without modifying the core logic that uses the policy. This example demonstrates a simple pricing policy. The PricingService class depends on an IPricingPolicy interface. Different concrete policies (e.g., StandardPricingPolicy, DiscountedPricingPolicy) implement the pricing logic. The client can inject the desired policy, enabling dynamic pricing strategies. This approach aligns with C#’s dependency injection principles and interface-based programming, making the code testable and maintainable.
// IPricingPolicy.cs
public interface IPricingPolicy
{
decimal CalculatePrice(decimal originalPrice, int quantity);
}
// StandardPricingPolicy.cs
public class StandardPricingPolicy : IPricingPolicy
{
public decimal CalculatePrice(decimal originalPrice, int quantity)
{
return originalPrice * quantity;
}
}
// DiscountedPricingPolicy.cs
public class DiscountedPricingPolicy : IPricingPolicy
{
private readonly decimal _discountPercentage;
public DiscountedPricingPolicy(decimal discountPercentage)
{
_discountPercentage = discountPercentage;
}
public decimal CalculatePrice(decimal originalPrice, int quantity)
{
decimal discountFactor = 1 - _discountPercentage;
return (originalPrice * quantity) * discountFactor;
}
}
// PricingService.cs
public class PricingService
{
private readonly IPricingPolicy _pricingPolicy;
public PricingService(IPricingPolicy pricingPolicy)
{
_pricingPolicy = pricingPolicy;
}
public decimal GetPrice(decimal originalPrice, int quantity)
{
return _pricingPolicy.CalculatePrice(originalPrice, quantity);
}
}
// Example Usage (Program.cs)
public class Program
{
public static void Main(string[] args)
{
// Use standard pricing
var standardPolicy = new StandardPricingPolicy();
var pricingServiceStandard = new PricingService(standardPolicy);
decimal price1 = pricingServiceStandard.GetPrice(10, 5);
Console.WriteLine($"Standard Price: {price1}");
// Use discounted pricing
var discountPolicy = new DiscountedPricingPolicy(0.1m); // 10% discount
var pricingServiceDiscount = new PricingService(discountPolicy);
decimal price2 = pricingServiceDiscount.GetPrice(10, 5);
Console.WriteLine($"Discounted Price: {price2}");
}
}
The Policy pattern defines a family of algorithms and encapsulates each one into a separate class. It then allows the client to select the appropriate algorithm at runtime. This promotes loose coupling and allows for easy extension of functionality without modifying existing code.
This TypeScript implementation uses an interface ValidationPolicy to define the contract for validation logic. Concrete policies like RequiredFieldPolicy and EmailFormatPolicy implement this interface, each providing a specific validation rule. A Validator class accepts a ValidationPolicy via dependency injection and applies it to a given data object. This approach is idiomatic TypeScript due to its strong typing, use of interfaces for abstraction, and reliance on composition over inheritance.
// ValidationPolicy Interface
interface ValidationPolicy {
validate(data: any): string | null;
}
// Concrete Policy 1: Required Field
class RequiredFieldPolicy implements ValidationPolicy {
private fieldName: string;
constructor(fieldName: string) {
this.fieldName = fieldName;
}
validate(data: any): string | null {
if (!data[this.fieldName]) {
return `${this.fieldName} is required.`;
}
return null;
}
}
// Concrete Policy 2: Email Format
class EmailFormatPolicy implements ValidationPolicy {
validate(data: any): string | null {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(data.email)) {
return "Invalid email format.";
}
return null;
}
}
// Validator Class
class Validator {
private policy: ValidationPolicy;
constructor(policy: ValidationPolicy) {
this.policy = policy;
}
validate(data: any): string | null {
return this.policy.validate(data);
}
}
// Example Usage
const userData = {
name: "John Doe",
email: "invalid-email"
};
const requiredNamePolicy = new RequiredFieldPolicy("name");
const emailFormatPolicy = new EmailFormatPolicy();
const nameValidator = new Validator(requiredNamePolicy);
const emailValidator = new Validator(emailFormatPolicy);
const nameError = nameValidator.validate(userData);
const emailError = emailValidator.validate(userData);
if (nameError) {
console.error("Name Validation Error:", nameError);
}
if (emailError) {
console.error("Email Validation Error:", emailError);
}
The Policy pattern allows you to dynamically apply different behaviors or rules to an object based on certain conditions, without modifying the object itself. It decouples the decision-making logic (the policy) from the object being governed. This is achieved by defining a policy object that contains methods representing different actions. The object delegates the action to the policy, which then determines the appropriate implementation to execute. This implementation uses a simple object literal to represent the policy, and a function to execute the action based on the current context. This approach is common in JavaScript due to its flexible object nature and reliance on function calls.
/**
* Policy Pattern Implementation in JavaScript
*/
// The Context - the object whose behavior is governed by the policy
class User {
constructor(role) {
this.role = role;
}
canAccessFeature(policy, feature) {
return policy.canAccess(this.role, feature);
}
}
// The Policy - defines the rules for access
const accessPolicy = {
canAccess: (role, feature) => {
switch (role) {
case 'admin':
return true;
case 'editor':
return feature === 'edit';
case 'viewer':
return feature === 'view';
default:
return false;
}
}
};
// Example Usage
const adminUser = new User('admin');
const editorUser = new User('editor');
const viewerUser = new User('viewer');
console.log("Admin can access edit:", adminUser.canAccessFeature(accessPolicy, 'edit')); // true
console.log("Editor can access edit:", editorUser.canAccessFeature(accessPolicy, 'edit')); // true
console.log("Viewer can access edit:", viewerUser.canAccessFeature(accessPolicy, 'edit')); // false
console.log("Admin can access view:", adminUser.canAccessFeature(accessPolicy, 'view')); // true
console.log("Editor can access view:", editorUser.canAccessFeature(accessPolicy, 'view')); // false
console.log("Viewer can access view:", viewerUser.canAccessFeature(accessPolicy, 'view')); // true
The Policy pattern allows for runtime modification of object behavior without altering the object’s code. It achieves this by encapsulating the behavior within separate policy classes, and allowing the object to accept different policies as needed. This promotes flexibility and adheres to the Open/Closed Principle.
The Python example defines a Subject class representing the object whose behavior is to be modified. A Policy abstract base class defines the interface for behaviors. Concrete policies like ValidationPolicy and LoggingPolicy implement this interface. The Subject accepts a policy during initialization and delegates behavior to it. This is idiomatic Python due to its use of duck typing and ABCs for defining interfaces, and its flexible nature allows for easy extension with new policies.
from abc import ABC, abstractmethod
import logging
logging.basicConfig(level=logging.INFO)
class Policy(ABC):
@abstractmethod
def execute(self, data):
pass
class ValidationPolicy(Policy):
def __init__(self, rules):
self.rules = rules
def execute(self, data):
for rule in self.rules:
if not rule(data):
raise ValueError("Data validation failed.")
return data
class LoggingPolicy(Policy):
def execute(self, data):
logging.info(f"Data processed: {data}")
return data
class Subject:
def __init__(self, policy: Policy):
self.policy = policy
def process_data(self, data):
return self.policy.execute(data)
# Example Usage
def is_positive(data):
return data > 0
def is_string(data):
return isinstance(data, str)
if __name__ == "__main__":
# Process a positive number with validation and logging
validation_policy = ValidationPolicy([is_positive])
logging_policy = LoggingPolicy()
# Combine policies (chain of responsibility style)
combined_policy = lambda data: logging_policy.execute(validation_policy.execute(data))
subject = Subject(combined_policy)
try:
result = subject.process_data(5)
print(f"Processed data: {result}")
except ValueError as e:
print(f"Error: {e}")
try:
result = subject.process_data(-2)
print(f"Processed data: {result}")
except ValueError as e:
print(f"Error: {e}")
# Process a string with only logging
subject = Subject(logging_policy)
result = subject.process_data("Hello, Policy Pattern!")
print(f"Processed data: {result}")
The Policy pattern allows for runtime selection of algorithms or behaviors based on configurable rules. It decouples the decision-making process (the policy) from the specific implementations (the strategies). This promotes flexibility and avoids hardcoding logic.
The Java implementation uses an interface ValidationPolicy to define the validation logic. Concrete policies like EmailValidationPolicy and LengthValidationPolicy implement this interface. A PolicyHandler class encapsulates a list of policies and iterates through them, applying each to the input data. This approach is idiomatic Java due to its use of interfaces for abstraction and classes for concrete implementations, leveraging polymorphism for dynamic behavior selection. The PolicyHandler acts as a context, managing the policies.
// ValidationPolicy.java
interface ValidationPolicy {
boolean validate(String data);
String getErrorMessage();
}
// EmailValidationPolicy.java
class EmailValidationPolicy implements ValidationPolicy {
@Override
public boolean validate(String data) {
return data.matches("^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$");
}
@Override
public String getErrorMessage() {
return "Invalid email format";
}
}
// LengthValidationPolicy.java
class LengthValidationPolicy implements ValidationPolicy {
private final int minLength;
private final int maxLength;
public LengthValidationPolicy(int minLength, int maxLength) {
this.minLength = minLength;
this.maxLength = maxLength;
}
@Override
public boolean validate(String data) {
return data.length() >= minLength && data.length() <= maxLength;
}
@Override
public String getErrorMessage() {
return "Data length must be between " + minLength + " and " + maxLength + " characters.";
}
}
// PolicyHandler.java
import java.util.List;
import java.util.ArrayList;
class PolicyHandler {
private final List<ValidationPolicy> policies;
public PolicyHandler(List<ValidationPolicy> policies) {
this.policies = new ArrayList<>(policies);
}
public ValidationResult validate(String data) {
for (ValidationPolicy policy : policies) {
if (!policy.validate(data)) {
return new ValidationResult(false, policy.getErrorMessage());
}
}
return new ValidationResult(true, null);
}
}
// ValidationResult.java
class ValidationResult {
private final boolean isValid;
private final String errorMessage;
public ValidationResult(boolean isValid, String errorMessage) {
this.isValid = isValid;
this.errorMessage = errorMessage;
}
public boolean isValid() {
return isValid;
}
public String getErrorMessage() {
return errorMessage;
}
}
// Example Usage (Main.java)
public class Main {
public static void main(String[] args) {
List<ValidationPolicy> policies = new ArrayList<>();
policies.add(new EmailValidationPolicy());
policies.add(new LengthValidationPolicy(5, 20));
PolicyHandler policyHandler = new PolicyHandler(policies);
ValidationResult result1 = policyHandler.validate("test@example.com");
System.out.println("Data: test@example.com, Valid: " + result1.isValid() + ", Error: " + result1.getErrorMessage());
ValidationResult result2 = policyHandler.validate("short");
System.out.println("Data: short, Valid: " + result2.isValid() + ", Error: " + result2.getErrorMessage());
ValidationResult result3 = policyHandler.validate("thisisareallylongstringthatwillfailthevalidation");
System.out.println("Data: thisisareallylongstringthatwillfailthevalidation, Valid: " + result3.isValid() + ", Error: " + result3.getErrorMessage());
}
}