java闲聊 设计模式①

118 阅读5分钟

代理模式

代理模式用于控制对自身实现的访问,封装了实际执行工作的实现类。

public interface ProxyBase {

    void p();

    class ProxyLonelyWarrior implements ProxyBase{
        @Override
        public void p() {
            System.out.println("aha");
        }
    }

    class Proxy implements ProxyBase{
        private ProxyBase delegate = new ProxyLonelyWarrior();
        @Override
        public void p() {
            //可以做点什么
            delegate.p();
            //可以做点什么
        }
    }

    public static void main(String[] args) {
        ProxyBase proxyBase = new Proxy();
        proxyBase.p();
    }
}

实际工作的实现类并不需要实现相同的接口,但如果有个公共的接口会来强制实现会更方便。

应用场景:

  1. 远程调用(Remote Proxy):当客户端需要访问远程对象时,使用代理模式可以隐藏底层复杂的网络通信细节,并提供类似于对本地对象访问的接口,从而使得远程对象像本地对象一样进行访问。
  2. 安全性控制(Protection Proxy):使用代理模式可以在调用对象真实方法之前进行安全性检查,如果客户端没有足够的权限,可以通过代理拒绝对对象的访问。
  3. 日志记录(Logging Proxy):使用代理来记录原始对象的方法调用次数、执行时间、参数等信息,这样可以在不修改现有代码的情况下进行日志记录。
  4. 延迟加载(Virtual Proxy):当创建对象非常耗时时,可以使用代理模式实现延迟加载。代理对象会在真正需要访问对象时才将其实例化或初始化。
  5. 缓存对象(Caching Proxy):在获取某些对象时,可以在代理类中设置缓存机制,避免多次调用代价昂贵的真实对象,提高系统性能。
  6. 方法增强:日常开发中最常见到的 Mybatis、Feign调用、切面等,你只需要关心自己的业务代码或者提供约定的配置,就能让代理对象帮你实现通用的功能

状态机

public class StateMachine {
    private State currentState;

    private interface State {
        void turnOn();
        void turnOff();
        void pause();
    }

    private class OnState implements State {
        public void turnOn() {
            System.out.println("Already on");
        }

        public void turnOff() {
            System.out.println("Turning off");
            currentState = new OffState();
        }

        public void pause() {
            System.out.println("Pausing");
            currentState = new PauseState();
        }
    }

    private class OffState implements State {
        public void turnOn() {
            System.out.println("Turning on");
            currentState = newt OnStae();
        }

        public void turnOff() {
            System.out.println("Already off");
        }

        public void pause() {
            System.out.println("Cannot pause, machine is currently off");
        }
    }

    private class PauseState implements State {
        public void turnOn() {
            System.out.println("Resuming");
            currentState = new OnState();
        }

        public void turnOff() {
            System.out.println("Turning off");
            currentState = new OffState();
        }

        public void pause() {
            System.out.println("Already paused");
        }
    }

    public StateMachine() {
        currentState = new OffState();
    }

    public void turnOn() {
        currentState.turnOn();
    }

    public void turnOff() {
        currentState.turnOff();
    }

    public void pause() {
        currentState.pause();
    }
}

状态模式使得调用方可以改变具体的实现,而状态机则通过一种强加的结构来自动改变具体实现。

当状态变化时,对象的行为也会自动的发生变化,因为其内部包含状态模式。

状态机模式适用于需要根据对象内部状态自动改变行为的应用程序:

  1. 订单状态管理:在订单处理系统中,订单状态可能会在整个处理流程中经历多个不同的状态,如已下单、付款中、已发货等。可以使用状态机模式来自动地根据当前订单状态调整处理流程。
  2. 流程审批状态管理:在企业流程审批系统中,一个任务可能需要经过多个审核环节,每个环节的处理方式和身份不同。可以使用状态机模式来管理和控制流程的不同状态和状态转换。
  3. 自动售货机状态管理:在自动售货机中,根据选择的商品和支付金额,自动售货机可能会进入不同的状态。例如,如果付款金额不足,自动售货机将处于 “投币中” 状态,直到客户完全支付所需金额。可以使用状态机模式来自动管理和控制自动售货机的状态。

