Java 中的规格模式:使用解耦逻辑增强业务规则
也称为
- 过滤器
- 条件
规格设计模式的意图
封装业务规则和对象必须满足的条件,以便在应用程序的各个部分检查这些规则。
规格模式的详细说明及真实世界示例
真实世界示例
想象一下,您正在组织一个会议,需要根据特定条件(例如注册状态、付款完成和会议兴趣)过滤与会者。
使用规格设计模式,您可以为每个条件创建单独的规格(例如,“已注册”、“已付款”、“对会议 X 感兴趣”)。这些规格可以动态组合,以过滤满足所有所需条件的与会者,例如已注册、已完成付款且对特定会议感兴趣的与会者。这种方法允许灵活且可重用的业务规则,确保可以根据需要轻松调整过滤逻辑,而无需更改底层与会者对象。
简单来说
Java 中的规格设计模式可以有效地封装和重用业务规则,提供一种灵活且动态的方式来组合条件,从而实现健壮的软件开发。
维基百科说
在计算机编程中,规格模式是一种特定的软件设计模式,其中业务规则可以通过使用布尔逻辑将业务规则链接在一起进行重新组合。
Java 中规格模式的编程示例
让我们考虑一个生物池示例。我们有一个具有特定属性的生物集合。这些属性可能属于一个预定义的有限集(用枚举表示,例如Size
、Movement
和Color
),或者它们可能是连续值(例如,Creature
的质量)。在具有连续值的情况下,最好使用“参数化规格”,其中属性值在Creature
实例化时作为参数提供,从而提高灵活性。此外,预定义和/或参数化属性可以使用布尔逻辑组合,提供几乎无限的选择可能性(这称为“复合规格”,将在下面进一步解释)。每种方法的优缺点在本文件末尾的表格中详细说明。
首先,这里是接口Creature
。
public interface Creature {
String getName();
Size getSize();
Movement getMovement();
Color getColor();
Mass getMass();
}
Dragon
实现看起来像这样。
public class Dragon extends AbstractCreature {
public Dragon() {
super("Dragon", Size.LARGE, Movement.FLYING, Color.RED, new Mass(39300.0));
}
}
现在我们想要选择它们的一部分,我们使用选择器。要选择能飞的生物,我们应该使用MovementSelector
。代码片段还显示了基类AbstractSelector
。
public abstract class AbstractSelector<T> implements Predicate<T> {
public AbstractSelector<T> and(AbstractSelector<T> other) {
return new ConjunctionSelector<>(this, other);
}
public AbstractSelector<T> or(AbstractSelector<T> other) {
return new DisjunctionSelector<>(this, other);
}
public AbstractSelector<T> not() {
return new NegationSelector<>(this);
}
}
public class MovementSelector extends AbstractSelector<Creature> {
private final Movement movement;
public MovementSelector(Movement m) {
this.movement = m;
}
@Override
public boolean test(Creature t) {
return t.getMovement().equals(movement);
}
}
另一方面,当选择比选定重量更重的生物时,我们使用MassGreaterThanSelector
。
public class MassGreaterThanSelector extends AbstractSelector<Creature> {
private final Mass mass;
public MassGreaterThanSelector(double mass) {
this.mass = new Mass(mass);
}
@Override
public boolean test(Creature t) {
return t.getMass().greaterThan(mass);
}
}
有了这些构建块,我们就可以执行一些搜索了。
@Slf4j
public class App {
public static void main(String[] args) {
// initialize creatures list
var creatures = List.of(
new Goblin(),
new Octopus(),
new Dragon(),
new Shark(),
new Troll(),
new KillerBee()
);
// so-called "hard-coded" specification
LOGGER.info("Demonstrating hard-coded specification :");
// find all walking creatures
LOGGER.info("Find all walking creatures");
print(creatures, new MovementSelector(Movement.WALKING));
// find all dark creatures
LOGGER.info("Find all dark creatures");
print(creatures, new ColorSelector(Color.DARK));
LOGGER.info("\n");
// so-called "parameterized" specification
LOGGER.info("Demonstrating parameterized specification :");
// find all creatures heavier than 500kg
LOGGER.info("Find all creatures heavier than 600kg");
print(creatures, new MassGreaterThanSelector(600.0));
// find all creatures heavier than 500kg
LOGGER.info("Find all creatures lighter than or weighing exactly 500kg");
print(creatures, new MassSmallerThanOrEqSelector(500.0));
LOGGER.info("\n");
// so-called "composite" specification
LOGGER.info("Demonstrating composite specification :");
// find all red and flying creatures
LOGGER.info("Find all red and flying creatures");
var redAndFlying = new ColorSelector(Color.RED).and(new MovementSelector(Movement.FLYING));
print(creatures, redAndFlying);
// find all creatures dark or red, non-swimming, and heavier than or equal to 400kg
LOGGER.info("Find all scary creatures");
var scaryCreaturesSelector = new ColorSelector(Color.DARK)
.or(new ColorSelector(Color.RED)).and(new MovementSelector(Movement.SWIMMING).not())
.and(new MassGreaterThanSelector(400.0).or(new MassEqualSelector(400.0)));
print(creatures, scaryCreaturesSelector);
}
private static void print(List<? extends Creature> creatures, Predicate<Creature> selector) {
creatures.stream().filter(selector).map(Objects::toString).forEach(LOGGER::info);
}
}
控制台输出
12:49:24.808 [main] INFO com.iluwatar.specification.app.App -- Demonstrating hard-coded specification :
12:49:24.810 [main] INFO com.iluwatar.specification.app.App -- Find all walking creatures
12:49:24.812 [main] INFO com.iluwatar.specification.app.App -- Goblin [size=small, movement=walking, color=green, mass=30.0kg]
12:49:24.812 [main] INFO com.iluwatar.specification.app.App -- Troll [size=large, movement=walking, color=dark, mass=4000.0kg]
12:49:24.812 [main] INFO com.iluwatar.specification.app.App -- Find all dark creatures
12:49:24.815 [main] INFO com.iluwatar.specification.app.App -- Octopus [size=normal, movement=swimming, color=dark, mass=12.0kg]
12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Troll [size=large, movement=walking, color=dark, mass=4000.0kg]
12:49:24.816 [main] INFO com.iluwatar.specification.app.App --
12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Demonstrating parameterized specification :
12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Find all creatures heavier than 600kg
12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Dragon [size=large, movement=flying, color=red, mass=39300.0kg]
12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Troll [size=large, movement=walking, color=dark, mass=4000.0kg]
12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Find all creatures lighter than or weighing exactly 500kg
12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Goblin [size=small, movement=walking, color=green, mass=30.0kg]
12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Octopus [size=normal, movement=swimming, color=dark, mass=12.0kg]
12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Shark [size=normal, movement=swimming, color=light, mass=500.0kg]
12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- KillerBee [size=small, movement=flying, color=light, mass=6.7kg]
12:49:24.816 [main] INFO com.iluwatar.specification.app.App --
12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Demonstrating composite specification :
12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Find all red and flying creatures
12:49:24.817 [main] INFO com.iluwatar.specification.app.App -- Dragon [size=large, movement=flying, color=red, mass=39300.0kg]
12:49:24.817 [main] INFO com.iluwatar.specification.app.App -- Find all scary creatures
12:49:24.818 [main] INFO com.iluwatar.specification.app.App -- Dragon [size=large, movement=flying, color=red, mass=39300.0kg]
12:49:24.818 [main] INFO com.iluwatar.specification.app.App -- Troll [size=large, movement=walking, color=dark, mass=4000.0kg]
采用规格模式显着增强了 Java 应用程序中业务规则的灵活性和可重用性,有助于编写更易于维护的代码。
何时在 Java 中使用规格模式
在以下情况下应用 Java 规格模式
- 您需要根据不同的条件过滤对象。
- 过滤条件可以动态更改。
- 非常适合涉及必须在应用程序的不同部分重用的复杂业务规则的用例。
Java 中规格模式的真实世界应用
- 在企业应用程序中验证用户输入。
- 在电子商务应用程序中过滤搜索结果。
- 领域驱动设计 (DDD) 中的业务规则验证。
规格模式的优缺点
优点
- 增强业务规则的灵活性和可重用性。
- 通过将业务规则与实体分离,促进单一职责原则。
- 便于对业务规则进行单元测试。
权衡
- 会导致大量小型类,从而增加复杂性。
- 由于规格的动态检查,可能会导致性能开销。