设计模式- Java

发布于:2024-12-06 ⋅ 阅读:(29) ⋅ 点赞:(0)

工厂模式

通过将对象的创建过程封装到一个工厂类中,使得客户端不需要直接使用 new 去创建对象,而是通过调用工厂方法来获取所需的对象。这样可以降低代码耦合度,并方便后续的扩展和维护。

示例代码

简单工厂模式(不配合策略模式):

// 手机顶层接口
public interface Phone {
    void makeCall(String number);
}

// 华为手机实现
public class HuaweiPhone implements Phone {
    @Override
    public void makeCall(String number) {
        System.out.println("Using HuaweiPhone to call: " + number);
    }
}

// 苹果手机实现
public class ApplePhone implements Phone {
    @Override
    public void makeCall(String number) {
        System.out.println("Using ApplePhone to call: " + number);
    }
}

// 工厂类
public class PhoneFactory {
    public static Phone createPhone(String type) {
        switch (type) {
            case "Huawei":
                return new HuaweiPhone();
            case "Apple":
                return new ApplePhone();
            default:
                throw new IllegalArgumentException("Unknown phone type: " + type);
        }
    }
}

// 测试类
public class Main {
    public static void main(String[] args) {
        Phone huawei = PhoneFactory.createPhone("Huawei");
        huawei.makeCall("123456789");

        Phone apple = PhoneFactory.createPhone("Apple");
        apple.makeCall("987654321");
    }
}

工厂模式 + 策略模式

目的:当需要扩展更多设备(如平板)时,可以抽象顶层接口,将工厂类的职责划分得更清晰。

// 顶层接口:设备
public interface Device {
    void start();
}

// 手机实现
public class PhoneDevice implements Device {
    @Override
    public void start() {
        System.out.println("Starting Phone...");
    }
}

// 平板实现
public class TabletDevice implements Device {
    @Override
    public void start() {
        System.out.println("Starting Tablet...");
    }
}

// 策略模式:设备工厂接口
public interface DeviceFactory {
    Device createDevice();
}

// 手机工厂实现
public class PhoneFactory implements DeviceFactory {
    @Override
    public Device createDevice() {
        return new PhoneDevice();
    }
}

// 平板工厂实现
public class TabletFactory implements DeviceFactory {
    @Override
    public Device createDevice() {
        return new TabletDevice();
    }
}

// 客户端
public class Main {
    public static void main(String[] args) {
        DeviceFactory phoneFactory = new PhoneFactory();
        Device phone = phoneFactory.createDevice();
        phone.start();

        DeviceFactory tabletFactory = new TabletFactory();
        Device tablet = tabletFactory.createDevice();
        tablet.start();
    }
}

配合 Map 的映射关系

当设备种类较多时,可以用 Map 管理工厂类,动态获取:

import java.util.HashMap;
import java.util.Map;

public class DeviceFactoryRegistry {
    private static final Map<String, DeviceFactory> factoryMap = new HashMap<>();

    static {
        factoryMap.put("Phone", new PhoneFactory());
        factoryMap.put("Tablet", new TabletFactory());
    }

    public static Device getDevice(String type) {
        DeviceFactory factory = factoryMap.get(type);
        if (factory == null) {
            throw new IllegalArgumentException("Unknown device type: " + type);
        }
        return factory.createDevice();
    }
}

// 测试类
public class Main {
    public static void main(String[] args) {
        Device phone = DeviceFactoryRegistry.getDevice("Phone");
        phone.start();

        Device tablet = DeviceFactoryRegistry.getDevice("Tablet");
        tablet.start();
    }
}
结论

通过引入工厂模式和策略模式的结合,可以让代码更加灵活、可扩展,特别适用于需要动态创建不同类型对象的场景。

装饰器模式

  1. 目的:在不修改原有类的情况下,动态地增强类的功能。
  2. 前提:被增强的类需要有一个顶层的抽象接口。
  3. 实现:通过实现相同接口的装饰器类,将被增强的类作为成员变量进行组合(而不是继承),然后对其方法进行增强或扩展。