命令模式

命令模式通常用于解决将命令请求者执行者分离的问题,即将发起请求的对象(命令请求者)与执行实际操作的对象(命令接收者)解耦,从而支持更灵活的应用程序设计和扩展。一个简单的应用场景是文本编辑器的撤销重做功能。

public interface Command {
    void action();
    void undo();

    class AddCommand implements Command{
        TextEditor textEditor;
        String text;

        public AddCommand(TextEditor textEditor, String text) {
            this.textEditor = textEditor;
            this.text = text;
        }

        @Override
        public void action() {
            textEditor.add(text);
        }

        @Override
        public void undo() {
            textEditor.delete(text);
        }
    }

    class DeleteCommand implements Command{
        TextEditor textEditor;
        String text;

        public DeleteCommand(TextEditor textEditor, String text) {
            this.textEditor = textEditor;
            this.text = text;
        }

        @Override
        public void action() {
            textEditor.delete(text);
        }

        @Override
        public void undo() {
            textEditor.add(text);
        }
    }
    
    //执行者
    class TextEditor{
        String text = "";

        void add(String textToAdd){
            this.text += textToAdd;
        }

        void delete(String textToDelete){
            int index = text.lastIndexOf(textToDelete);
            if (index != -1) {
                this.text = text.substring(0, index) + text.substring(index + textToDelete.length());
            }
        }
    }

    //请求者
    class Invoker{
        Deque<Command> history = new LinkedList<>();

        public void executeCommand(Command command) {
            command.action();
            history.add(command);
        }

        public void undoLastCommand() {
            if (!history.isEmpty()) {
                Command previousCommand = history.pollLast();
                previousCommand.undo();
            }
        }
    }

    public static void main(String[] args) {
        Invoker invoker = new Invoker();
        TextEditor textEditor = new TextEditor();
        invoker.executeCommand(new AddCommand(textEditor,"1"));
        invoker.executeCommand(new AddCommand(textEditor,"2"));
        invoker.executeCommand(new DeleteCommand(textEditor,"2"));
        System.out.println(textEditor.text);
        invoker.undoLastCommand();
        System.out.println(textEditor.text);
    }
}

命令模式的关键在于 把命令分离出来,并将其封装为一个对象/函数 进行传递 从而生成不同的行为。

命令模式主要适用于 需要回滚、撤销和重做操作的业务场景中,例如:文本编辑器中的撤销和重做功能、交易系统中的事务管理、多级菜单导航等。其实任何需要记录操作历史、支持撤销操作的系统都可能会使用命令模式。

适配器模式

当我有了某个类A,但是现有的代码 只能传入一个B进行调用时,适配器就再合适不过了。

public interface Cal {
    Integer cal(Object obj);

    class BigDecimalCalculator{
        BigDecimal execute(Object obj){
            return new BigDecimal(obj.hashCode());
        }
    }

    class BigDecimalCalculatorAdapter implements Cal{
        @Override
        public Integer cal(Object obj) {
            return new BigDecimalCalculator().execute(obj).intValue();
        }
    }

    public static void main(String[] args) {
        Cal cal = new BigDecimalCalculatorAdapter();
        Integer cal1 = cal.cal("qwe");
        System.out.println(cal1);
    }
}

适配器模式适用于在已有的类之间转换接口,主要解决接口不兼容的问题。可以使用适配器模式来“适配”现有的类使其拥其他新接口定义的功能。

外观模式

如果一段代码很丑,就把它藏到对象里 。 这基本就是外观模式的功能。

public class CPU {
    public void processData() {
        System.out.println("进行数据处理");
    }
}

class Memory {
    public void loadMemory() {
        System.out.println("装载内存");
    }
}

class HardDrive {
    public void readData() {
        System.out.println("读取硬盘数据");
    }
}

class Computer {
    private CPU cpu;
    private Memory memory;
    private HardDrive hardDrive;

