============================================================
Creational Patterns
Singleton Pattern
Ensures a class has only one instance, and provides a global point of access to it.
/**
* Eager initialization
*/
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
/**
* Lazy initialization
*/
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() {}
public static synchronized LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
public class Client {
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);
LazySingleton s3 = LazySingleton.getInstance();
LazySingleton s4 = LazySingleton.getInstance();
System.out.println(s3 == s4);
}
}
Registry-based Singleton
Overcomes limitations of eager or lazy singletons regarding inheritance.
public class RegistrySingleton {
private static final Map<String, Object> REGISTRY = new HashMap<>();
static {
RegistrySingleton instance = new RegistrySingleton();
REGISTRY.put(instance.getClass().getName(), instance);
}
protected RegistrySingleton() {}
public static RegistrySingleton getInstance(String name) {
if (name == null) {
name = "com.example.RegistrySingleton";
}
if (!REGISTRY.containsKey(name)) {
try {
REGISTRY.put(name, Class.forName(name).newInstance());
} catch (Exception e) {
System.out.println("Error occurred");
}
}
return (RegistrySingleton) REGISTRY.get(name);
}
public String about() {
return "Hello, I am RegistrySingleton";
}
}
public class ChildSingleton extends RegistrySingleton {
public ChildSingleton() {}
public static ChildSingleton getInstance() {
return (ChildSingleton) RegistrySingleton.getInstance("com.example.ChildSingleton");
}
public String about() {
return "Hello, I am ChildSingleton";
}
}
Singleton Usage Example (Configuration Management)
// Double-checked locking for thread-safe singleton
public class AppConfig {
private static volatile AppConfig instance;
private Properties config;
private AppConfig() {
loadConfig();
}
public static AppConfig getInstance() {
if (instance == null) {
synchronized (AppConfig.class) {
if (instance == null) {
instance = new AppConfig();
}
}
}
return instance;
}
private void loadConfig() {
config = new Properties();
try (InputStream input = getClass().getClassLoader()
.getResourceAsStream("application.properties")) {
config.load(input);
} catch (IOException e) {
throw new RuntimeException("Failed to load configuration file", e);
}
}
public String getProperty(String key) {
return config.getProperty(key);
}
}
// Usage example
public class ConfigService {
public void init() {
String dbUrl = AppConfig.getInstance().getProperty("database.url");
// Initialize database connection with config
}
}
Simple Factory Pattern
A factory class with a static method that returns different instances based on parameters.
public interface Car {
void drive();
}
public class Benz implements Car {
public void drive() {
System.out.println("Driving Benz");
}
}
public class Bmw implements Car {
public void drive() {
System.out.println("Driving BMW");
}
}
/**
* Simple Factory
* Creates instances based on parameter input
*/
public class CarFactory {
public static Car createCar(String type) throws Exception {
if ("Benz".equalsIgnoreCase(type)) {
return new Benz();
} else if ("BMW".equalsIgnoreCase(type)) {
return new Bmw();
}
return null;
}
}
public class Client {
public static void main(String[] args) {
Car car = CarFactory.createCar("benz");
car.drive();
}
}
Case 2:
public class FoodFactory {
public static Food makeFood(String name) {
if ("noodle".equals(name)) {
Food noodle = new LanzhouNoodle();
noodle.addSpicy("more");
return noodle;
} else if ("chicken".equals(name)) {
Food chicken = new HuangMenChicken();
chicken.addCondiment("potato");
return chicken;
} else {
return null;
}
}
}
Factory Method Pattern
Defines an interface for creating objects, letting subclasses decide which class to instantiate.
/**
* Abstract factory role
*/
public interface CarFactory {
Car createCar();
}
/**
* Subclasses determine which class to instantiate
*/
public class BenzFactory implements CarFactory {
public Car createCar() {
return new Benz();
}
}
public class BmwFactory implements CarFactory {
public Car createCar() {
return new Bmw();
}
}
public class Client {
public static void main(String[] args) {
CarFactory factory = new BenzFactory();
Car car = factory.createCar();
car.drive();
}
}
Case 2:
public interface FoodFactory {
Food makeFood(String name);
}
public class ChineseFoodFactory implements FoodFactory {
@Override
public Food makeFood(String name) {
if ("A".equals(name)) {
return new ChineseFoodA();
} else if ("B".equals(name)) {
return new ChineseFoodB();
} else {
return null;
}
}
}
public class AmericanFoodFactory implements FoodFactory {
@Override
public Food makeFood(String name) {
if ("A".equals(name)) {
return new AmericanFoodA();
} else if ("B".equals(name)) {
return new AmericanFoodB();
} else {
return null;
}
}
}
public class App {
public static void main(String[] args) {
// Choose a specific factory
FoodFactory factory = new ChineseFoodFactory();
// Create specific food
Food food = factory.makeFood("A");
}
}
Abstract Factory Pattern
Provides an interface for creating families of related or dependent objects without specifying their concrete classes.
/**
* Human interface
* White, black, yellow races
*/
public interface Human {
void getColor();
void talk();
void getSex();
}
public abstract class AbstractWhiteHuman implements Human {
public void getColor() {
System.out.println("White human skin is white!");
}
public void talk() {
System.out.println("White humans speak in single-byte characters.");
}
}
public abstract class AbstractBlackHuman implements Human {
public void getColor() {
System.out.println("Black human skin is black!");
}
public void talk() {
System.out.println("Black humans speak in incomprehensible language.");
}
}
public abstract class AbstractYellowHuman implements Human {
public void getColor() {
System.out.println("Yellow human skin is yellow!");
}
public void talk() {
System.out.println("Yellow humans speak in double-byte characters.");
}
}
/**
* Yellow humans - male and female
*/
public class FemaleYellowHuman extends AbstractYellowHuman {
public void getSex() {
System.out.println("Yellow female human");
}
}
public class MaleYellowHuman extends AbstractYellowHuman {
public void getSex() {
System.out.println("Yellow male human");
}
}
/**
* Human production factory
* Men: yellow, white, black
* Women: yellow, white, black
*/
public interface HumanFactory {
Human createYellowHuman();
Human createWhiteHuman();
Human createBlackHuman();
}
public class FemaleFactory implements HumanFactory {
public Human createBlackHuman() {
return new FemaleBlackHuman();
}
public Human createWhiteHuman() {
return new FemaleWhiteHuman();
}
public Human createYellowHuman() {
return new FemaleYellowHuman();
}
}
public class MaleFactory implements HumanFactory {
public Human createBlackHuman() {
return new MaleBlackHuman();
}
public Human createWhiteHuman() {
return new MaleWhiteHuman();
}
public Human createYellowHuman() {
return new MaleYellowHuman();
}
}
public class NvWa {
public static void main(String[] args) {
// Male production line
HumanFactory maleHumanFactory = new MaleFactory();
// Female production line
HumanFactory femaleHumanFactory = new FemaleFactory();
// Produce humans:
Human maleYellowHuman = maleHumanFactory.createYellowHuman();
Human femaleYellowHuman = femaleHumanFactory.createYellowHuman();
System.out.println("---Producing a yellow female---");
femaleYellowHuman.getColor();
femaleYellowHuman.talk();
femaleYellowHuman.getSex();
System.out.println("\n---Producing a yellow male---");
maleYellowHuman.getColor();
maleYellowHuman.talk();
maleYellowHuman.getSex();
}
}
Case 2:
public static void main(String[] args) {
// Select a major factory (CPU, motherboard, hard disk)
ComputerFactory cf = new AmdFactory();
CPU cpu = cf.makeCPU();
MainBoard board = cf.makeMainBoard();
HardDisk hardDisk = cf.makeHardDisk();
// Assemble components from same factory
Computer result = new Computer(cpu, board, hardDisk);
}
Factory Pattern Usage Example (Payment Channel Creation)
// Payment interface
public interface PaymentService {
boolean pay(BigDecimal amount);
boolean refund(String orderId);
}
// Concrete implementations
public class AlipayService implements PaymentService {
@Override
public boolean pay(BigDecimal amount) {
// Alipay payment logic
return true;
}
@Override
public boolean refund(String orderId) {
// Alipay refund logic
return true;
}
}
public class WechatPayService implements PaymentService {
@Override
public boolean pay(BigDecimal amount) {
// WeChat payment logic
return true;
}
@Override
public boolean refund(String orderId) {
// WeChat refund logic
return true;
}
}
// Payment factory
public class PaymentFactory {
public static PaymentService createPayment(String paymentType) {
switch (paymentType.toUpperCase()) {
case "ALIPAY":
return new AlipayService();
case "WECHAT":
return new WechatPayService();
case "UNIONPAY":
return new UnionPayService();
default:
throw new IllegalArgumentException("Unsupported payment type: " + paymentType);
}
}
}
// Usage example
@Service
public class OrderService {
public boolean processPayment(Order order, String paymentType) {
PaymentService paymentService = PaymentFactory.createPayment(paymentType);
return paymentService.pay(order.getAmount());
}
}
Builder Pattern
Separates the construction of a complex object from its representation, allowing the same construction process to create different representations.
/**
* Car model
*/
public abstract class CarModel {
private ArrayList<String> sequence = new ArrayList<>();
protected abstract void start();
protected abstract void stop();
protected abstract void alarm();
protected abstract void engineBoom();
final public void run() {
for (int i = 0; i < this.sequence.size(); i++) {
String actionName = this.sequence.get(i);
if (actionName.equalsIgnoreCase("start")) {
this.start();
} else if (actionName.equalsIgnoreCase("stop")) {
this.stop();
} else if (actionName.equalsIgnoreCase("alarm")) {
this.alarm();
} else if (actionName.equalsIgnoreCase("engine boom")) {
this.engineBoom();
}
}
}
final public void setSequence(ArrayList<String> sequence) {
this.sequence = sequence;
}
}
/**
* Benz
*/
public class BenzModel extends CarModel {
protected void alarm() {
System.out.println("Benz car's horn sounds like this...");
}
protected void engineBoom() {
System.out.println("Benz car's engine sounds like this...");
}
protected void start() {
System.out.println("Benz car runs like this...");
}
protected void stop() {
System.out.println("Benz car stops like this...");
}
}
/**
* BMW
*/
public class BMWModel extends CarModel {
protected void alarm() {
System.out.println("BMW car's horn sounds like this...");
}
protected void engineBoom() {
System.out.println("BMW car's engine sounds like this...");
}
protected void start() {
System.out.println("BMW car runs like this...");
}
protected void stop() {
System.out.println("BMW car stops like this...");
}
}
/**
* Car assembly: Builder pattern
*/
public abstract class CarBuilder {
public abstract void setSequence(ArrayList<String> sequence);
public abstract CarModel getCarModel();
}
public class BenzBuilder extends CarBuilder {
private BenzModel benz = new BenzModel();
public CarModel getCarModel() {
return this.benz;
}
public void setSequence(ArrayList<String> sequence) {
this.benz.setSequence(sequence);
}
}
public class BMWBuilder extends CarBuilder {
private BMWModel bmw = new BMWModel();
public CarModel getCarModel() {
return this.bmw;
}
public void setSequence(ArrayList<String> sequence) {
this.bmw.setSequence(sequence);
}
}
public class Client {
public static void main(String[] args) {
ArrayList<String> sequence = new ArrayList<>();
sequence.add("engine boom");
sequence.add("start");
sequence.add("stop");
BenzBuilder benzBuilder = new BenzBuilder();
benzBuilder.setSequence(sequence);
BenzModel benz = (BenzModel) benzBuilder.getCarModel();
benz.run();
}
}
Fluent Builder Pattern
class User {
private String name;
private String password;
private String nickName;
private int age;
private User(String name, String password, String nickName, int age) {
this.name = name;
this.password = password;
this.nickName = nickName;
this.age = age;
}
public static UserBuilder builder() {
return new UserBuilder();
}
public static class UserBuilder {
private String name;
private String password;
private String nickName;
private int age;
private UserBuilder() {}
public UserBuilder name(String name) {
this.name = name;
return this;
}
public UserBuilder password(String password) {
this.password = password;
return this;
}
public UserBuilder nickName(String nickName) {
this.nickName = nickName;
return this;
}
public UserBuilder age(int age) {
this.age = age;
return this;
}
public User build() {
if (name == null || password == null) {
throw new RuntimeException("Username and password required");
}
if (age <= 0 || age >= 150) {
throw new RuntimeException("Invalid age");
}
if (nickName == null) {
nickName = name;
}
return new User(name, password, nickName, age);
}
}
}
public class App {
public static void main(String[] args) {
User u = User.builder()
.name("foo")
.password("123")
.age(23)
.build();
}
}
Case 2:
The builder pattern sets all properties, then copies them to the actual object during the build phase. When there are many properties with some being required and others optional, this pattern makes the code much clearer. We can enforce mandatory fields in the builder constructor and validate parameters in the build method, which is more elegant than doing so in the User constructor.
class User {
private String name;
private String password;
private String nickName;
private int age;
private User(String name, String password, String nickName, int age) {
this.name = name;
this.password = password;
this.nickName = nickName;
this.age = age;
}
public static UserBuilder builder() {
return new UserBuilder();
}
public static class UserBuilder {
private String name;
private String password;
private String nickName;
private int age;
private UserBuilder() {}
public UserBuilder name(String name) {
this.name = name;
return this;
}
public UserBuilder password(String password) {
this.password = password;
return this;
}
public UserBuilder nickName(String nickName) {
this.nickName = nickName;
return this;
}
public UserBuilder age(int age) {
this.age = age;
return this;
}
public User build() {
if (name == null || password == null) {
throw new RuntimeException("Username and password required");
}
if (age <= 0 || age >= 150) {
throw new RuntimeException("Invalid age");
}
if (nickName == null) {
nickName = name;
}
return new User(name, password, nickName, age);
}
}
}
public class App {
public static void main(String[] args) {
User u = User.builder()
.name("foo")
.password("123")
.age(25)
.build();
}
}
Prototype Pattern
Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.
Case 1:
/**
* Concrete prototype
*/
public class EnemyPlane implements Cloneable {
private int x;
private int y = 0;
public EnemyPlane(int x) {
this.x = x;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public void fly() {
y++;
}
public void setX(int x) {
this.x = x;
}
@Override
public EnemyPlane clone() throws CloneNotSupportedException {
return (EnemyPlane) super.clone();
}
}
public class EnemyPlaneFactory {
private static EnemyPlane protoType = new EnemyPlane(200);
public static EnemyPlane getInstance(int x) {
EnemyPlane clone = protoType.clone();
clone.setX(x);
return clone;
}
}
Case 2:
public class Mail implements Cloneable {
private String receiver;
private String subject;
private String appellation;
private String content;
private String tail;
public Mail(AdvTemplate advTemplate) {
this.content = advTemplate.getAdvContext();
this.subject = advTemplate.getAdvSubject();
}
@Override
public Mail clone() {
Mail mail = null;
try {
mail = (Mail) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return mail;
}
// Getters and setters
public String getReceiver() { return receiver; }
public void setReceiver(String receiver) { this.receiver = receiver; }
public String getSubject() { return subject; }
public void setSubject(String subject) { this.subject = subject; }
public String getAppellation() { return appellation; }
public void setAppellation(String appellation) { this.appellation = appellation; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
public String getTail() { return tail; }
public void setTail(String tail) { this.tail = tail; }
}
public class Client {
private static int MAX_COUNT = 6;
public static void main(String[] args) {
Mail mail = new Mail(new AdvTemplate());
mail.setTail("XX Bank Copyright");
for (int i = 0; i < MAX_COUNT; i++) {
Mail cloneMail = mail.clone();
cloneMail.setAppellation(getRandString(5) + " Mr./Ms.");
cloneMail.setReceiver(getRandString(5) + "@" + getRandString(8) + ".com");
sendMail(cloneMail);
}
}
}
Iterator Pattern
Also known as Cursor pattern, provides a way to access elements of a container object sequentially without exposing its underlying representation.
Case 1:
/**
* Iterator interface standard
*/
public interface Iterator<E> {
E next();
boolean hasNext();
}
/**
* Driving recorder
*/
public class DrivingRecorder {
private int index = -1;
private String[] records = new String[10];
public void append(String record) {
if (index == 9) {
index = 0;
} else {
index++;
}
records[index] = record;
}
public Iterator<String> iterator() {
return new Itr();
}
private class Itr implements Iterator<String> {
int cursor = index;
int loopCount = 0;
@Override
public boolean hasNext() {
return loopCount < 10;
}
@Override
public String next() {
int i = cursor;
if (cursor == 0) {
cursor = 9;
} else {
cursor--;
}
loopCount++;
return records[i];
}
}
}
public class Client {
public static void main(String[] args) {
DrivingRecorder dr = new DrivingRecorder();
for (int i = 0; i < 12; i++) {
dr.append("Video_" + i);
}
List<String> accidents = new ArrayList<>();
Iterator<String> it = dr.iterator();
while (it.hasNext()) {
String video = it.next();
System.out.println(video);
if ("Video_10".equals(video) || "Video_8".equals(video)) {
accidents.add(video);
}
}
System.out.println("Accident evidence: " + accidents);
}
}
Case 2:
public interface IProject {
void add(String name, int num, int cost);
String getProjectInfo();
IProjectIterator iterator();
}
public class Project implements IProject {
private ArrayList<IProject> projectList = new ArrayList<>();
private String name = "";
private int num = 0;
private int cost = 0;
public void add(String name, int num, int cost) {
this.projectList.add(new Project(name, num, cost));
}
public Project(String name, int num, int cost) {
this.name = name;
this.num = num;
this.cost = cost;
}
public String getProjectInfo() {
String info = "";
info += "Project name: " + this.name;
info += "\tPeople: " + this.num;
info += "\tCost: " + this.cost;
return info;
}
public IProjectIterator iterator() {
return new ProjectIterator(this.projectList);
}
}
public interface IProjectIterator extends Iterator {
}
public class ProjectIterator implements IProjectIterator {
private ArrayList<IProject> projectList = new ArrayList<>();
private int currentItem = 0;
public ProjectIterator(ArrayList<IProject> projectList) {
this.projectList = projectList;
}
public boolean hasNext() {
boolean b = true;
if (this.currentItem >= projectList.size() || this.projectList.get(this.currentItem) == null) {
b = false;
}
return b;
}
public IProject next() {
return (IProject) this.projectList.get(this.currentItem++);
}
public void remove() {}
}
public class Boss {
public static void main(String[] args) {
ArrayList<IProject> projectList = new ArrayList<>();
projectList.add(new Project("Star Wars", 10, 100000));
projectList.add(new Project("Time Warp", 100, 10000000));
projectList.add(new Project("Superman", 10000, 1000000000));
for (int i = 4; i < 104; i++) {
projectList.add(new Project("Project " + i, i * 5, i * 1000000));
}
IProjectIterator projectIterator = projectList.iterator();
while (projectIterator.hasNext()) {
IProject p = (IProject) projectIterator.next();
System.out.println(p.getProjectInfo());
}
}
}
=======================================================
Behavioral Patterns
Command Pattern
Encapsulates a request as an object, thereby allowing for parameterization of clients with queues, logging, and support for undo operations.
Interpreter Pattern
Defines a representation of a language and defines an interpreter for that language.
Chain of Responsibility
Allows multiple objects to handle a request, avoiding coupling between sender and receiver. Objects are linked together in a chain and pass the request along until one handles it.
Usage scenarios:
- Multi-condition flow control, permission management
- ERP system approval processes, CEO, HR manager, project manager
- Java filters implementation
Case 1:
public abstract class Approver {
protected String name;
protected Approver nextApprover;
public Approver(String name) {
this.name = name;
}
protected Approver setNextApprover(Approver nextApprover) {
this.nextApprover = nextApprover;
return this.nextApprover;
}
public abstract void approve(int amount);
}
public class Staff extends Approver {
public Staff(String name) {
super(name);
}
@Override
public void approve(int amount) {
if (amount <= 1000) {
System.out.println("Approved. [Staff: " + name + "]");
} else {
System.out.println("No authority, escalating. [Staff: " + name + "]");
this.nextApprover.approve(amount);
}
}
}
public class Manager extends Approver {
public Manager(String name) {
super(name);
}
@Override
public void approve(int amount) {
if (amount <= 5000) {
System.out.println("Approved. [Manager: " + name + "]");
} else {
System.out.println("No authority, escalating. [Manager: " + name + "]");
this.nextApprover.approve(amount);
}
}
}
public class CEO extends Approver {
public CEO(String name) {
super(name);
}
@Override
public void approve(int amount) {
if (amount <= 10000) {
System.out.println("Approved. [CEO: " + name + "]");
} else {
System.out.println("Rejected. [CEO: " + name + "]");
}
}
}
public class Client {
public static void main(String[] args) {
Approver flightJohn = new Staff("Zhang Fei");
flightJohn.setNextApprover(new Manager("Guan Yu")).setNextApprover(new CEO("Liu Bei"));
flightJohn.approve(1000);
flightJohn.approve(4000);
flightJohn.approve(9000);
flightJohn.approve(88000);
}
}
Case 2: Users participating in an event can receive prizes, but need to pass several rules before being allowed to take them, such as checking if the user is new, daily participation limits, overall participation limits, etc.
public abstract class RuleHandler {
protected RuleHandler successor;
public abstract void apply(Context context);
public void setSuccessor(RuleHandler successor) {
this.successor = successor;
}
public RuleHandler getSuccessor() {
return successor;
}
}
public class NewUserRuleHandler extends RuleHandler {
public void apply(Context context) {
if (context.isNewUser()) {
if (this.getSuccessor() != null) {
this.getSuccessor().apply(context);
}
} else {
throw new RuntimeException("This activity is only for new users");
}
}
}
public class LocationRuleHandler extends RuleHandler {
public void apply(Context context) {
boolean allowed = activityService.isSupportedLocation(context.getLocation);
if (allowed) {
if (this.getSuccessor() != null) {
this.getSuccessor().apply(context);
}
} else {
throw new RuntimeException("Sorry, your region cannot participate in this event");
}
}
}
public class LimitRuleHandler extends RuleHandler {
public void apply(Context context) {
int remainedTime = activityService.queryRemainedTimes(context);
if (remainedTimes > 0) {
if (this.getSuccessor() != null) {
this.getSuccessor().apply(context);
}
}
throw new RuntimeException("You're too late, prizes are all taken");
}
}
public static void main(String[] args) {
RuleHandler newUserHandler = new NewUserRuleHandler();
RuleHandler locationHandler = new LocationRuleHandler();
RuleHandler limitHandler = new LimitRuleHandler();
locationHandler.setSuccessor(limitHandler);
locationHandler.apply(context);
}
Chain of Responsibility Factory Enhancement
Maintain relationships in configuration files or enums.
public enum GatewayEnum {
API_HANDLER(new GatewayEntity(1, "API rate limiting",
"cn.dgut.design.chain_of_responsibility.Gateway.impl.ApiLimitGatewayHandler", null, 2)),
BLACKLIST_HANDLER(new GatewayEntity(2, "Blacklist blocking",
"cn.dgut.design.chain_of_responsibility.Gateway.impl.BlacklistGatewayHandler", 1, 3)),
SESSION_HANDLER(new GatewayEntity(3, "Session blocking",
"cn.dgut.design.chain_of_responsibility.Gateway.impl.SessionGatewayHandler", 2, null)),
;
GatewayEntity gatewayEntity;
public GatewayEntity getGatewayEntity() {
return gatewayEntity;
}
GatewayEnum(GatewayEntity gatewayEntity) {
this.gatewayEntity = gatewayEntity;
}
}
public class GatewayEntity {
private String name;
private String className;
private Integer handlerId;
private Integer preHandlerId;
private Integer nextHandlerId;
}
public interface GatewayDao {
GatewayEntity getGatewayEntity(Integer handlerId);
GatewayEntity getFirstGatewayEntity();
}
public class GatewayImpl implements GatewayDao {
private static Map<Integer, GatewayEntity> gatewayEntityMap = new HashMap<>();
static {
GatewayEnum[] values = GatewayEnum.values();
for (GatewayEnum value : values) {
GatewayEntity gatewayEntity = value.getGatewayEntity();
gatewayEntityMap.put(gatewayEntity.getHandlerId(), gatewayEntity);
}
}
@Override
public GatewayEntity getGatewayEntity(Integer handlerId) {
return gatewayEntityMap.get(handlerId);
}
@Override
public GatewayEntity getFirstGatewayEntity() {
for (Map.Entry<Integer, GatewayEntity> entry : gatewayEntityMap.entrySet()) {
GatewayEntity value = entry.getValue();
if (value.getPreHandlerId() == null) {
return value;
}
}
return null;
}
}
public class GatewayHandlerEnumFactory {
private static GatewayDao gatewayDao = new GatewayImpl();
public static GatewayHandler getFirstGatewayHandler() {
GatewayEntity firstGatewayEntity = gatewayDao.getFirstGatewayEntity();
GatewayHandler firstGatewayHandler = newGatewayHandler(firstGatewayEntity);
if (firstGatewayHandler == null) {
return null;
}
GatewayEntity tempGatewayEntity = firstGatewayEntity;
Integer nextHandlerId = null;
GatewayHandler tempGatewayHandler = firstGatewayHandler;
while ((nextHandlerId = tempGatewayEntity.getNextHandlerId()) != null) {
GatewayEntity gatewayEntity = gatewayDao.getGatewayEntity(nextHandlerId);
GatewayHandler gatewayHandler = newGatewayHandler(gatewayEntity);
tempGatewayHandler.setNext(gatewayHandler);
tempGatewayHandler = gatewayHandler;
tempGatewayEntity = gatewayEntity;
}
return firstGatewayHandler;
}
private static GatewayHandler newGatewayHandler(GatewayEntity firstGatewayEntity) {
String className = firstGatewayEntity.getClassName();
try {
Class<?> clazz = Class.forName(className);
return (GatewayHandler) clazz.newInstance();
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}
return null;
}
}
public class GatewayClient {
public static void main(String[] args) {
GatewayHandler firstGatewayHandler = GatewayHandlerEnumFactory.getFirstGatewayHandler();
firstGatewayHandler.service();
}
}
Observer Pattern
Also known as Publish/Subscribe pattern. Defines a one-to-many dependency between objects so that when one object changes state, all dependents are notified automatically.
public class Subject {
private List<Observer> observers = new ArrayList<>();
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
notifyAllObservers();
}
public void attach(Observer observer) {
observers.add(observer);
}
public void notifyAllObservers() {
for (Observer observer : observers) {
observer.update();
}
}
}
public abstract class Observer {
protected Subject subject;
public abstract void update();
}
public class BinaryObserver extends Observer {
public BinaryObserver(Subject subject) {
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update() {
String result = Integer.toBinaryString(subject.getState());
System.out.println("Data changed, binary value: " + result);
}
}
public class HexaObserver extends Observer {
public HexaObserver(Subject subject) {
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update() {
String result = Integer.toHexString(subject.getState()).toUpperCase();
System.out.println("Data changed, hex value: " + result);
}
}
public static void main(String[] args) {
Subject subject1 = new Subject();
new BinaryObserver(subject1);
new HexaObserver(subject1);
subject.setState(11);
}
Case 2:
public interface IHanFeiZi {
void haveBreakfast();
void haveFun();
}
public class HanFeiZi implements IHanFeiZi {
private ILiSi liSi = new LiSi();
private boolean isHavingBreakfast = false;
private boolean isHavingFun = false;
public void haveBreakfast() {
System.out.println("Han Feizi: eating breakfast...");
this.liSi.update("Han Feizi is eating");
}
public void haveFun() {
System.out.println("Han Feizi: having fun...");
this.liSi.update("Han Feizi is having fun");
}
public boolean isHavingBreakfast() { return isHavingBreakfast; }
public void setHavingBreakfast(boolean isHavingBreakfast) { this.isHavingBreakfast = isHavingBreakfast; }
public boolean isHavingFun() { return isHavingFun; }
public void setHavingFun(boolean isHavingFun) { this.isHavingFun = isHavingFun; }
}
public interface ILiSi {
void update(String context);
}
public class LiSi implements ILiSi {
public void update(String str) {
System.out.println("Li Si: observed Han Feizi's actions, reporting to Qin Shi Huang...");
this.reportToQinShiHuang(str);
System.out.println("Li Si: report complete...\n");
}
private void reportToQinShiHuang(String reportContext) {
System.out.println("Li Si: Report to Qin Shi Huang! Han Feizi is active-->" + reportContext);
}
}
class Spy extends Thread {
private HanFeiZi hanFeiZi;
private LiSi liSi;
private String type;
public Spy(HanFeiZi _hanFeiZi, LiSi _liSi, String _type) {
this.hanFeiZi = _hanFeiZi;
this.liSi = _liSi;
this.type = _type;
}
@Override
public void run() {
while (true) {
if (this.type.equals("breakfast")) {
if (this.hanFeiZi.isHavingBreakfast()) {
this.liSi.update("Han Feizi is eating");
this.hanFeiZi.setHavingBreakfast(false);
}
} else {
if (this.hanFeiZi.isHavingFun()) {
this.liSi.update("Han Feizi is having fun");
this.hanFeiZi.setHavingFun(false);
}
}
}
}
}
public class Client {
public static void main(String[] args) throws InterruptedException {
HanFeiZi hanFeiZi = new HanFeiZi();
hanFeiZi.haveBreakfast();
hanFeiZi.haveFun();
}
}
Case 3: Observers subscribe to topics they care about. When the subject's data changes, it notifies the observers. Observer patterns are often implemented with message middleware.
public class Subject {
private List<Observer> observers = new ArrayList<>();
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
notifyAllObservers();
}
public void attach(Observer observer) {
observers.add(observer);
}
public void notifyAllObservers() {
for (Observer observer : observers) {
observer.update();
}
}
}
public abstract class Observer {
protected Subject subject;
public abstract void update();
}
public class BinaryObserver extends Observer {
public BinaryObserver(Subject subject) {
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update() {
String result = Integer.toBinaryString(subject.getState());
System.out.println("Data changed, binary value: " + result);
}
}
public class HexaObserver extends Observer {
public HexaObserver(Subject subject) {
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update() {
String result = Integer.toHexString(subject.getState()).toUpperCase();
System.out.println("Data changed, hex value: " + result);
}
}
public static void main() {
Subject subject = new Subject();
new BinaryObserver(subject);
new HexaObserver(subject);
subject.setState(11);
}
Mediator Pattern
Uses a mediator object to encapsulate interactions between a set of objects. The mediator allows them to communicate without explicit references to each other, making them loosely coupled and easier to modify independently.
public class User {
private String name;
private ChatRoom chatRoom;
public User(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void login(ChatRoom chatRoom) {
chatRoom.connect(this);
this.chatRoom = chatRoom;
}
public void talk(String msg) {
chatRoom.sendMsg(this, msg);
}
public void listen(User fromWhom, String msg) {
System.out.print("[" + this.name + "'s dialog]");
System.out.println(fromWhom.getName() + " says: " + msg);
}
}
public class ChatRoom {
private String name;
private List<User> users = new ArrayList<>();
public ChatRoom(String name) {
this.name = name;
}
public void connect(User user) {
this.users.add(user);
System.out.print("Welcome ");
System.out.print(user.getName());
System.out.println(" to chat room " + this.name);
}
public void sendMsg(User fromWhom, String msg) {
users.stream()
.filter(user -> !user.equals(fromWhom))
.forEach(toWhom -> toWhom.listen(fromWhom, msg));
}
}
Observer Pattern Usage Example (Order Status Change Notification)
public class OrderEvent {
private String orderId;
private String oldStatus;
private String newStatus;
private LocalDateTime eventTime;
// Constructor, getters, setters
}
public interface OrderEventListener {
void onOrderStatusChange(OrderEvent event);
}
@Component
public class EmailNotificationListener implements OrderEventListener {
@Override
public void onOrderStatusChange(OrderEvent event) {
System.out.println("Sending email notification: " + event.getOrderId());
}
}
@Component
public class InventoryUpdateListener implements OrderEventListener {
@Override
public void onOrderStatusChange(OrderEvent event) {
if ("PAID".equals(event.getNewStatus())) {
System.out.println("Updating inventory for order: " + event.getOrderId());
}
}
}
@Component
public class PointsCalculationListener implements OrderEventListener {
@Override
public void onOrderStatusChange(OrderEvent event) {
if ("COMPLETED".equals(event.getNewStatus())) {
System.out.println("Calculating points for order: " + event.getOrderId());
}
}
}
@Service
public class OrderEventPublisher {
private final List<OrderEventListener> listeners = new ArrayList<>();
@Autowired
public OrderEventPublisher(List<OrderEventListener> listeners) {
this.listeners.addAll(listeners);
}
public void publishOrderStatusChange(OrderEvent event) {
listeners.forEach(listener -> {
try {
listener.onOrderStatusChange(event);
} catch (Exception e) {
System.err.println("Listener execution failed: " + e.getMessage());
}
});
}
}
@Service
public class OrderService {
@Autowired
private OrderEventPublisher eventPublisher;
@Transactional
public void updateOrderStatus(String orderId, String newStatus) {
String oldStatus = getCurrentStatus(orderId);
updateStatusInDB(orderId, newStatus);
OrderEvent event = new OrderEvent(orderId, oldStatus, newStatus, LocalDateTime.now());
eventPublisher.publishOrderStatusChange(event);
}
}
Memento Pattern
Captures an object's internal state without violating encapsulation, and saves this state outside the object so it can be restored later.
Case 1:
public class History {
private String body;
public History(String body) {
this.body = body;
}
public String getBody() {
return body;
}
}
public class Doc {
private String title;
private String body;
public Doc(String title) {
this.title = title;
this.body = "";
}
public void setTitle(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public History createHistory() {
return new History(body);
}
public void restoreHistory(History history) {
this.body = history.getBody();
}
}
public class Editor {
private Doc doc;
private List<History> historyRecords = new ArrayList<>();
private int historyPosition = -1;
public Editor(Doc doc) {
System.out.println("Opening document " + doc.getTitle());
this.doc = doc;
historyRecords.add(doc.createHistory());
historyPosition++;
show();
}
public void append(String txt) {
System.out.println("Inserting text");
doc.setBody(doc.getBody() + txt);
backup();
show();
}
public void save() {
System.out.println("Saving document");
}
public void delete() {
System.out.println("Deleting content");
doc.setBody("");
backup();
show();
}
private void backup() {
historyRecords.add(doc.createHistory());
historyPosition++;
}
private void show() {
System.out.println(doc.getBody());
System.out.println("Document end>>>\n");
}
public void undo() {
System.out.println("Undoing operation");
if (historyPosition == 0) {
return;
}
historyPosition--;
History history = historyRecords.get(historyPosition);
doc.restoreHistory(history);
show();
}
}
public class Author {
public static void main(String[] args) {
Editor editor = new Editor(new Doc("The Awakening of AI"));
editor.append("Chapter 1: Chaos Begins");
editor.append("\n 2000 words...");
editor.append("\nChapter 2: Desert Flower\n 3000 words...");
editor.delete();
editor.undo();
}
}
Case 2:
public class Boy {
private String state = "";
public void changeState() {
this.state = "mood might be bad";
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public Memento createMemento() {
return new Memento(this.state);
}
public void restoreMemento(Memento memento) {
this.setState(memento.getState());
}
}
public class Memento {
private String state = "";
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
public class Client {
public static void main(String[] args) {
Boy boy = new Boy();
boy.setState("feeling great!");
System.out.println("Current state: " + boy.getState());
Memento mem = boy.createMemento();
boy.changeState();
System.out.println("After pursuing girl: " + boy.getState());
boy.restoreMemento(mem);
System.out.println("Restored state: " + boy.getState());
}
}
State Pattern
Allows an object to alter its behavior when its internal state changes.
public interface State {
void doAction(Context context);
}
public class DeductState implements State {
public void doAction(Context context) {
System.out.println("Product sold, preparing to deduct inventory");
context.setState(this);
}
public String toString() {
return "Deduct State";
}
}
public class RevertState implements State {
public void doAction(Context context) {
System.out.println("Restocking product");
context.setState(this);
}
public String toString() {
return "Revert State";
}
}
public class Context {
private State state;
private String name;
public Context(String name) {
this.name = name;
}
public void setState(State state) {
this.state = state;
}
public State getState() {
return this.state;
}
}
public static void main(String[] args) {
Context context = new Context("iPhone X");
State revertState = new RevertState();
revertState.doAction(context);
State deductState = new DeductState();
deductState.doAction(context);
}
Strategy Pattern
Defines a family of algorithms, encapsulates each one, and makes them interchangeable.
Case 1:
public interface Strategy {
int calculate(int a, int b);
}
public class Addition implements Strategy {
@Override
public int calculate(int a, int b) {
return a + b;
}
}
public class Subtraction implements Strategy {
@Override
public int calculate(int a, int b) {
return a - b;
}
}
public class Calculator {
private Strategy strategy;
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public int getResult(int a, int b) {
return this.strategy.calculate(a, b);
}
}
public class Client {
public static void main(String[] args) {
Calculator calculator = new Calculator();
calculator.setStrategy(new Addition());
int result = calculator.getResult(1, 1);
System.out.println(result);
calculator.setStrategy(new Subtraction());
result = calculator.getResult(1, 1);
System.out.println(result);
}
}
Case 2:
public interface IStrategy {
void operate();
}
public class BackDoor implements IStrategy {
public void operate() {
System.out.println("Consulting Jiao Guo Lao to pressure Sun Quan");
}
}
public class GivenGreenLight implements IStrategy {
public void operate() {
System.out.println("Seeking Wu Guo Tai's green light");
}
}
public class BlockEnemy implements IStrategy {
public void operate() {
System.out.println("Sun夫人 blocking pursuers");
}
}
public class Context {
private IStrategy strategy;
public Context(IStrategy strategy) {
this.strategy = strategy;
}
public void operate() {
this.strategy.operate();
}
}
Case 3:
public interface MyPredicate<T> {
boolean test(T t);
}
public class FilterEmployeeByAge implements MyPredicate<Employee> {
@Override
public boolean test(Employee t) {
return t.getAge() >= 35;
}
}
public class FilterEmployeeBySalary implements MyPredicate<Employee> {
@Override
public boolean test(Employee t) {
return t.getSalary() >= 5000;
}
}
public List<Employee> filterEmployee(List<Employee> list, MyPredicate<Employee> mp) {
List<Employee> emps = new ArrayList<>();
for (Employee employee : list) {
if (mp.test(employee)) {
emps.add(employee);
}
}
return emps;
}
Spring Boot + Strategy Pattern:
public interface CalculationStrategy {
int operate(int num1, int num2);
}
@Component("add")
class AddCalculationStrategyImpl implements CalculationStrategy {
@Override
public int operate(int num1, int num2) {
return num1 + num2;
}
}
@Component("division")
class DivisionStrategyImpl implements CalculationStrategy {
@Override
public int operate(int num1, int num2) {
return num1 / num2;
}
}
@Component("multiple")
class MultiplicationStrategyImpl implements CalculationStrategy {
@Override
public int operate(int num1, int num2) {
return num1 * num2;
}
}
@Component("substract")
class SubtractionStrategyImpl implements CalculationStrategy {
@Override
public int operate(int num1, int num2) {
return num1 - num2;
}
}
@Component
class TestStrategyImpl implements CalculationStrategy {
@Override
public int operate(int num1, int num2) {
return num1 - num2;
}
}
@Component
public class CalculationFactory {
public final Map<String, CalculationStrategy> calculationStrategyMap = new HashMap<>();
public CalculationFactory(Map<String, CalculationStrategy> strategyMap) {
this.calculationStrategyMap.clear();
this.calculationStrategyMap.putAll(strategyMap);
}
public Map<String, CalculationStrategy> getCalculationStrategyMap() {
return calculationStrategyMap;
}
}
@Service
public class CalculationService {
@Autowired
private CalculationFactory calculationFactory;
public int operateByStrategy(String strategy, int num1, int num2) {
return calculationFactory.getCalculationStrategyMap().get(strategy).operate(num1, num2);
}
}
@RestController
@RequestMapping("/strategy")
public class TestStrategyController {
@Autowired
private CalculationService calculationService;
@GetMapping("/test/{operation}/{num1}/{num2}")
public int testCalculation(@PathVariable String operation,
@PathVariable int num1,
@PathVariable int num2) {
return calculationService.operateByStrategy(operation, num1, num2);
}
}
Strategy Pattern Usage Example (Multiple Discount Strategies)
public interface DiscountStrategy {
BigDecimal calculateDiscount(BigDecimal amount);
String getStrategyName();
}
@Component
public class PercentageDiscountStrategy implements DiscountStrategy {
private final BigDecimal discountRate = new BigDecimal("0.1");
@Override
public BigDecimal calculateDiscount(BigDecimal amount) {
return amount.multiply(discountRate);
}
@Override
public String getStrategyName() {
return "PERCENTAGE_DISCOUNT";
}
}
@Component
public class FixedAmountDiscountStrategy implements DiscountStrategy {
private final BigDecimal fixedAmount = new BigDecimal("50");
@Override
public BigDecimal calculateDiscount(BigDecimal amount) {
return fixedAmount.compareTo(amount) > 0 ? amount : fixedAmount;
}
@Override
public String getStrategyName() {
return "FIXED_AMOUNT_DISCOUNT";
}
}
@Service
public class DiscountContext {
private final Map<String, DiscountStrategy> strategyMap;
@Autowired
public DiscountContext(List<DiscountStrategy> strategies) {
strategyMap = strategies.stream()
.collect(Collectors.toMap(
DiscountStrategy::getStrategyName,
Function.identity()
));
}
public BigDecimal applyDiscount(String strategyName, BigDecimal amount) {
DiscountStrategy strategy = strategyMap.get(strategyName);
if (strategy == null) {
throw new IllegalArgumentException("Unknown discount strategy: " + strategyName);
}
return strategy.calculateDiscount(amount);
}
}
@RestController
public class OrderController {
@Autowired
private DiscountContext discountContext;
@PostMapping("/orders/calculate")
public BigDecimal calculateFinalAmount(@RequestBody OrderRequest request) {
BigDecimal discount = discountContext.applyDiscount(
request.getDiscountStrategy(),
request.getAmount()
);
return request.getAmount().subtract(discount);
}
}
Template Method Pattern
Case 1:
public abstract class Mammal {
protected final void feedMilk() {
if (female) {
System.out.println("Feeding milk");
} else {
System.out.println("Cannot feed milk");
}
}
public abstract void move();
}
public class Human extends Mammal {
@Override
public void move() {
System.out.println("Walking on two legs...");
}
}
Case 2:
public abstract class PM {
protected abstract void analyze();
protected abstract void design();
protected abstract void develop();
protected abstract boolean test();
protected abstract void release();
protected final void kickoff() {
analyze();
design();
develop();
test();
release();
}
}
public class AutoTestPM extends PM {
@Override
protected void analyze() {
System.out.println("Business communication, requirement analysis");
}
}
Case 3:
public abstract class AbstractTemplate {
public void templateMethod() {
init();
apply();
end();
}
protected void init() {
System.out.println("Init already implemented, subclass can override");
}
protected abstract void apply();
protected void end() {}
}
public class ConcreteTemplate extends AbstractTemplate {
public void apply() {
System.out.println("Subclass implements abstract method apply");
}
public void end() {
System.out.println("Using method3 as hook method, override when needed");
}
}
public static void main(String[] args) {
AbstractTemplate t = new ConcreteTemplate();
t.templateMethod();
}
Template Method Pattern Scenario (Data Export Process)
public abstract class DataExporter {
public final void export(String filePath) {
validateParameters();
List<Object> data = fetchData();
data = processData(data);
String formattedData = formatData(data);
writeToFile(formattedData, filePath);
postProcess();
}
protected void validateParameters() {
// General parameter validation
}
protected abstract List<Object> fetchData();
protected List<Object> processData(List<Object> data) {
return data;
}
protected abstract String formatData(List<Object> data);
protected void writeToFile(String data, String filePath) {
try (FileWriter writer = new FileWriter(filePath)) {
writer.write(data);
} catch (IOException e) {
throw new RuntimeException("File writing failed", e);
}
}
protected void postProcess() {
System.out.println("Export completed");
}
}
@Component
public class UserExporter extends DataExporter {
@Autowired
private UserRepository userRepository;
@Override
protected List<Object> fetchData() {
return new ArrayList<>(userRepository.findAll());
}
@Override
protected String formatData(List<Object> data) {
StringBuilder sb = new StringBuilder();
sb.append("ID,Username,Email\n");
for (Object obj : data) {
User user = (User) obj;
sb.append(user.getId()).append(",")
.append(user.getUsername()).append(",")
.append(user.getEmail()).append("\n");
}
return sb.toString();
}
}
@Component
public class OrderExporter extends DataExporter {
@Autowired
private OrderRepository orderRepository;
@Override
protected List<Object> fetchData() {
return new ArrayList<>(orderRepository.findRecentOrders());
}
@Override
protected String formatData(List<Object> data) {
JSONArray jsonArray = new JSONArray();
for (Object obj : data) {
Order order = (Order) obj;
JSONObject json = new JSONObject();
json.put("orderId", order.getId());
json.put("amount", order.getAmount());
json.put("status", order.getStatus());
jsonArray.put(json);
}
return jsonArray.toString();
}
}
Visitor Pattern
Encapsulates operations on elements of a data structure without changing the structure itself.
public interface IVisitor {
void visit(CommonEmployee commonEmployee);
void visit(Manager manager);
}
public class Visitor implements IVisitor {
public void visit(CommonEmployee commonEmployee) {
System.out.println(this.getCommonEmployee(commonEmployee));
}
public void visit(Manager manager) {
System.out.println(this.getManagerInfo(manager));
}
private String getBasicInfo(Employee employee) {
String info = "Name: " + employee.getName() + "\t";
info += "Gender: " + ((employee.getSex() == Employee.FEMALE) ? "Female" : "Male") + "\t";
info += "Salary: " + employee.getSalary() + "\t";
return info;
}
private String getManagerInfo(Manager manager) {
String basicInfo = this.getBasicInfo(manager);
String otherInfo = "Performance: " + manager.getPerformance() + "\t";
return basicInfo + otherInfo;
}
private String getCommonEmployee(CommonEmployee commonEmployee) {
String basicInfo = this.getBasicInfo(commonEmployee);
String otherInfo = "Work: " + commonEmployee.getJob() + "\t";
return basicInfo + otherInfo;
}
}
public abstract class Employee {
public final static int MALE = 0;
public final static int FEMALE = 1;
private String name;
private int salary;
private int sex;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getSalary() { return salary; }
public void setSalary(int salary) { this.salary = salary; }
public int getSex() { return sex; }
public void setSex(int sex) { this.sex = sex; }
public abstract void accept(IVisitor visitor);
}
public class CommonEmployee extends Employee {
private String job;
public String getJob() { return job; }
public void setJob(String job) { this.job = job; }
@Override
public void accept(IVisitor visitor) {
visitor.visit(this);
}
}
public class Manager extends Employee {
private String performance;
public String getPerformance() { return performance; }
public void setPerformance(String performance) { this.performance = performance; }
@Override
public void accept(IVisitor visitor) {
visitor.visit(this);
}
}
public class Client {
public static void main(String[] args) {
for (Employee emp : mockEmployee()) {
emp.accept(new Visitor());
}
}
public static List<Employee> mockEmployee() {
List<Employee> empList = new ArrayList<>();
CommonEmployee zhangSan = new CommonEmployee();
zhangSan.setJob("Writing Java programs, blue collar worker");
zhangSan.setName("Zhang San");
zhangSan.setSalary(1800);
zhangSan.setSex(Employee.MALE);
empList.add(zhangSan);
CommonEmployee liSi = new CommonEmployee();
liSi.setJob("Web designer, poor taste");
liSi.setName("Li Si");
liSi.setSalary(1900);
liSi.setSex(Employee.FEMALE);
empList.add(liSi);
Manager wangWu = new Manager();
wangWu.setName("Wang Wu");
wangWu.setPerformance("Negative performance, but good at flattery");
wangWu.setSalary(18750);
wangWu.setSex(Employee.MALE);
empList.add(wangWu);
return empList;
}
}
=======================================================
Structural Patterns
Adapter Pattern
Three variations: Default adapter, object adapter, and class adapter.
Converts one interface into another that clients expect, allowing incompatible classes to work together.
Default adapter: Example from Apache Commons IO FileAlterationListener
public interface FileAlterationListener {
void onStart(final FileAlterationObserver observer);
void onDirectoryCreate(final File directory);
void onDirectoryChange(final File directory);
void onDirectoryDelete(final File directory);
void onFileCreate(final File file);
void onFileChange(final File file);
void onFileDelete(final File file);
void onStop(final FileAlterationObserver observer);
}
public class FileAlterationListenerAdaptor implements FileAlterationListener {
public void onStart(final FileAlterationObserver observer) {}
public void onDirectoryCreate(final File directory) {}
public void onDirectoryChange(final File directory) {}
public void onDirectoryDelete(final File directory) {}
public void onFileCreate(final File file) {}
public void onFileChange(final File file) {}
public void onFileDelete(final File file) {}
public void onStop(final FileAlterationObserver observer) {}
}
public class FileMonitor extends FileAlterationListenerAdaptor {
public void onFileCreate(final File file) {
doSomething();
}
public void onFileDelete(final File file) {
doSomething();
}
}
Object adapter case 1:
public interface TriplePin {
void electrify(int l, int n, int e);
}
public class Adapter implements TriplePin {
private DualPin dualPinDevice;
public Adapter(DualPin dualPinDevice) {
this.dualPinDevice = dualPinDevice;
}
@Override
public void electrify(int l, int n, int e) {
dualPinDevice.electrify(l, n);
}
}
public interface DualPin {
void electrify(int l, int n);
}
public class TV implements DualPin {
@Override
public void electrify(int l, int n) {
System.out.println("Live wire: " + l);
System.out.println("Neutral wire: " + n);
}
}
Composite Pattern
Composes objects into tree structures to represent part-whole hierarchies, enabling consistent treatment of individual objects and compositions.
Case 1:
public interface Test {
int countTestCases();
void run(TestResult result);
}
public class TestSuite implements Test {
private Vector fTests = new Vector(10);
private String fName;
public void addTest(Test test) {
fTests.addElement(test);
}
public int countTestCases() {
int count = 0;
for (Enumeration e = tests(); e.hasMoreElements(); ) {
Test test = (Test)e.nextElement();
count = count + test.countTestCases();
}
return count;
}
public void run(TestResult result) {
for (Enumeration e = tests(); e.hasMoreElements(); ) {
if (result.shouldStop() )
break;
Test test = (Test)e.nextElement();
runTest(test, result);
}
}
public void runTest(Test test, TestResult result) {
test.run(result);
}
}
Case 2:
public abstract class Node {
protected String name;
public Node(String name) {
this.name = name;
}
protected abstract void add(Node child);
}
public class Folder extends Node {
private List<Node> childrenNodes = new ArrayList<>();
public Folder(String name) {
super(name);
}
@Override
protected void add(Node child) {
childrenNodes.add(child);
}
}
public class File extends Node {
public File(String name) {
super(name);
}
@Override
protected void add(Node child) {
System.out.println("Cannot add child nodes.");
}
}
public class Client {
public static void main(String[] args) {
Node driveD = new Folder("D Drive");
Node doc = new Folder("Documents");
doc.add(new File("resume.doc"));
doc.add(new File("project.ppt"));
driveD.add(doc);
Node music = new Folder("Music");
Node jay = new Folder("Jay Chou");
jay.add(new File("Double Section棍.mp3"));
jay.add(new File("Confession Balloon.mp3"));
jay.add(new File("Listen to Mom.mp3"));
Node jack = new Folder("Jackie Chan");
jack.add(new File("Kiss Goodbye.mp3"));
jack.add(new File("One Thousand Heartbreaks.mp3"));
music.add(jay);
music.add(jack);
driveD.add(music);
}
}
Case 3: Hierarchical data structures where we treat individual objects and compositions consistently.
public class Employee {
private String name;
private String dept;
private int salary;
private List<Employee> subordinates;
public Employee(String name, String dept, int sal) {
this.name = name;
this.dept = dept;
this.salary = sal;
subordinates = new ArrayList<>();
}
public void add(Employee e) {
subordinates.add(e);
}
public void remove(Employee e) {
subordinates.remove(e);
}
public List<Employee> getSubordinates() {
return subordinates;
}
}
Proxy Pattern
Provides a surrogate or placeholder for another object to control access to it.
public interface FoodService {
Food makeChicken();
Food makeNoodle();
}
public class FoodServiceImpl implements FoodService {
public Food makeChicken() {
Food f = new Chicken();
f.setChicken("1kg");
f.setSpicy("1g");
f.setSalt("3g");
return f;
}
public Food makeNoodle() {
Food f = new Noodle();
f.setNoodle("500g");
f.setSalt("5g");
return f;
}
}
public class FoodServiceProxy implements FoodService {
private FoodService foodService = new FoodServiceImpl();
public Food makeChicken() {
System.out.println("We're about to make chicken");
Food food = foodService.makeChicken();
System.out.println("Chicken finished, adding pepper");
food.addCondiment("pepper");
return food;
}
public Food makeNoodle() {
System.out.println("Preparing noodles~");
Food food = foodService.makeNoodle();
System.out.println("Noodles ready");
return food;
}
}
Bridge Pattern
Decouples abstraction from implementation, allowing both to vary independently.
public interface DrawAPI {
void draw(int radius, int x, int y);
}
public class RedPen implements DrawAPI {
@Override
public void draw(int radius, int x, int y) {
System.out.println("Drawing with red pen, radius:" + radius + ", x:" + x + ", y:" + y);
}
}
public class GreenPen implements DrawAPI {
@Override
public void draw(int radius, int x, int y) {
System.out.println("Drawing with green pen, radius:" + radius + ", x:" + x + ", y:" + y);
}
}
public class BluePen implements DrawAPI {
@Override
public void draw(int radius, int x, int y) {
System.out.println("Drawing with blue pen, radius:" + radius + ", x:" + x + ", y:" + y);
}
}
public abstract class Shape {
protected DrawAPI drawAPI;
protected Shape(DrawAPI drawAPI) {
this.drawAPI = drawAPI;
}
public abstract void draw();
}
public class Circle extends Shape {
private int radius;
public Circle(int radius, DrawAPI drawAPI) {
super(drawAPI);
this.radius = radius;
}
public void draw() {
drawAPI.draw(radius, 0, 0);
}
}
public class Rectangle extends Shape {
private int x;
private int y;
public Rectangle(int x, int y, DrawAPI drawAPI) {
super(drawAPI);
this.x = x;
this.y = y;
}
public void draw() {
drawAPI.draw(0, x, y);
}
}
public static void main(String[] args) {
Shape greenCircle = new Circle(10, new GreenPen());
Shape redRectangle = new Rectangle(4, 8, new RedPen());
greenCircle.draw();
redRectangle.draw();
}
Decorator Pattern
Dynamically adds responsibilities to objects. More flexible than subclassing.
Case 1:
public interface Showable {
void show();
}
public class Girl implements Showable {
@Override
public void show() {
System.out.print("Girl's natural face");
}
}
public abstract class Decorator implements Showable {
protected Showable showable;
public Decorator(Showable showable) {
this.showable = showable;
}
@Override
public void show() {
showable.show();
}
}
public class FoundationMakeup extends Decorator {
public FoundationMakeup(Showable showable) {
super(showable);
}
@Override
public void show() {
System.out.print("Apply foundation(");
showable.show();
System.out.print(")");
}
}
public class Lipstick extends Decorator {
public Lipstick(Showable showable) {
super(showable);
}
@Override
public void show() {
System.out.print("Apply lipstick(");
showable.show();
System.out.print(")");
}
}
public class Client {
public static void main(String[] args) {
Showable madeupGirl = new Lipstick(new FoundationMakeup(new Girl()));
madeupGirl.show();
}
}
Case 2:
public abstract class SchoolReport {
public abstract void report();
public abstract void sign();
}
public class FouthGradeSchoolReport extends SchoolReport {
public void report() {
System.out.println("Dear XXX parent:");
System.out.println("......");
System.out.println("Chinese 62 Math 65 PE 98 Science 63");
System.out.println(".......");
System.out.println("Parent signature: ");
}
public void sign(String name) {
System.out.println("Parent signature: " + name);
}
}
public class SugarFouthGradeSchoolReport extends FouthGradeSchoolReport {
private void reportHighScore() {
System.out.println("Highest scores: Chinese 75, Math 78, Science 80");
}
private void reportSort() {
System.out.println("Ranking 38th...");
}
@Override
public void report() {
this.reportHighScore();
super.report();
this.reportSort();
}
}
public class Father {
public static void main(String[] args) {
SchoolReport sr = new SugarFouthGradeSchoolReport();
sr.report();
}
}
Case 3:
public abstract class Beverage {
public abstract String getDescription();
public abstract double cost();
}
public class BlackTea extends Beverage {
public String getDescription() {
return "Black tea";
}
public double cost() {
return 10;
}
}
public class GreenTea extends Beverage {
public String getDescription() {
return "Green tea";
}
public double cost() {
return 11;
}
}
public abstract class Condiment extends Beverage {
}
public class Lemon extends Condiment {
private Beverage beverage;
public Lemon(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ", add lemon";
}
public double cost() {
return beverage.cost() + 2;
}
}
public static void main(String[] args) {
Beverage beverage = new GreenTea();
beverage = new Lemon(beverage);
beverage = new Mango(beverage);
}
Decorator Pattern Usage Example (Caching Enhanced Data Service)
public interface UserService {
User getUserById(Long id);
void saveUser(User user);
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Override
public User getUserById(Long id) {
return userRepository.findById(id).orElse(null);
}
@Override
public void saveUser(User user) {
userRepository.save(user);
}
}
public abstract class UserServiceDecorator implements UserService {
protected UserService decoratedUserService;
public UserServiceDecorator(UserService userService) {
this.decoratedUserService = userService;
}
@Override
public User getUserById(Long id) {
return decoratedUserService.getUserById(id);
}
@Override
public void saveUser(User user) {
decoratedUserService.saveUser(user);
}
}
@Component
public class CachingUserServiceDecorator extends UserServiceDecorator {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public CachingUserServiceDecorator(UserService userService) {
super(userService);
}
@Override
public User getUserById(Long id) {
String cacheKey = "user:" + id;
User user = (User) redisTemplate.opsForValue().get(cacheKey);
if (user == null) {
user = decoratedUserService.getUserById(id);
if (user != null) {
redisTemplate.opsForValue().set(cacheKey, user, Duration.ofMinutes(30));
}
}
return user;
}
@Override
public void saveUser(User user) {
decoratedUserService.saveUser(user);
String cacheKey = "user:" + user.getId();
redisTemplate.opsForValue().set(cacheKey, user, Duration.ofMinutes(30));
}
}
@Configuration
public class ServiceConfig {
@Autowired
private UserRepository userRepository;
@Bean
@Primary
public UserService userService() {
UserService basicService = new UserServiceImpl(userRepository);
return new CachingUserServiceDecorator(basicService);
}
}
Facade Pattern
Provides a unified interface to a set of interfaces in a subsystem, making the subsystem easier to use.
Case 1:
public class VegVendor {
public void sell() {
System.out.println("Veg vendor selling vegetables...");
}
}
public class GirlFriend {
public void cook() {
System.out.println("Girlfriend cooking...");
}
}
public class Me {
public void eat() {
System.out.println("I just eat...");
}
public static void main(String[] args) {
VegVendor vv = new VegVendor();
vv.sell();
GirlFriend gf = new GirlFriend();
gf.cook();
Me me = new Me();
me.eat();
}
}
public class Facade {
private VegVendor vv;
private Chef chef;
private Waiter waiter;
private Cleaner cleaner;
public Facade() {
this.vv = new VegVendor();
vv.sell();
this.chef = new Chef();
this.waiter = new Waiter();
this.cleaner = new Cleaner();
}
public void provideService() {
waiter.order();
chef.cook();
waiter.serve();
cleaner.clean();
cleaner.wash();
}
}
Case 2:
public interface Shape {
void draw();
}
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Circle::draw()");
}
}
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Rectangle::draw()");
}
}
public class ShapeMaker {
private Shape circle;
private Shape rectangle;
private Shape square;
public ShapeMaker() {
circle = new Circle();
rectangle = new Rectangle();
square = new Square();
}
public void drawCircle() {
circle.draw();
}
public void drawRectangle() {
rectangle.draw();
}
public void drawSquare() {
square.draw();
}
}
public static void main(String[] args) {
ShapeMaker shapeMaker = new ShapeMaker();
shapeMaker.drawCircle();
shapeMaker.drawRectangle();
shapeMaker.drawSquare();
}
Flyweight Pattern
Uses sharing to support large numbers of fine-grained objects efficiently.
public class SignInfo {
private String id;
private String location;
private String subject;
private String postAddress;
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getLocation() { return location; }
public void setLocation(String location) { this.location = location; }
public String getSubject() { return subject; }
public void setSubject(String subject) { this.subject = subject; }
public String getPostAddress() { return postAddress; }
public void setPostAddress(String postAddress) { this.postAddress = postAddress; }
}
public class SignInfo4Pool extends SignInfo {
private String key;
public SignInfo4Pool(String _key) {
this.key = _key;
}
public String getKey() { return key; }
public void setKey(String key) { this.key = key; }
}
public class SignInfoFactory {
private static HashMap pool = new HashMap();
public static SignInfo getSignInfo(String key) {
SignInfo result = null;
if (!pool.containsKey(key)) {
System.out.println(key + "----creating object and placing in pool");
result = new SignInfo4Pool(key);
pool.put(key, result);
} else {
result = pool.get(key);
System.out.println(key + "---taking directly from pool");
}
return result;
}
}
public class Client {
public static void main(String[] args) {
for (int i = 0; i < 4; i++) {
String subject = "Subject" + i;
for (int j = 0; j < 30; j++) {
String key = subject + "Exam Location" + j;
SignInfoFactory.getSignInfo(key);
}
}
SignInfo signInfo = SignInfoFactory.getSignInfo("Subject1Exam Location1");
}
}