示例代码
核心结构
  1. 顶层接口:定义核心功能。
  2. 具体实现类:实现核心功能。
  3. 装饰器类:实现顶层接口,并将被装饰类作为成员变量,通过组合的方式增强功能。
代码实现
// 顶层接口
public interface Component {
    void operation();
}

// 具体实现类
public class ConcreteComponent implements Component {
    @Override
    public void operation() {
        System.out.println("ConcreteComponent: 原本的功能");
    }
}

// 装饰器基类
public abstract class Decorator implements Component {
    protected Component component;

    public Decorator(Component component) {
        this.component = component;
    }

    @Override
    public void operation() {
        component.operation();
    }
}

// 第一个具体装饰器
public class DecoratorA extends Decorator {
    public DecoratorA(Component component) {
        super(component);
    }

    @Override
    public void operation() {
        super.operation();
        addFunctionalityA();
    }

    private void addFunctionalityA() {
        System.out.println("DecoratorA: 增强功能 A");
    }
}

// 第二个具体装饰器
public class DecoratorB extends Decorator {
    public DecoratorB(Component component) {
        super(component);
    }

    @Override
    public void operation() {
        super.operation();
        addFunctionalityB();
    }

    private void addFunctionalityB() {
        System.out.println("DecoratorB: 增强功能 B");
    }
}

// 测试类
public class Main {
    public static void main(String[] args) {
        // 原始组件
        Component component = new ConcreteComponent();

        // 增强功能 A
        Component decoratorA = new DecoratorA(component);
        decoratorA.operation();

        System.out.println("----------");

        // 增强功能 A + B
        Component decoratorB = new DecoratorB(decoratorA);
        decoratorB.operation();
    }
}
输出结果
ConcreteComponent: 原本的功能
DecoratorA: 增强功能 A
----------
ConcreteComponent: 原本的功能
DecoratorA: 增强功能 A
DecoratorB: 增强功能 B

装饰器模式的优点
  1. 开闭原则:在不修改原类的情况下,对功能进行扩展。
  2. 灵活性:可以动态组合多个装饰器,叠加增强功能。
  3. 替代继承:相比于直接继承扩展类功能,装饰器更灵活,不会导致类爆炸问题。
装饰器模式的缺点
  1. 复杂性:对于过多的装饰器,代码结构可能变得复杂。
  2. 性能开销:每一层装饰器都会增加额外的调用栈。

适配器模式

  1. 目标:使得不兼容的接口可以协同工作,适配器模式让一个类的接口变得与客户端期望的接口兼容。
  2. 工作原理:适配器类通过组合的方式持有旧接口对象,并通过重写方法来实现对旧接口的适配,让它能够符合新的接口要求。
  3. 典型场景:当你有一个过时的接口或第三方库,无法直接使用,但是你又不想修改旧的代码,适配器模式就非常适合。
示例代码

假设我们有一个音乐播放器,旧版本只支持 MP3 格式,而新版本支持 MP4 格式。我们可以使用适配器模式来将旧播放器适配到新的播放器接口。

1. 定义接口
  • 新播放器接口:定义新版本播放器支持的播放方法。
  • 旧播放器接口:定义老版本播放器支持的播放方法。
// 新播放器接口
public interface NewPlayer {
    void playMP4(String fileName);
}

// 旧播放器接口
public interface OldPlayer {
    void playMP3(String fileName);
}
2. 旧播放器实现

旧播放器只能播放 MP3 格式。

// 旧的播放器实现
public class OldMP3Player implements OldPlayer {
    @Override
    public void playMP3(String fileName) {
        System.out.println("Playing MP3 file: " + fileName);
    }
}
3. 新播放器实现

新播放器支持 MP4 格式。

// 新的播放器实现
public class NewMP4Player implements NewPlayer {
    @Override
    public void playMP4(String fileName) {
        System.out.println("Playing MP4 file: " + fileName);
    }
}
4. 适配器类

