工厂模式
通过将对象的创建过程封装到一个工厂类中,使得客户端不需要直接使用 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();
}
}
结论
通过引入工厂模式和策略模式的结合,可以让代码更加灵活、可扩展,特别适用于需要动态创建不同类型对象的场景。
装饰器模式
- 目的:在不修改原有类的情况下,动态地增强类的功能。
- 前提:被增强的类需要有一个顶层的抽象接口。
- 实现:通过实现相同接口的装饰器类,将被增强的类作为成员变量进行组合(而不是继承),然后对其方法进行增强或扩展。
示例代码
核心结构
- 顶层接口:定义核心功能。
- 具体实现类:实现核心功能。
- 装饰器类:实现顶层接口,并将被装饰类作为成员变量,通过组合的方式增强功能。
代码实现
// 顶层接口
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
装饰器模式的优点
- 开闭原则:在不修改原类的情况下,对功能进行扩展。
- 灵活性:可以动态组合多个装饰器,叠加增强功能。
- 替代继承:相比于直接继承扩展类功能,装饰器更灵活,不会导致类爆炸问题。
装饰器模式的缺点
- 复杂性:对于过多的装饰器,代码结构可能变得复杂。
- 性能开销:每一层装饰器都会增加额外的调用栈。
适配器模式
- 目标:使得不兼容的接口可以协同工作,适配器模式让一个类的接口变得与客户端期望的接口兼容。
- 工作原理:适配器类通过组合的方式持有旧接口对象,并通过重写方法来实现对旧接口的适配,让它能够符合新的接口要求。
- 典型场景:当你有一个过时的接口或第三方库,无法直接使用,但是你又不想修改旧的代码,适配器模式就非常适合。
示例代码
假设我们有一个音乐播放器,旧版本只支持 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. 定义顶层接口
// 区域接口,所有的区域类(省、市等)都需要实现这个接口
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. 客户端使用
通过组合 RegionGroup
和 Region
,可以灵活地统计不同区域的人口。
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);
}
}
组合模式的优点
- 简化客户端代码:客户端可以通过统一的
Region
接口来处理单个对象和组合对象,简化了代码的复杂性。 - 灵活扩展:可以方便地添加新的
Region
类型(如城市、地区等),而不需要修改现有的代码。 - 支持递归结构:组合模式使得树形结构的递归实现变得容易,适合表示具有层次结构的对象。
组合模式的缺点
- 增加了系统复杂性:由于组合结构的递归特性,如果层次结构复杂,系统的复杂度会增加。
- 不适合所有场景:如果对象之间没有明显的层级结构,组合模式可能会使得设计变得不必要复杂。
代理模式
- 代理角色:代理类代表另一个对象,并控制对该对象的访问。客户端与代理类交互,而不直接与目标对象(真实对象)交互。
- 真实角色:目标对象,执行实际的业务逻辑。
- 代理类型:
- 静态代理:代理类在编译时就已经存在,代理类和目标类之间的关系是静态的。
- 动态代理:代理类在运行时动态生成,通常通过反射机制创建,目标类和代理类的关系是动态的。
代理模式的常见应用场景
- 延迟加载:代理可以在需要时再创建真实对象,避免不必要的资源消耗。
- 权限控制:代理可以控制对目标对象的访问,确保只有经过授权的操作才能执行。
- 日志记录、性能监控:代理可以在调用目标对象之前或之后添加日志记录、性能监控等功能。
- 远程代理:在分布式系统中,代理可以用来代表远程对象,隐藏远程调用的细节。
示例代码
下面是一个简单的静态代理模式的示例:
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: 代理后的操作
代理模式的优点
- 控制访问:代理对象可以控制对真实对象的访问,增加安全性和权限控制。
- 增加功能:代理类可以在不修改真实对象的前提下,增加额外的功能(如日志记录、事务处理等)。
- 延迟初始化:通过代理模式,可以在实际需要的时候再创建真实对象,节省资源。
- 透明性:代理模式使得客户端代码与真实对象的交互保持一致,客户端不需要关心代理对象的存在。
代理模式的缺点
- 增加系统复杂度:代理模式需要创建额外的代理对象,可能会增加系统的复杂性。
- 性能开销:由于代理对象在方法调用前后可能添加了额外的处理逻辑,可能会带来性能开销。
责任链模式
- 链条结构:多个处理对象(处理者)形成一个链条,每个处理对象处理某个特定的请求或任务。
- 职责划分:每个处理对象只负责自己特定的任务,且每个对象都知道下一个处理对象是哪个。
- 请求传递:请求从链头开始,依次传递给链中的各个处理对象,每个对象可以选择处理请求或将请求传递给链中的下一个对象。
- 解耦:客户端无需知道请求将被哪个处理对象处理,只需要将请求发送到链的开始,其他的交给链中的处理对象。
责任链模式的应用场景
- 日志处理:日志记录时可以通过不同的处理器(如输出到文件、数据库、控制台等)依次传递。
- 权限验证:请求可以在多个权限验证处理器之间传递,直到找到合适的处理器进行处理。
- 事件处理: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.
责任链模式的优点
- 解耦:客户端不需要知道具体的处理者,只需要发送请求,链条中的处理者会依次处理请求。这降低了客户端与具体处理者之间的耦合。
- 动态处理:可以动态地改变链条的处理顺序,增加或移除处理者非常方便。
- 扩展性:可以通过添加新的处理者来扩展功能,符合开闭原则。
责任链模式的缺点
- 性能问题:如果链条过长,或者每个处理者的处理逻辑较复杂,可能会影响性能,因为请求会经过多次处理。
- 调试困难:链条结构可能导致请求在链中传递,调试时难以跟踪请求的流向。
总结
责任链模式通过将多个处理对象连接成一个链条,让请求在链中传递,直到某个处理对象能够处理它为止。每个处理对象可以选择处理请求或将请求转发给下一个处理对象。这种模式不仅解耦了请求的发送者与处理者,还提供了灵活的扩展机制。
建造者模式
没错,建造者模式(Builder Pattern)确实和链式调用(Fluent Interface)有很大的相似之处。它的核心思想就是将复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。
通过链式调用的方式,你可以一步步地为对象设置所需的属性,而不必关心对象具体是如何被构建的。建造者模式的优势在于:
- 解耦构建过程和表示:建造者模式将对象的构建逻辑与业务逻辑分开,便于维护和扩展。
- 链式调用:使代码简洁、直观,调用顺序也更加灵活。
- 代码可读性强:通过链式调用可以清晰地了解一个对象的构建过程。
举个简单的例子:
// 假设我们构建一个 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();
}
}
输出结果
烧水...
泡茶叶...
将饮品倒入杯子...
添加柠檬...
------
烧水...
加热牛奶...
将饮品倒入杯子...
添加糖...
优点:
- 代码复用:通用部分(如烧水、倒入杯子)只需实现一次。
- 扩展性好:添加新类型的饮品只需创建新的子类即可。
- 灵活性:可以灵活地定义和调整每一步的具体实现。
模版模式适合用于具有固定流程但实现细节可变的场景,非常实用!