结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
结构型模式主要关注类或对象的组合,帮助确保如果一个系统的结构发生变化,系统的其他部分不会受到影响。
适配器模式
主要组成部分
-
目标接口(Target):客户端所期待的接口。
-
源类(Adaptee):需要适配的类,具有与目标接口不兼容的方法。
-
适配器(Adapter):实现目标接口,并持有一个源类的实例,负责将源类的调用转换为目标接口的调用。
使用场景
-
当你想使用一些现有的类,但它们的接口不符合你的需求时。
-
系统需要与一些不兼容的接口交互。
-
当需要将多个不相关的类组合成一个统一的接口时。
在我们玩游戏去外服玩的时候,就需要用到一个叫做加速器的东西,不用的话就会很卡,我们用适配器模式来编写一遍
// Game 接口
interface Game {
void start();
}
// SteamGame 类
class SteamGame implements Game {
@Override
public void start() {
System.out.println("Starting game on Steam.");
}
}
// VPNAccelerator 类
class VPNAccelerator {
public void connect() {
System.out.println("Connecting to VPN...");
}
public void accelerate() {
System.out.println("Accelerating game speed.");
}
}
// Adapter 类
class VPNAcceleratorAdapter implements Game {
private VPNAccelerator vpnAccelerator;
public VPNAcceleratorAdapter(VPNAccelerator vpnAccelerator) {
this.vpnAccelerator = vpnAccelerator;
}
@Override
public void start() {
vpnAccelerator.connect();
vpnAccelerator.accelerate();
System.out.println("Game started with accelerator.");
}
}
// 测试
public class AdapterPatternDemo {
public static void main(String[] args) {
Game steamGame = new SteamGame();
steamGame.start();
VPNAccelerator vpnAccelerator = new VPNAccelerator();
Game vpnAcceleratorAdapter = new VPNAcceleratorAdapter(vpnAccelerator);
vpnAcceleratorAdapter.start();
}
}
这样子做同样是开启游戏的start(),但是我们使用了加速器在中间加速
桥接模式
主要组成部分
-
抽象类(Abstraction):定义高层操作的接口。
-
扩展抽象类(RefinedAbstraction):对抽象类的具体实现,可能会增加一些新的功能。
-
实现接口(Implementor):定义实现类的接口,通常会包含一些基础的方法。
-
具体实现类(ConcreteImplementor):实现实现接口的具体类。
使用场景
-
当你希望在抽象和实现之间进行解耦,以便能够独立地改变它们。
-
当你希望可以在运行时选择实现,或者需要组合多个实现。
当英雄联盟里各个英雄的皮肤的炫彩特效就可以使用桥接模式设计
// 抽象特效接口
interface Effect {
void apply();
}
// 具体的特效实现:蓝色炫彩
class BlueEffect implements Effect {
@Override
public void apply() {
System.out.println("应用蓝色炫彩特效");
}
}
// 具体的特效实现:紫色炫彩
class PurpleEffect implements Effect {
@Override
public void apply() {
System.out.println("应用紫色炫彩特效");
}
}
// 抽象皮肤类
abstract class Skin {
protected Effect effect;
public Skin(Effect effect) {
this.effect = effect;
}
public abstract void applySkin();
}
// 具体的皮肤实现:剑圣泳池派对
class PoolPartyYasuo extends Skin {
public PoolPartyYasuo(Effect effect) {
super(effect);
}
@Override
public void applySkin() {
System.out.println("应用剑圣泳池派对皮肤");
effect.apply();
}
}
// 客户端代码
public class Main {
public static void main(String[] args) {
Skin bluePoolPartyYasuo = new PoolPartyYasuo(new BlueEffect());
bluePoolPartyYasuo.applySkin(); // 应用剑圣泳池派对皮肤和蓝色炫彩特效
Skin purplePoolPartyYasuo = new PoolPartyYasuo(new PurpleEffect());
purplePoolPartyYasuo.applySkin(); // 应用剑圣泳池派对皮肤和紫色炫彩特效
}
}
在例子中泳池派对皮肤,不同的炫彩就不需要创建不同类来表达,避免了类爆炸的问题。
组合模式
主要组成部分
-
组件(Component):
-
声明了叶子节点和组合节点的共同接口,通常是一个抽象类或接口。
-
-
叶子(Leaf):
-
具体的子类,表示组合中的叶子节点。叶子节点没有子节点,因此通常实现了组件接口中的所有方法。
-
-
组合(Composite):
-
继承自组件,定义了有子节点的对象的行为。组合可以包含叶子节点或其他组合,提供操作方法来管理其子节点(如添加、删除等)。
-
使用场景
-
文件系统:
-
在文件系统中,文件和文件夹可以使用组合模式表示。文件夹可以包含其他文件夹或文件,客户端可以通过统一的接口处理文件和文件夹。
-
-
图形绘制:
-
在图形编辑器中,形状(如线条、圆形、矩形等)可以组合成复杂的图形。组合模式允许用户以统一的方式处理单个形状和形状组合。
-
-
树形结构:
-
当数据具有树形结构(如组织架构、目录结构等)时,可以使用组合模式来管理部分和整体的关系。
-
-
菜单系统:
-
在用户界面设计中,菜单可以包含子菜单和菜单项。使用组合模式,可以轻松地构建复杂的菜单结构,同时提供一致的操作接口。
-
import java.util.ArrayList;
import java.util.List;
// 组件接口
interface FileSystemComponent {
void display();
}
// 叶子类:文件
class File implements FileSystemComponent {
private String name;
public File(String name) {
this.name = name;
}
@Override
public void display() {
System.out.println("文件: " + name);
}
}
// 组合类:文件夹
class Folder implements FileSystemComponent {
private String name;
private List<FileSystemComponent> components = new ArrayList<>();
public Folder(String name) {
this.name = name;
}
public void add(FileSystemComponent component) {
components.add(component);
}
public void remove(FileSystemComponent component) {
components.remove(component);
}
@Override
public void display() {
System.out.println("文件夹: " + name);
for (FileSystemComponent component : components) {
component.display();
}
}
}
// 客户端代码
public class Main {
public static void main(String[] args) {
// 创建文件
File file1 = new File("文档1.txt");
File file2 = new File("图片1.jpg");
// 创建文件夹
Folder folder1 = new Folder("文件夹1");
folder1.add(file1);
folder1.add(file2);
// 创建一个更大的文件夹
Folder rootFolder = new Folder("根文件夹");
rootFolder.add(folder1);
// 显示文件系统结构
rootFolder.display();
}
}
组合模式就很适合那种需要套娃的数据结构模式。
装饰模式
主要组成部分
-
抽象组件(Component):
-
定义一个接口或抽象类,用于声明对象的行为。
-
-
具体组件(Concrete Component):
-
实现了抽象组件接口的具体类,表示被装饰的对象。
-
-
装饰类(Decorator):
-
继承自抽象组件,持有一个指向抽象组件的引用。装饰类可以在调用基本行为之前或之后添加额外的功能。
-
-
具体装饰类(Concrete Decorators):
-
具体的装饰类,扩展了装饰类,添加具体的功能。
-
使用场景
-
动态增加功能:
-
当需要在运行时为对象动态地添加新功能时,装饰模式非常适用。例如,图形编辑器中可以为形状添加不同的边框或颜色。
-
-
避免类爆炸:
-
当功能的组合可能导致类的数量迅速增加时,可以使用装饰模式。它通过将功能分离到装饰类中,减少了子类的数量。
-
-
增强功能:
-
可以通过装饰模式来增强已有对象的功能,而不需要修改其代码。例如,给一个已有的文本框添加滚动条。
-
// 抽象组件接口
interface Attack {
String getDescription();
double getDamage();
}
// 具体组件:基础攻击
class BasicAttack implements Attack {
@Override
public String getDescription() {
return "基础攻击";
}
@Override
public double getDamage() {
return 50.0; // 基础攻击伤害
}
}
// 装饰类
abstract class AttackDecorator implements Attack {
protected Attack attack;
public AttackDecorator(Attack attack) {
this.attack = attack;
}
@Override
public String getDescription() {
return attack.getDescription();
}
@Override
public double getDamage() {
return attack.getDamage();
}
}
// 具体装饰类:长剑
class LongSwordDecorator extends AttackDecorator {
public LongSwordDecorator(Attack attack) {
super(attack);
}
@Override
public String getDescription() {
return attack.getDescription() + ", 加长剑";
}
@Override
public double getDamage() {
return attack.getDamage() + 10.0; // 长剑增加10点伤害
}
}
// 具体装饰类:破败
class BladeOfTheRuinedKingDecorator extends AttackDecorator {
public BladeOfTheRuinedKingDecorator(Attack attack) {
super(attack);
}
@Override
public String getDescription() {
return attack.getDescription() + ", 加破败";
}
@Override
public double getDamage() {
return attack.getDamage() + 20.0; // 破败增加20点伤害
}
}
// 客户端代码
public class Main {
public static void main(String[] args) {
Attack basicAttack = new BasicAttack();
System.out.println(basicAttack.getDescription() + " 伤害: " + basicAttack.getDamage());
// 添加长剑
Attack longSwordAttack = new LongSwordDecorator(basicAttack);
System.out.println(longSwordAttack.getDescription() + " 伤害: " + longSwordAttack.getDamage());
// 添加破败
Attack finalAttack = new BladeOfTheRuinedKingDecorator(longSwordAttack);
System.out.println(finalAttack.getDescription() + " 伤害: " + finalAttack.getDamage());
}
}
使用另外一个对象来改变整个对象的行为。
外观模式
主要组成部分
-
外观类(Facade):
-
提供一个统一的接口,调用复杂子系统的功能。外观类通常包含对多个子系统的引用。
-
-
子系统类(Subsystem Classes):
-
具体的功能类,负责实现系统的某些功能。子系统通常不会直接与客户端交互。
-
使用场景
-
简化复杂系统:
-
当系统包含多个类和功能时,使用外观模式可以简化客户端的调用,隐藏复杂性。
-
-
分离接口和实现:
-
当希望将接口与实现分离,使得客户端与子系统的解耦。
-
-
提高可维护性:
-
当系统的某些部分发生变化时,外观模式可以减少对客户端的影响,从而提高系统的可维护性。
-
// 子系统类
class CPU {
public void freeze() {
System.out.println("CPU 冻结");
}
public void jump(long position) {
System.out.println("CPU 跳转到 " + position);
}
public void execute() {
System.out.println("CPU 执行");
}
}
// 子系统类
class Memory {
public void load(long position, byte[] data) {
System.out.println("内存加载数据到 " + position);
}
}
// 子系统类
class HardDrive {
public byte[] read(long lba, int size) {
System.out.println("从硬盘读取数据,LBA: " + lba + ", 大小: " + size);
return new byte[size];
}
}
// 外观类
class Computer {
private CPU cpu;
private Memory memory;
private HardDrive hardDrive;
public Computer() {
cpu = new CPU();
memory = new Memory();
hardDrive = new HardDrive();
}
public void start() {
cpu.freeze();
memory.load(0, hardDrive.read(0, 1024));
cpu.jump(0);
cpu.execute();
}
}
// 客户端代码
public class Main {
public static void main(String[] args) {
Computer computer = new Computer();
computer.start(); // 启动计算机
}
}
我们只需要暴露出一个start()方法就可以调用那些用户不需要知道的复杂操作
享元模式
主要组成部分
-
享元接口(Flyweight):
-
定义了可以共享的对象的接口,通常包括一些方法来访问内部状态。
-
-
具体享元(Concrete Flyweight):
-
实现了享元接口,存储共享状态(内部状态)并可以接受外部状态(非共享状态)作为参数。
-
-
享元工厂(Flyweight Factory):
-
负责管理享元对象的创建和共享。工厂类确保返回的享元对象是共享的。
-
使用场景
-
需要大量相似对象:
-
在应用中需要创建大量相似的对象,但这些对象的状态大部分是相同的,可以使用享元模式来共享相同的部分。
-
-
节省内存:
-
当对象数量较多时,使用享元模式可以显著减少内存占用,特别是在需要频繁创建和销毁对象的场合。
-
-
提高性能:
-
通过共享相同的对象,可以减少对象创建的时间和内存分配的开销,从而提高性能。
-
import java.util.HashMap;
import java.util.Map;
// 享元接口
interface MinionType {
void display(int x, int y);
}
// 具体享元类:小兵类型
class Minion implements MinionType {
private String type; // 小兵类型(如 melee, ranged)
private String skin; // 皮肤(如普通小兵、超级小兵)
public Minion(String type, String skin) {
this.type = type;
this.skin = skin;
}
@Override
public void display(int x, int y) {
System.out.println("小兵类型: " + type + " | 皮肤: " + skin + " | 坐标: (" + x + ", " + y + ")");
}
}
// 享元工厂
class MinionFactory {
private Map<String, MinionType> minionTypes = new HashMap<>();
public MinionType getMinionType(String type, String skin) {
String key = type + "-" + skin;
MinionType minionType = minionTypes.get(key);
if (minionType == null) {
minionType = new Minion(type, skin);
minionTypes.put(key, minionType);
}
return minionType;
}
}
// 客户端代码
public class Main {
public static void main(String[] args) {
MinionFactory minionFactory = new MinionFactory();
// 创建不同类型的小兵,使用享元模式共享小兵的类型
MinionType meleeMinion = minionFactory.getMinionType("近战", "普通");
meleeMinion.display(10, 20);
MinionType rangedMinion = minionFactory.getMinionType("远程", "普通");
rangedMinion.display(15, 25);
// 重复获取相同类型的小兵,应该返回同一个实例
MinionType anotherMeleeMinion = minionFactory.getMinionType("近战", "普通");
anotherMeleeMinion.display(30, 40);
System.out.println(meleeMinion == anotherMeleeMinion); // 输出: true,说明共享了同一个对象
}
}
这样子,小兵就不用重复创建和销毁了,可以节约系统资源的损耗
代理模式
主要组成部分
-
抽象主题(Subject):
-
定义了真实主题和代理对象的共同接口。
-
-
真实主题(RealSubject):
-
实现了抽象主题接口,代表实际的业务对象。
-
-
代理(Proxy):
-
也实现了抽象主题接口,并持有一个真实主题的引用。代理对象可以在调用真实主题的方法时添加额外的行为(如权限检查、日志记录等)。
-
// 抽象主题
interface Image {
void display();
}
// 真实主题
class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadImageFromDisk();
}
private void loadImageFromDisk() {
System.out.println("加载图像: " + filename);
}
@Override
public void display() {
System.out.println("显示图像: " + filename);
}
}
// 代理
class ProxyImage implements Image {
private RealImage realImage;
private String filename;
public ProxyImage(String filename) {
this.filename = filename;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(filename);
}
realImage.display();
}
}
// 客户端代码
public class Main {
public static void main(String[] args) {
Image image1 = new ProxyImage("image1.jpg");
Image image2 = new ProxyImage("image2.jpg");
// 图像加载只在第一次调用时发生
image1.display();
image1.display(); // 不会重复加载
image2.display();
}
}
代码解析
-
Image 接口:定义了显示图像的方法。
-
RealImage 类:实现了
Image
接口,负责实际图像的加载和显示。 -
ProxyImage 类:实现了
Image
接口,持有对RealImage
的引用,控制图像的加载和显示。 -
Main 类:客户端代码,使用代理对象来展示图像,真实图像在首次调用时才加载。
输出结果
当运行上述代码时,输出结果将是:
加载图像: image1.jpg
显示图像: image1.jpg
显示图像: image1.jpg
加载图像: image2.jpg
显示图像: image2.jpg