适配器将旧播放器的 playMP3 方法适配为新播放器的 playMP4 方法。

// 适配器类:将旧播放器适配为新播放器
public class AdapterPlayer implements NewPlayer {
    private OldPlayer oldPlayer;

    public AdapterPlayer(OldPlayer oldPlayer) {
        this.oldPlayer = oldPlayer;
    }

    @Override
    public void playMP4(String fileName) {
        // 当遇到 MP3 格式时,调用旧播放器播放
        System.out.println("Adapting to MP4 format, using old player to play MP3...");
        oldPlayer.playMP3(fileName);
    }
}
5. 客户端使用

在客户端使用时,可以通过适配器类使得旧播放器也能兼容新播放器的接口。

public class Main {
    public static void main(String[] args) {
        OldPlayer oldPlayer = new OldMP3Player();
        NewPlayer adapterPlayer = new AdapterPlayer(oldPlayer);

        // 通过适配器调用新播放器的接口
        adapterPlayer.playMP4("song.mp3");
    }
}
输出结果
Adapting to MP4 format, using old player to play MP3...
Playing MP3 file: song.mp3
适配器模式的优点
  1. 兼容性:适配器模式能够使得旧系统与新系统之间保持兼容,避免修改旧代码。
  2. 灵活性:适配器类可以根据不同的需求来适配多个接口,增加了系统的灵活性。
  3. 单一职责:适配器专门用于接口的转换,符合单一职责原则。
适配器模式的缺点
  1. 增加了代码复杂度:每个适配器类都会增加代码量,可能会增加维护的复杂性。
  2. 可能需要大量的适配器:如果需要适配的接口很多,适配器类的数量可能会增加,导致类的管理变得更加复杂。

组合模式

  1. 统一接口:通过定义一个顶层接口,使得客户端可以通过相同的方式来处理单个对象和组合对象(容器对象)。
  2. 递归结构:对象可以包含子对象,这样形成一个树形结构,允许递归组合。
  3. 灵活性:通过组合树中的节点(例如,单个区域或区域列表),可以灵活地构建复杂结构。
组合模式的应用场景
  • 组织结构:例如公司组织结构,其中有公司、部门、员工等组成部分。
  • 文件系统:文件夹中包含文件,文件夹也可以包含文件夹,形成递归的树形结构。
  • 图形绘制:多个图形可以组合成一个复合图形,例如矩形、圆形等基本图形可以组合成更复杂的图形。
示例代码

假设你要统计中国各个省市的人口数量,可以使用组合模式来表示不同层级的区域(省、市等)并进行统计。

1. 定义顶层接口
// 区域接口,所有的区域类(省、市等)都需要实现这个接口
public interface Region {
    int getPopulation();  // 获取该区域的人口数
}
2. 具体实现类(叶子节点)

每个区域的具体实现类,如省或者市,都实现 Region 接口,并计算其人口数。

// 省级区域,叶子节点
public class Province implements Region {
    private String name;
    private int population;

    public Province(String name, int population) {
        this.name = name;
        this.population = population;
    }

    @Override
    public int getPopulation() {
        System.out.println("统计 " + name + " 的人口: " + population);
        return population;
    }
}

// 市级区域,叶子节点
public class City implements Region {
    private String name;
    private int population;

    public City(String name, int population) {
        this.name = name;
        this.population = population;
    }

    @Override
    public int getPopulation() {
        System.out.println("统计 " + name + " 的人口: " + population);
        return population;
    }
}
3. 组合类(容器节点)

组合类可以包含多个 Region 对象,无论是单独的 Province 还是包含多个市的 RegionGroup

// 区域组,组合节点
import java.util.List;

public class RegionGroup implements Region {
    private String name;
    private List<Region> subRegions;

    public RegionGroup(String name, List<Region> subRegions) {
        this.name = name;
        this.subRegions = subRegions;
    }

