* #1317 Add Special Case Pattern To focus on pattern itself, I implement DB and maintenance lock by the singleton instance. * #1317 Add special cases unit tests Assert the logger output (ref: https://stackoverflow.com/a/52229629) * #1317 Add README.md Add Special Case Pattern README * #1317 Format: add a new line to end of file Co-authored-by: Subhrodip Mohanta <subhrodipmohanta@gmail.com>
This commit is contained in:
47
special-case/src/main/java/com/iluwatar/specialcase/App.java
Normal file
47
special-case/src/main/java/com/iluwatar/specialcase/App.java
Normal file
@ -0,0 +1,47 @@
|
||||
package com.iluwatar.specialcase;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* <p>The Special Case Pattern is a software design pattern that encapsulates particular cases
|
||||
* into subclasses that provide special behaviors.</p>
|
||||
*
|
||||
* <p>In this example ({@link ReceiptViewModel}) encapsulates all particular cases.</p>
|
||||
*/
|
||||
public class App {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
|
||||
|
||||
/**
|
||||
* Program entry point.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
// DB seeding
|
||||
LOGGER.info("Db seeding: " + "1 user: {\"ignite1771\", amount = 1000.0}, "
|
||||
+ "2 products: {\"computer\": price = 800.0, \"car\": price = 20000.0}");
|
||||
Db.getInstance().seedUser("ignite1771", 1000.0);
|
||||
Db.getInstance().seedItem("computer", 800.0);
|
||||
Db.getInstance().seedItem("car", 20000.0);
|
||||
|
||||
final var applicationServices = new ApplicationServicesImpl();
|
||||
ReceiptViewModel receipt;
|
||||
|
||||
LOGGER.info("[REQUEST] User: " + "abc123" + " buy product: " + "tv");
|
||||
receipt = applicationServices.loggedInUserPurchase("abc123", "tv");
|
||||
receipt.show();
|
||||
MaintenanceLock.getInstance().setLock(false);
|
||||
LOGGER.info("[REQUEST] User: " + "abc123" + " buy product: " + "tv");
|
||||
receipt = applicationServices.loggedInUserPurchase("abc123", "tv");
|
||||
receipt.show();
|
||||
LOGGER.info("[REQUEST] User: " + "ignite1771" + " buy product: " + "tv");
|
||||
receipt = applicationServices.loggedInUserPurchase("ignite1771", "tv");
|
||||
receipt.show();
|
||||
LOGGER.info("[REQUEST] User: " + "ignite1771" + " buy product: " + "car");
|
||||
receipt = applicationServices.loggedInUserPurchase("ignite1771", "car");
|
||||
receipt.show();
|
||||
LOGGER.info("[REQUEST] User: " + "ignite1771" + " buy product: " + "computer");
|
||||
receipt = applicationServices.loggedInUserPurchase("ignite1771", "computer");
|
||||
receipt.show();
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package com.iluwatar.specialcase;
|
||||
|
||||
public interface ApplicationServices {
|
||||
|
||||
ReceiptViewModel loggedInUserPurchase(String userName, String itemName);
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.iluwatar.specialcase;
|
||||
|
||||
public class ApplicationServicesImpl implements ApplicationServices {
|
||||
|
||||
private DomainServicesImpl domain = new DomainServicesImpl();
|
||||
|
||||
@Override
|
||||
public ReceiptViewModel loggedInUserPurchase(String userName, String itemName) {
|
||||
if (isDownForMaintenance()) {
|
||||
return new DownForMaintenance();
|
||||
}
|
||||
return this.domain.purchase(userName, itemName);
|
||||
}
|
||||
|
||||
private boolean isDownForMaintenance() {
|
||||
return MaintenanceLock.getInstance().isLock();
|
||||
}
|
||||
}
|
150
special-case/src/main/java/com/iluwatar/specialcase/Db.java
Normal file
150
special-case/src/main/java/com/iluwatar/specialcase/Db.java
Normal file
@ -0,0 +1,150 @@
|
||||
package com.iluwatar.specialcase;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class Db {
|
||||
|
||||
private static Db instance;
|
||||
private Map<String, User> userName2User;
|
||||
private Map<User, Account> user2Account;
|
||||
private Map<String, Product> itemName2Product;
|
||||
|
||||
/**
|
||||
* Get the instance of Db.
|
||||
*
|
||||
* @return singleton instance of Db class
|
||||
*/
|
||||
public static Db getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (Db.class) {
|
||||
if (instance == null) {
|
||||
instance = new Db();
|
||||
instance.userName2User = new HashMap<>();
|
||||
instance.user2Account = new HashMap<>();
|
||||
instance.itemName2Product = new HashMap<>();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Seed a user into Db.
|
||||
*
|
||||
* @param userName of the user
|
||||
* @param amount of the user's account
|
||||
*/
|
||||
public void seedUser(String userName, Double amount) {
|
||||
User user = new User(userName);
|
||||
instance.userName2User.put(userName, user);
|
||||
Account account = new Account(amount);
|
||||
instance.user2Account.put(user, account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Seed an item into Db.
|
||||
*
|
||||
* @param itemName of the item
|
||||
* @param price of the item
|
||||
*/
|
||||
public void seedItem(String itemName, Double price) {
|
||||
Product item = new Product(price);
|
||||
itemName2Product.put(itemName, item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a user with the userName.
|
||||
*
|
||||
* @param userName of the user
|
||||
* @return instance of User
|
||||
*/
|
||||
public User findUserByUserName(String userName) {
|
||||
if (!userName2User.containsKey(userName)) {
|
||||
return null;
|
||||
}
|
||||
return userName2User.get(userName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an account of the user.
|
||||
*
|
||||
* @param user in Db
|
||||
* @return instance of Account of the user
|
||||
*/
|
||||
public Account findAccountByUser(User user) {
|
||||
if (!user2Account.containsKey(user)) {
|
||||
return null;
|
||||
}
|
||||
return user2Account.get(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a product with the itemName.
|
||||
*
|
||||
* @param itemName of the item
|
||||
* @return instance of Product
|
||||
*/
|
||||
public Product findProductByItemName(String itemName) {
|
||||
if (!itemName2Product.containsKey(itemName)) {
|
||||
return null;
|
||||
}
|
||||
return itemName2Product.get(itemName);
|
||||
}
|
||||
|
||||
public class User {
|
||||
|
||||
private String userName;
|
||||
|
||||
public User(String userName) {
|
||||
this.userName = userName;
|
||||
}
|
||||
|
||||
public String getUserName() {
|
||||
return userName;
|
||||
}
|
||||
|
||||
public ReceiptDto purchase(Product item) {
|
||||
return new ReceiptDto(item.getPrice());
|
||||
}
|
||||
}
|
||||
|
||||
public class Account {
|
||||
|
||||
private Double amount;
|
||||
|
||||
public Account(Double amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Withdraw the price of the item from the account.
|
||||
*
|
||||
* @param price of the item
|
||||
* @return instance of MoneyTransaction
|
||||
*/
|
||||
public MoneyTransaction withdraw(Double price) {
|
||||
if (price > amount) {
|
||||
return null;
|
||||
}
|
||||
return new MoneyTransaction(amount, price);
|
||||
}
|
||||
|
||||
public Double getAmount() {
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
|
||||
public class Product {
|
||||
|
||||
private Double price;
|
||||
|
||||
public Product(Double price) {
|
||||
this.price = price;
|
||||
}
|
||||
|
||||
public Double getPrice() {
|
||||
return price;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
package com.iluwatar.specialcase;
|
||||
|
||||
public interface DomainServices {
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package com.iluwatar.specialcase;
|
||||
|
||||
public class DomainServicesImpl implements DomainServices {
|
||||
|
||||
/**
|
||||
* Domain purchase with userName and itemName, with validation for userName.
|
||||
*
|
||||
* @param userName of the user
|
||||
* @param itemName of the item
|
||||
* @return instance of ReceiptViewModel
|
||||
*/
|
||||
public ReceiptViewModel purchase(String userName, String itemName) {
|
||||
Db.User user = Db.getInstance().findUserByUserName(userName);
|
||||
if (user == null) {
|
||||
return new InvalidUser(userName);
|
||||
}
|
||||
|
||||
Db.Account account = Db.getInstance().findAccountByUser(user);
|
||||
return purchase(user, account, itemName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Domain purchase with user, account and itemName,
|
||||
* with validation for whether product is out of stock
|
||||
* and whether user has insufficient funds in the account.
|
||||
*
|
||||
* @param user in Db
|
||||
* @param account in Db
|
||||
* @param itemName of the item
|
||||
* @return instance of ReceiptViewModel
|
||||
*/
|
||||
private ReceiptViewModel purchase(Db.User user, Db.Account account, String itemName) {
|
||||
Db.Product item = Db.getInstance().findProductByItemName(itemName);
|
||||
if (item == null) {
|
||||
return new OutOfStock(user.getUserName(), itemName);
|
||||
}
|
||||
|
||||
ReceiptDto receipt = user.purchase(item);
|
||||
MoneyTransaction transaction = account.withdraw(receipt.getPrice());
|
||||
if (transaction == null) {
|
||||
return new InsufficientFunds(user.getUserName(), account.getAmount(), itemName);
|
||||
}
|
||||
|
||||
return receipt;
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package com.iluwatar.specialcase;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class DownForMaintenance implements ReceiptViewModel {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DownForMaintenance.class);
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
LOGGER.info("Down for maintenance");
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package com.iluwatar.specialcase;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class InsufficientFunds implements ReceiptViewModel {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(InsufficientFunds.class);
|
||||
|
||||
private String userName;
|
||||
private Double amount;
|
||||
private String itemName;
|
||||
|
||||
/**
|
||||
* Constructor of InsufficientFunds.
|
||||
*
|
||||
* @param userName of the user
|
||||
* @param amount of the user's account
|
||||
* @param itemName of the item
|
||||
*/
|
||||
public InsufficientFunds(String userName, Double amount, String itemName) {
|
||||
this.userName = userName;
|
||||
this.amount = amount;
|
||||
this.itemName = itemName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
LOGGER.info("Insufficient funds: " + amount + " of user: " + userName
|
||||
+ " for buying item: " + itemName);
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package com.iluwatar.specialcase;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class InvalidUser implements ReceiptViewModel {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(InvalidUser.class);
|
||||
|
||||
private final String userName;
|
||||
|
||||
public InvalidUser(String userName) {
|
||||
this.userName = userName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
LOGGER.info("Invalid user: " + userName);
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package com.iluwatar.specialcase;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class MaintenanceLock {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(MaintenanceLock.class);
|
||||
|
||||
private static MaintenanceLock instance;
|
||||
private boolean lock = true;
|
||||
|
||||
/**
|
||||
* Get the instance of MaintenanceLock.
|
||||
*
|
||||
* @return singleton instance of MaintenanceLock
|
||||
*/
|
||||
public static MaintenanceLock getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (MaintenanceLock.class) {
|
||||
if (instance == null) {
|
||||
instance = new MaintenanceLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public boolean isLock() {
|
||||
return lock;
|
||||
}
|
||||
|
||||
public void setLock(boolean lock) {
|
||||
this.lock = lock;
|
||||
LOGGER.info("Maintenance lock is set to: " + lock);
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package com.iluwatar.specialcase;
|
||||
|
||||
public class MoneyTransaction {
|
||||
|
||||
private Double amount;
|
||||
private Double price;
|
||||
|
||||
public MoneyTransaction(Double amount, Double price) {
|
||||
this.amount = amount;
|
||||
this.price = price;
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package com.iluwatar.specialcase;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class OutOfStock implements ReceiptViewModel {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(OutOfStock.class);
|
||||
|
||||
private String userName;
|
||||
private String itemName;
|
||||
|
||||
public OutOfStock(String userName, String itemName) {
|
||||
this.userName = userName;
|
||||
this.itemName = itemName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
LOGGER.info("Out of stock: " + itemName + " for user = " + userName + " to buy");
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package com.iluwatar.specialcase;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ReceiptDto implements ReceiptViewModel {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ReceiptDto.class);
|
||||
|
||||
private Double price;
|
||||
|
||||
public ReceiptDto(Double price) {
|
||||
this.price = price;
|
||||
}
|
||||
|
||||
public Double getPrice() {
|
||||
return price;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
LOGGER.info("Receipt: " + price + " paid");
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package com.iluwatar.specialcase;
|
||||
|
||||
public interface ReceiptViewModel {
|
||||
|
||||
void show();
|
||||
}
|
Reference in New Issue
Block a user