    public Computer() {
        this.cpu = new CPU();
        this.memory = new Memory();
        this.hardDrive = new HardDrive();
    }

    public void startComputer() {
        hardDrive.readData();
        memory.loadMemory();
        cpu.processData();
    }
}

class Client {
    public static void main(String[] args) {
        Computer computer = new Computer();
        computer.startComputer();
    }
}

如上,Computer 类封装了多个子系统的复杂功能(CPUMemoryHardDrive),并提供了一个简单的 startComputer() 方法来启动计算机。客户端只需要创建 Computer 对象,调用 startComputer() 方法即可启动计算机,而无需了解各个子系统的实现细节。

再譬如

public class URLDemo {
    public static void main(String[] args) throws MalformedURLException, IOException {
        URL url = new URL("https://www.baidu.com");
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");
        BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        String inputLine;
        while ((inputLine = in.readLine()) != null) {
            System.out.println(inputLine);
        }
        in.close();
    }
}

在 Java 中,java.net.URL 类封装了与 URL 相关的复杂操作(如协议解析、数据传输等),并提供了简单的方法来访问 URL 资源。其中,openConnection() 方法即体现了外观模式。

openConnection() 方法返回一个 URLConnection 对象,该对象提供了与 URL 相关的连接、读取、写入等功能,相当于封装了多个子系统的调用,为客户端提供了一个更简单的接口。通过 URLConnection 对象,客户端可以使用常见的 HTTP 方法(如 GET、POST 等)来获取资源,而无需了解底层实现细节。

访问者模式

访问者模式适用于数据结构稳定但在运行时需要不断添加新的处理操作的场景。当我们需要对一个数据结构中的元素进行多种不同的操作时,传统的做法是在数据结构中增加各种方法来支持这些操作,但这样会导致数据结构变得复杂,难以维护。使用访问者模式,可以将数据结构与处理操作分离,使得数据结构更加清晰简洁,并且可以方便地添加新的处理操作

public interface Visitor {
    void settle(CashRecord record);
    void settle(BankNoteRecord record);
    void settle(PointRecord record);
}
//支付记录
interface Record{
    void accept(Visitor visitor);
}
class CashRecord implements Record{
    @Override
    public void accept(Visitor visitor) {
        visitor.settle(this);
    }
}
class BankNoteRecord implements Record{
    @Override
    public void accept(Visitor visitor) {
        visitor.settle(this);
    }
}
class PointRecord implements Record{
    @Override
    public void accept(Visitor visitor) {
        visitor.settle(this);
    }
}
//定金模式
class DepositeSettleVisitor implements Visitor{
    @Override
    public void settle(CashRecord record) {
        //现金结算
    }
    @Override
    public void settle(BankNoteRecord record) {
        //银票结算
    }
    @Override
    public void settle(PointRecord record) {
        //积分结算
    }
}
//订单
class Order{
    public Order(List<Record> records) {
        this.records = records;
    }
    List<Record> records;
    void settle(Visitor visitor){
        records.forEach(item-> item.accept(visitor));
    }
}

class Main{
    public static void main(String[] args) {
        Order order = new Order(new ArrayList<>());
        order.settle(new DepositeSettleVisitor());
    }
}

这段代码中 Order 和 Record的关系是稳定的,在处理结算逻辑时只需要遍历付款记录进行处理,在后续付款类型、结算类型(示例只有定金,还会有全额、分期之类的)时只需要添加不同的实现。

可能用到访问者模式的业务场景:

  1. 图形界面应用程序中,遍历UI组件(如窗口、标签、按钮等)并执行其不同类型的行为。
  2. 编译器前端,遍历语法树并进行不同的静态分析,如语义检查、类型检查等。
  3. 电子商务网站中的购物车应用程序,遍历商品列表并执行不同的购买和结算操作。
  4. 生产线上的质量控制应用程序,遍历生产过程中的各种元素,并根据检测结果采取相应的措施。
  5. 文本处理应用程序,遍历文档树并进行不同的转换操作,如Markdown到HTML的转换等。