    @Override
    public int getPopulation() {
        int totalPopulation = 0;
        System.out.println("统计 " + name + " 的人口总数:");
        for (Region region : subRegions) {
            totalPopulation += region.getPopulation();
        }
        return totalPopulation;
    }
}
4. 客户端使用

通过组合 RegionGroupRegion,可以灵活地统计不同区域的人口。

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        // 创建市级区域
        Region changsha = new City("长沙市", 1000);
        Region zhuzhou = new City("株洲市", 800);

        // 创建省级区域
        Region hunan = new Province("湖南省", 10000);

        // 创建包含多个市的区域组合
        Region cityGroup = new RegionGroup("湖南省", Arrays.asList(changsha, zhuzhou));

        // 再创建包含省和市的更大组合
        Region country = new RegionGroup("中国", Arrays.asList(hunan, cityGroup));

        // 获取中国的总人口
        int totalPopulation = country.getPopulation();
        System.out.println("中国总人口:" + totalPopulation);
    }
}
组合模式的优点
  1. 简化客户端代码:客户端可以通过统一的 Region 接口来处理单个对象和组合对象,简化了代码的复杂性。
  2. 灵活扩展:可以方便地添加新的 Region 类型(如城市、地区等),而不需要修改现有的代码。
  3. 支持递归结构:组合模式使得树形结构的递归实现变得容易,适合表示具有层次结构的对象。
组合模式的缺点
  1. 增加了系统复杂性:由于组合结构的递归特性,如果层次结构复杂,系统的复杂度会增加。
  2. 不适合所有场景:如果对象之间没有明显的层级结构,组合模式可能会使得设计变得不必要复杂。

代理模式

  1. 代理角色:代理类代表另一个对象,并控制对该对象的访问。客户端与代理类交互,而不直接与目标对象(真实对象)交互。
  2. 真实角色:目标对象,执行实际的业务逻辑。
  3. 代理类型:
    • 静态代理:代理类在编译时就已经存在,代理类和目标类之间的关系是静态的。
    • 动态代理:代理类在运行时动态生成,通常通过反射机制创建,目标类和代理类的关系是动态的。
代理模式的常见应用场景
  • 延迟加载:代理可以在需要时再创建真实对象,避免不必要的资源消耗。
  • 权限控制:代理可以控制对目标对象的访问,确保只有经过授权的操作才能执行。
  • 日志记录、性能监控:代理可以在调用目标对象之前或之后添加日志记录、性能监控等功能。
  • 远程代理:在分布式系统中,代理可以用来代表远程对象,隐藏远程调用的细节。
示例代码

下面是一个简单的静态代理模式的示例:

1. 定义接口
// 接口,真实角色和代理角色都实现这个接口
public interface Subject {
    void request();
}
2. 真实角色
// 真实角色
public class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject: 执行请求");
    }
}
3. 代理角色
// 代理角色
public class ProxySubject implements Subject {
    private RealSubject realSubject;

    @Override
    public void request() {
        // 在代理角色中加入额外的操作,例如权限验证、日志记录等
        if (realSubject == null) {
            realSubject = new RealSubject();
        }
        System.out.println("ProxySubject: 代理前的操作");
        realSubject.request();  // 调用真实角色的方法
        System.out.println("ProxySubject: 代理后的操作");
    }
}
4. 客户端代码
public class Client {
    public static void main(String[] args) {
        Subject proxy = new ProxySubject();  // 创建代理对象
        proxy.request();  // 通过代理对象调用请求
    }
}
输出结果
ProxySubject: 代理前的操作
RealSubject: 执行请求
ProxySubject: 代理后的操作
代理模式的优点
  1. 控制访问:代理对象可以控制对真实对象的访问,增加安全性和权限控制。
  2. 增加功能:代理类可以在不修改真实对象的前提下,增加额外的功能(如日志记录、事务处理等)。
  3. 延迟初始化:通过代理模式,可以在实际需要的时候再创建真实对象,节省资源。
  4. 透明性:代理模式使得客户端代码与真实对象的交互保持一致,客户端不需要关心代理对象的存在。
