Java 中的特殊情况模式:使用预定义情况简化异常处理
也称为
- 特殊情况
特殊情况设计模式的意图
Java 中的特殊情况设计模式为解决软件开发中独特或特殊情况提供了一个健壮的框架,而不会使主代码库复杂化。
特殊情况模式的详细解释,并结合现实世界示例
现实世界示例
考虑高速公路上的收费站系统。通常,车辆通过收费站,系统根据车辆类型收取通行费。但是,有一些特殊情况:紧急车辆(如救护车和消防车)不应该收费。
例如,在收费管理系统中,特殊情况模式可以为紧急车辆提供单独的处理方式,确保在没有额外检查的情况下实现简化的收费流程。紧急车辆类将覆盖通行费计算方法以确保不收取任何费用,封装这种特殊行为,而不会使主要的通行费计算逻辑充斥着条件检查。这保持了代码库的整洁,并确保一致地处理特殊情况。
用简单的语言来说
特殊情况设计模式封装并隔离了异常情况和特定场景,以简化主代码逻辑并提高可维护性。
在 企业应用架构模式 中,Martin Fowler 说
如果你能原谅这个无法抗拒的双关语,我认为 空对象 是特殊情况的一个特例。
Java 中特殊情况模式的编程示例
特殊情况模式是一种软件设计模式,用于单独处理代码中特定且通常不常见的案例,而不是一般案例。当类具有需要根据其状态进行条件逻辑处理的行为时,此模式很有用。我们可以在子类中封装特殊行为,而不是使类充斥着条件逻辑。
在电子商务系统中,表示层依赖于应用层来生成特定视图模型。在成功的场景中,收据视图模型将包括实际的购买数据以及一些失败场景。
Db
类是一个单例,它为用户、帐户和产品保存数据。它提供将数据播种到数据库和在数据库中查找数据的方法。
@RequiredArgsConstructor
@Getter
public class Db {
// Singleton instance of Db
private static Db instance;
// Maps to hold data
private Map<String, User> userName2User;
private Map<User, Account> user2Account;
private Map<String, Product> itemName2Product;
// Singleton method to get instance of Db
public static synchronized Db getInstance() {
if (instance == null) {
Db newInstance = new Db();
newInstance.userName2User = new HashMap<>();
newInstance.user2Account = new HashMap<>();
newInstance.itemName2Product = new HashMap<>();
instance = newInstance;
}
return instance;
}
// Methods to seed data into Db
public void seedUser(String userName, Double amount) { /*...*/ }
public void seedItem(String itemName, Double price) { /*...*/ }
// Methods to find data in Db
public User findUserByUserName(String userName) { /*...*/ }
public Account findAccountByUser(User user) { /*...*/ }
public Product findProductByItemName(String itemName) { /*...*/ }
}
接下来,以下是表示层、收据视图模型接口及其成功场景的实现。
public interface ReceiptViewModel {
void show();
}
@RequiredArgsConstructor
@Getter
public class ReceiptDto implements ReceiptViewModel {
private static final Logger LOGGER = LoggerFactory.getLogger(ReceiptDto.class);
private final Double price;
@Override
public void show() {
LOGGER.info(String.format("Receipt: %s paid", price));
}
}
以下是失败场景的实现,它们是特殊情况。
public class DownForMaintenance implements ReceiptViewModel {
private static final Logger LOGGER = LoggerFactory.getLogger(DownForMaintenance.class);
@Override
public void show() {
LOGGER.info("Down for maintenance");
}
}
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);
}
}
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");
}
}
public class InsufficientFunds implements ReceiptViewModel {
private static final Logger LOGGER = LoggerFactory.getLogger(InsufficientFunds.class);
private String userName;
private Double amount;
private String itemName;
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);
}
}
以下是 App
及其执行不同场景的 main
函数。
public class App {
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
private static final String LOGGER_STRING = "[REQUEST] User: {} buy product: {}";
private static final String TEST_USER_1 = "ignite1771";
private static final String TEST_USER_2 = "abc123";
private static final String ITEM_TV = "tv";
private static final String ITEM_CAR = "car";
private static final String ITEM_COMPUTER = "computer";
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(TEST_USER_1, 1000.0);
Db.getInstance().seedItem(ITEM_COMPUTER, 800.0);
Db.getInstance().seedItem(ITEM_CAR, 20000.0);
final var applicationServices = new ApplicationServicesImpl();
ReceiptViewModel receipt;
LOGGER.info(LOGGER_STRING, TEST_USER_2, ITEM_TV);
receipt = applicationServices.loggedInUserPurchase(TEST_USER_2, ITEM_TV);
receipt.show();
MaintenanceLock.getInstance().setLock(false);
LOGGER.info(LOGGER_STRING, TEST_USER_2, ITEM_TV);
receipt = applicationServices.loggedInUserPurchase(TEST_USER_2, ITEM_TV);
receipt.show();
LOGGER.info(LOGGER_STRING, TEST_USER_1, ITEM_TV);
receipt = applicationServices.loggedInUserPurchase(TEST_USER_1, ITEM_TV);
receipt.show();
LOGGER.info(LOGGER_STRING, TEST_USER_1, ITEM_CAR);
receipt = applicationServices.loggedInUserPurchase(TEST_USER_1, ITEM_CAR);
receipt.show();
LOGGER.info(LOGGER_STRING, TEST_USER_1, ITEM_COMPUTER);
receipt = applicationServices.loggedInUserPurchase(TEST_USER_1, ITEM_COMPUTER);
receipt.show();
}
}
以下是运行示例的输出。
11:23:48.669 [main] INFO com.iluwatar.specialcase.App -- Db seeding: 1 user: {"ignite1771", amount = 1000.0}, 2 products: {"computer": price = 800.0, "car": price = 20000.0}
11:23:48.672 [main] INFO com.iluwatar.specialcase.App -- [REQUEST] User: abc123 buy product: tv
11:23:48.672 [main] INFO com.iluwatar.specialcase.DownForMaintenance -- Down for maintenance
11:23:48.672 [main] INFO com.iluwatar.specialcase.MaintenanceLock -- Maintenance lock is set to: false
11:23:48.672 [main] INFO com.iluwatar.specialcase.App -- [REQUEST] User: abc123 buy product: tv
11:23:48.673 [main] INFO com.iluwatar.specialcase.InvalidUser -- Invalid user: abc123
11:23:48.674 [main] INFO com.iluwatar.specialcase.App -- [REQUEST] User: ignite1771 buy product: tv
11:23:48.674 [main] INFO com.iluwatar.specialcase.OutOfStock -- Out of stock: tv for user = ignite1771 to buy
11:23:48.674 [main] INFO com.iluwatar.specialcase.App -- [REQUEST] User: ignite1771 buy product: car
11:23:48.676 [main] INFO com.iluwatar.specialcase.InsufficientFunds -- Insufficient funds: 1000.0 of user: ignite1771 for buying item: car
11:23:48.676 [main] INFO com.iluwatar.specialcase.App -- [REQUEST] User: ignite1771 buy product: computer
11:23:48.676 [main] INFO com.iluwatar.specialcase.ReceiptDto -- Receipt: 800.0 paid
总之,特殊情况模式通过将特殊情况与一般情况分开,有助于使代码保持整洁且易于理解。它还促进了代码重用,并使代码更易于维护。
何时在 Java 中使用特殊情况模式
- 当你想封装和处理特殊情况或错误条件时使用,方法是避免在主代码库中散布条件逻辑。
- 在某些操作具有已知特殊情况(需要不同处理方式)的场景中很有用。
Java 中特殊情况模式的实际应用
- 实现空对象模式以避免空检查。
- 在电子商务应用程序中处理特定的业务规则或验证逻辑。
- 在数据处理应用程序中管理不同的文件格式或协议。
特殊情况模式的优点和权衡
优点
采用特殊情况设计模式
- 通过从核心算法中移除特殊情况处理来简化主逻辑。
- 通过隔离特殊情况来提高代码可读性和可维护性。
权衡
- 可能会引入额外的类或接口,从而增加系统中组件的数量。
- 需要仔细设计以确保正确封装特殊情况,并且不会引入意外行为。