OnJava8-Enum-常量特定方法
责任链(Chain Of Responsibility)设计模式先创建了一批用于解决目标问题的不同方法,然后将它们连成一条“链”。
当一个请求先到达时,会顺着这条链传递下去,直到遇到链上某个可以处理该请求的方法。
可以很容易地用常量特定方法实现一条简单的责任链。考虑一个邮局模型,它对每一封邮件都会尝试用最常见的方式来处理,(如果行不通)并不断尝试别的方式,直到该邮件最终被视为“死信”(无法投递)。每种尝试可以看作一个策略(另一种设计模式),而整个策略列表放在一起就是一条责任链。
我们从一封邮件开始说起。它所有的重要特征都可以用来枚举表达。由于Mail对象是随机创建的,想要减小一封邮件的GenralDelivery被赋予YES的可能性,最简单的方法是创建更多的非YES的实例,因此枚举的定义一开始可能看起来有点好笑。
在Mail中,你会看到randomMail()方法,用来随机创建测试邮件。generator方法生成了一个Iterable对象,它使用randomMail方法来生成一定数量的Mail对象,每通过迭代器调用一次next()就会生成一个。这种结构允许通过调用Mail.genrator()方法实现for-in循环的简单创建能力。
首先对邮件进行建模:
package org.example.onjava.senior.example01enum.desgin;import org.example.onjava.onjavaUtils.Enums;import java.util.Iterator;/*** @Author Coder_Pans* @Date 2022/11/20 09:32* @PackageName:org.example.onjava.senior.example01enum.desgin* @ClassName: Mail* @Description: TODO 邮件建模* @Version 1.0*/
public class Mail {enum GeneralDelivery {YES,NO1,NO2,NO3,NO4,NO5}enum Scannability {UNSCANNABLE,YES1,YES2,YES3,YES4}enum Readability {ILLEGIBLE,YES1,YES2,YES3,YES4}enum Address {INCORRECT,OK1,OK2,OK3,OK4,OK5,OK6}enum ReturnAddress {MISSING,OK1,OK2,OK3,OK4,OK5}GeneralDelivery generalDelivery;Scannability scannability;Readability readability;Address address;ReturnAddress returnAddress;static long counter = 0;long id = counter++;@Override public String toString() {return "Mail " + id;}public String details() {return toString() +", General Delivery: " + generalDelivery +", Address Scannability: " + scannability +", Address Readability: " + readability +", Address Address: " + address +", Return address: " + returnAddress;}// Generate test Mail:public static Mail randomMail() {Mail m = new Mail();m.generalDelivery =Enums.random(GeneralDelivery.class);m.scannability =Enums.random(Scannability.class);m.readability =Enums.random(Readability.class);m.address = Enums.random(Address.class);m.returnAddress =Enums.random(ReturnAddress.class);return m;}public staticIterable generator(final int count) {return new Iterable() {int n = count;@Override public Iterator iterator() {return new Iterator() {@Override public boolean hasNext() {return n-- > 0;}@Override public Mail next() {return randomMail();}@Overridepublic void remove() { // Not implementedthrow new UnsupportedOperationException();}};}};}
}
创建常量特定方法:
package org.example.onjava.senior.example01enum.desgin;public class PostOffice {enum MailHandler {GENERAL_DELIVERY {@Override boolean handle(Mail m) {switch(m.generalDelivery) {case YES:System.out.println("Using general delivery for " + m);return true;default: return false;}}},MACHINE_SCAN {@Override boolean handle(Mail m) {switch(m.scannability) {case UNSCANNABLE: return false;default:switch(m.address) {case INCORRECT: return false;default:System.out.println("Delivering "+ m + " automatically");return true;}}}},VISUAL_INSPECTION {@Override boolean handle(Mail m) {switch(m.readability) {case ILLEGIBLE: return false;default:switch(m.address) {case INCORRECT: return false;default:System.out.println("Delivering " + m + " normally");return true;}}}},RETURN_TO_SENDER {@Override boolean handle(Mail m) {switch(m.returnAddress) {case MISSING: return false;default:System.out.println("Returning " + m + " to sender");return true;}}};abstract boolean handle(Mail m);}static void handle(Mail m) {for(MailHandler handler : MailHandler.values())if(handler.handle(m))return;System.out.println(m + " is a dead letter");}public static void main(String[] args) {for(Mail mail : Mail.generator(10)) {System.out.println(mail.details());handle(mail);System.out.println("*****");}}
}
/* Output:
Mail 0, General Delivery: NO2, Address Scannability:
UNSCANNABLE, Address Readability: YES3, Address
Address: OK1, Return address: OK1
Delivering Mail 0 normally
*****
Mail 1, General Delivery: NO5, Address Scannability:
YES3, Address Readability: ILLEGIBLE, Address Address:
OK5, Return address: OK1
Delivering Mail 1 automatically
*****
Mail 2, General Delivery: YES, Address Scannability:
YES3, Address Readability: YES1, Address Address: OK1,
Return address: OK5
Using general delivery for Mail 2
*****
Mail 3, General Delivery: NO4, Address Scannability:
YES3, Address Readability: YES1, Address Address:
INCORRECT, Return address: OK4
Returning Mail 3 to sender
*****
Mail 4, General Delivery: NO4, Address Scannability:
UNSCANNABLE, Address Readability: YES1, Address
Address: INCORRECT, Return address: OK2
Returning Mail 4 to sender
*****
Mail 5, General Delivery: NO3, Address Scannability:
YES1, Address Readability: ILLEGIBLE, Address Address:
OK4, Return address: OK2
Delivering Mail 5 automatically
*****
Mail 6, General Delivery: YES, Address Scannability:
YES4, Address Readability: ILLEGIBLE, Address Address:
OK4, Return address: OK4
Using general delivery for Mail 6
*****
Mail 7, General Delivery: YES, Address Scannability:
YES3, Address Readability: YES4, Address Address: OK2,
Return address: MISSING
Using general delivery for Mail 7
*****
Mail 8, General Delivery: NO3, Address Scannability:
YES1, Address Readability: YES3, Address Address:
INCORRECT, Return address: MISSING
Mail 8 is a dead letter
*****
Mail 9, General Delivery: NO1, Address Scannability:
UNSCANNABLE, Address Readability: YES2, Address
Address: OK1, Return address: OK4
Delivering Mail 9 normally
*****
*/
责任链模式的作用体现在了MailHandler枚举中,枚举的定义顺序则决定了各个策略在每封邮件上被应用的顺序。该模式会按顺序尝试应用每个策略,直到某个策略执行成功,或者全部策略都执行失败(即邮件无法投递)
枚举类型很适合用来实现状态机。状态机可以处于有限数量的特定状态。它们通常根据输入,从一个状态转移到下一个状态,但同时也会存在瞬态。当任务执行完毕后,状态机会立即跳出所有状态。
每个状态一般也会有某种对应的输出。
通过自动售货机案例来了解如何实现状态机,首先,在一个枚举中定义一系列输入:
/*** 用枚举实现状态机 01*/
public enum Input {NICKEL(5), DIME(10), QUARTER(25), DOLLAR(100),TOOTHPASTE(200), CHIPS(75), SODA(100), SOAP(50),ABORT_TRANSACTION {@Override public int amount() { // Disallowthrow new RuntimeException("ABORT.amount()");}},STOP { // This must be the last instance.@Override public int amount() { // Disallowthrow newRuntimeException("SHUT_DOWN.amount()");}};int value; // In centsInput(int value) { this.value = value; }Input() {}int amount() { return value; }; // In centsstatic Random rand = new Random(47);public static Input randomSelection() {// Don't include STOP:returnvalues()[rand.nextInt(values().length - 1)];}
}
VendingMachine(自动售货机)接收到输入后,首先通过Category(类别)枚举来对这些输入进行分类,这样就可以在各个类别间切换了。
package org.example.onjava.senior.example01enum.desgin;// enums/VendingMachine.java
// (c)2021 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
// {java VendingMachine VendingMachineInput.txt}import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.Iterator;
import java.util.function.Supplier;
import java.util.stream.Collectors;enum Category {MONEY(Input.NICKEL, Input.DIME,Input.QUARTER, Input.DOLLAR),ITEM_SELECTION(Input.TOOTHPASTE, Input.CHIPS,Input.SODA, Input.SOAP),QUIT_TRANSACTION(Input.ABORT_TRANSACTION),SHUT_DOWN(Input.STOP);private Input[] values;Category(Input... types) { values = types; }private static EnumMap categories =new EnumMap<>(Input.class);static {for(Category c : Category.class.getEnumConstants())for(Input type : c.values)categories.put(type, c);}public static Category categorize(Input input) {return categories.get(input);}
}public class VendingMachine {private static State state = State.RESTING;private static int amount = 0;private static Input selection = null;enum StateDuration { TRANSIENT } // Tagging enumenum State {RESTING {@Override void next(Input input) {switch(Category.categorize(input)) {case MONEY:amount += input.amount();state = ADDING_MONEY;break;case SHUT_DOWN:state = TERMINAL;default:}}},ADDING_MONEY {@Override void next(Input input) {switch(Category.categorize(input)) {case MONEY:amount += input.amount();break;case ITEM_SELECTION:selection = input;if(amount < selection.amount())System.out.println("Insufficient money for " + selection);else state = DISPENSING;break;case QUIT_TRANSACTION:state = GIVING_CHANGE;break;case SHUT_DOWN:state = TERMINAL;default:}}},DISPENSING(StateDuration.TRANSIENT) {@Override void next() {System.out.println("here is your " + selection);amount -= selection.amount();state = GIVING_CHANGE;}},GIVING_CHANGE(StateDuration.TRANSIENT) {@Override void next() {if(amount > 0) {System.out.println("Your change: " + amount);amount = 0;}state = RESTING;}},TERMINAL {@Overridevoid output() { System.out.println("Halted"); } };private boolean isTransient = false;State() {}State(StateDuration trans) { isTransient = true; }void next(Input input) {throw new RuntimeException("Only call " +"next(Input input) for non-transient states");}void next() {throw new RuntimeException("Only call next() for " +"StateDuration.TRANSIENT states");}void output() { System.out.println(amount); }}static void run(Supplier gen) {while(state != State.TERMINAL) {state.next(gen.get());while(state.isTransient)state.next();state.output();}}public static void main(String[] args) {Supplier gen = new RandomInputSupplier();if(args.length == 1)gen = new FileInputSupplier(args[0]);run(gen);}
}// For a basic sanity check:
class RandomInputSupplier implements Supplier {@Override public Input get() {return Input.randomSelection();}
}// Create Inputs from a file of ';'-separated strings:
class FileInputSupplier implements Supplier {private Iterator input;FileInputSupplier(String fileName) {try {input = Files.lines(Paths.get(fileName)).skip(1) // Skip the comment line.flatMap(s -> Arrays.stream(s.split(";"))).map(String::trim).collect(Collectors.toList()).iterator();} catch(IOException e) {throw new RuntimeException(e);}}@Override public Input get() {if(!input.hasNext())return null;return Enum.valueOf(Input.class, input.next().trim());}
}
/* Output:
25
50
75
here is your CHIPS
0
100
200
here is your TOOTHPASTE
0
25
35
Your change: 35
0
25
35
Insufficient money for SODA
35
60
70
75
Insufficient money for SODA
75
Your change: 75
0
Halted
*/