代理模式的缺点
  1. 增加系统复杂度:代理模式需要创建额外的代理对象,可能会增加系统的复杂性。
  2. 性能开销:由于代理对象在方法调用前后可能添加了额外的处理逻辑,可能会带来性能开销。

责任链模式

  1. 链条结构:多个处理对象(处理者)形成一个链条,每个处理对象处理某个特定的请求或任务。
  2. 职责划分:每个处理对象只负责自己特定的任务,且每个对象都知道下一个处理对象是哪个。
  3. 请求传递:请求从链头开始,依次传递给链中的各个处理对象,每个对象可以选择处理请求或将请求传递给链中的下一个对象。
  4. 解耦:客户端无需知道请求将被哪个处理对象处理,只需要将请求发送到链的开始,其他的交给链中的处理对象。
责任链模式的应用场景
  • 日志处理:日志记录时可以通过不同的处理器(如输出到文件、数据库、控制台等)依次传递。
  • 权限验证:请求可以在多个权限验证处理器之间传递,直到找到合适的处理器进行处理。
  • 事件处理:UI 事件的处理,事件可以通过不同的事件处理器(如按钮点击、输入框变化等)传递。
示例代码

以下是责任链模式的简单实现:

1. 定义处理者接口
// 处理者接口,每个处理者处理某个请求,或者将请求传递给下一个处理者
public interface Handler {
    void setNextHandler(Handler nextHandler);  // 设置下一个处理者
    void handleRequest(String request);  // 处理请求
}
2. 具体处理者实现
// 处理者 A
public class HandlerA implements Handler {
    private Handler nextHandler;

    @Override
    public void setNextHandler(Handler nextHandler) {
        this.nextHandler = nextHandler;
    }

    @Override
    public void handleRequest(String request) {
        if (request.equals("TaskA")) {
            System.out.println("HandlerA is handling the request.");
        } else if (nextHandler != null) {
            nextHandler.handleRequest(request);  // 传递给下一个处理者
        } else {
            System.out.println("No handler can handle the request.");
        }
    }
}

// 处理者 B
public class HandlerB implements Handler {
    private Handler nextHandler;

    @Override
    public void setNextHandler(Handler nextHandler) {
        this.nextHandler = nextHandler;
    }

    @Override
    public void handleRequest(String request) {
        if (request.equals("TaskB")) {
            System.out.println("HandlerB is handling the request.");
        } else if (nextHandler != null) {
            nextHandler.handleRequest(request);  // 传递给下一个处理者
        } else {
            System.out.println("No handler can handle the request.");
        }
    }
}
3. 客户端使用
public class Main {
    public static void main(String[] args) {
        // 创建处理者
        Handler handlerA = new HandlerA();
        Handler handlerB = new HandlerB();

        // 设置责任链
        handlerA.setNextHandler(handlerB);

        // 客户端请求
        System.out.println("Requesting TaskA:");
        handlerA.handleRequest("TaskA");

        System.out.println("\nRequesting TaskB:");
        handlerA.handleRequest("TaskB");

        System.out.println("\nRequesting TaskC:");
        handlerA.handleRequest("TaskC");
    }
}
输出结果
Requesting TaskA:
HandlerA is handling the request.

Requesting TaskB:
HandlerB is handling the request.

Requesting TaskC:
No handler can handle the request.
责任链模式的优点
  1. 解耦:客户端不需要知道具体的处理者,只需要发送请求,链条中的处理者会依次处理请求。这降低了客户端与具体处理者之间的耦合。
  2. 动态处理:可以动态地改变链条的处理顺序,增加或移除处理者非常方便。
  3. 扩展性:可以通过添加新的处理者来扩展功能,符合开闭原则。
责任链模式的缺点
  1. 性能问题:如果链条过长,或者每个处理者的处理逻辑较复杂,可能会影响性能,因为请求会经过多次处理。
  2. 调试困难:链条结构可能导致请求在链中传递,调试时难以跟踪请求的流向。
