Java-01enum常量特定方法
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 static Iterable<Mail> generator(final int count) { return new Iterable<Mail>() { int n = count; @Override public Iterator<Mail> iterator() { return new Iterator<Mail>() { @Override public boolean hasNext() { return n-- > 0; } @Override public Mail next() { return randomMail(); } @Override public void remove() { // Not implemented throw 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() { // Disallow throw new RuntimeException("ABORT.amount()"); } }, STOP { // This must be the last instance. @Override public int amount() { // Disallow throw new RuntimeException("SHUT_DOWN.amount()"); } }; int value; // In cents Input(int value) { this.value = value; } Input() {} int amount() { return value; }; // In cents static Random rand = new Random(47); public static Input randomSelection() { // Don't include STOP: return values()[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<Input,Category> 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 enum enum 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 {@Override void 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<Input> gen) { while(state != State.TERMINAL) { state.next(gen.get()); while(state.isTransient) state.next(); state.output(); } } public static void main(String[] args) { Supplier<Input> gen = new RandomInputSupplier(); if(args.length == 1) gen = new FileInputSupplier(args[0]); run(gen); } } // For a basic sanity check: class RandomInputSupplier implements Supplier<Input> { @Override public Input get() { return Input.randomSelection(); } } // Create Inputs from a file of ';'-separated strings: class FileInputSupplier implements Supplier<Input> { private Iterator<String> 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 */
posted on 2022-11-20 09:54 JavaCoderPan 阅读(26) 评论(0) 编辑 收藏 举报
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南