总结

责任链模式通过将多个处理对象连接成一个链条,让请求在链中传递,直到某个处理对象能够处理它为止。每个处理对象可以选择处理请求或将请求转发给下一个处理对象。这种模式不仅解耦了请求的发送者与处理者,还提供了灵活的扩展机制。

建造者模式

没错,建造者模式(Builder Pattern)确实和链式调用(Fluent Interface)有很大的相似之处。它的核心思想就是将复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。

通过链式调用的方式,你可以一步步地为对象设置所需的属性,而不必关心对象具体是如何被构建的。建造者模式的优势在于:

  1. 解耦构建过程和表示:建造者模式将对象的构建逻辑与业务逻辑分开,便于维护和扩展。
  2. 链式调用:使代码简洁、直观,调用顺序也更加灵活。
  3. 代码可读性强:通过链式调用可以清晰地了解一个对象的构建过程。

举个简单的例子:

// 假设我们构建一个 Computer 对象
public class Computer {
    private String CPU;
    private String RAM;
    private String storage;

    // 私有构造函数,避免直接创建对象
    private Computer(Builder builder) {
        this.CPU = builder.CPU;
        this.RAM = builder.RAM;
        this.storage = builder.storage;
    }

    public static class Builder {
        private String CPU;
        private String RAM;
        private String storage;

        public Builder setCPU(String CPU) {
            this.CPU = CPU;
            return this;
        }

        public Builder setRAM(String RAM) {
            this.RAM = RAM;
            return this;
        }

        public Builder setStorage(String storage) {
            this.storage = storage;
            return this;
        }

        public Computer build() {
            return new Computer(this);
        }
    }

    @Override
    public String toString() {
        return "Computer [CPU=" + CPU + ", RAM=" + RAM + ", Storage=" + storage + "]";
    }
}

// 使用示例
public class Main {
    public static void main(String[] args) {
        Computer computer = new Computer.Builder()
            .setCPU("Intel i7")
            .setRAM("16GB")
            .setStorage("512GB SSD")
            .build();

        System.out.println(computer);
    }
}

模版模式

模版模式就是把通用的方法全都定义在一个抽象的父类,子类去继承他只专注于实现其他功能就行了是吧,比如泡茶,泡牛奶,都有一个公共的过程就是烧水。

抽象父类
public abstract class Beverage {
    // 模版方法,定义流程
    public final void prepare() {
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    }

    // 通用方法,父类实现
    private void boilWater() {
        System.out.println("烧水...");
    }

    private void pourInCup() {
        System.out.println("将饮品倒入杯子...");
    }

    // 抽象方法,子类具体实现
    protected abstract void brew();
    protected abstract void addCondiments();
}
泡茶子类
public class Tea extends Beverage {
    @Override
    protected void brew() {
        System.out.println("泡茶叶...");
    }

    @Override
    protected void addCondiments() {
        System.out.println("添加柠檬...");
    }
}
泡牛奶子类
public class Milk extends Beverage {
    @Override
    protected void brew() {
        System.out.println("加热牛奶...");
    }

    @Override
    protected void addCondiments() {
        System.out.println("添加糖...");
    }
}
使用模版方法
public class Main {
    public static void main(String[] args) {
        Beverage tea = new Tea();
        tea.prepare();

        System.out.println("------");

        Beverage milk = new Milk();
        milk.prepare();
    }
}
输出结果
烧水...
泡茶叶...
将饮品倒入杯子...
添加柠檬...
------
烧水...
加热牛奶...
将饮品倒入杯子...
添加糖...

优点:

  1. 代码复用:通用部分(如烧水、倒入杯子)只需实现一次。
  2. 扩展性好:添加新类型的饮品只需创建新的子类即可。
  3. 灵活性:可以灵活地定义和调整每一步的具体实现。

模版模式适合用于具有固定流程但实现细节可变的场景,非常实用!


网站公告

今日签到

点亮在社区的每一天
去签到