Bootstrap

java设计模式,英雄联盟的例子学习结构型模式

结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

结构型模式主要关注类或对象的组合,帮助确保如果一个系统的结构发生变化,系统的其他部分不会受到影响。

适配器模式

主要组成部分

  1. 目标接口(Target):客户端所期待的接口。

  2. 源类(Adaptee):需要适配的类,具有与目标接口不兼容的方法。

  3. 适配器(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(),但是我们使用了加速器在中间加速

桥接模式

主要组成部分

  1. 抽象类(Abstraction):定义高层操作的接口。

  2. 扩展抽象类(RefinedAbstraction):对抽象类的具体实现,可能会增加一些新的功能。

  3. 实现接口(Implementor):定义实现类的接口,通常会包含一些基础的方法。

  4. 具体实现类(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(); // 应用剑圣泳池派对皮肤和紫色炫彩特效
    }
}

在例子中泳池派对皮肤,不同的炫彩就不需要创建不同类来表达,避免了类爆炸的问题。

组合模式

主要组成部分

  1. 组件(Component)

    • 声明了叶子节点和组合节点的共同接口,通常是一个抽象类或接口。

  2. 叶子(Leaf)

    • 具体的子类,表示组合中的叶子节点。叶子节点没有子节点,因此通常实现了组件接口中的所有方法。

  3. 组合(Composite)

    • 继承自组件,定义了有子节点的对象的行为。组合可以包含叶子节点或其他组合,提供操作方法来管理其子节点(如添加、删除等)。

使用场景

  1. 文件系统

    • 在文件系统中,文件和文件夹可以使用组合模式表示。文件夹可以包含其他文件夹或文件,客户端可以通过统一的接口处理文件和文件夹。

  2. 图形绘制

    • 在图形编辑器中,形状(如线条、圆形、矩形等)可以组合成复杂的图形。组合模式允许用户以统一的方式处理单个形状和形状组合。

  3. 树形结构

    • 当数据具有树形结构(如组织架构、目录结构等)时,可以使用组合模式来管理部分和整体的关系。

  4. 菜单系统

    • 在用户界面设计中,菜单可以包含子菜单和菜单项。使用组合模式,可以轻松地构建复杂的菜单结构,同时提供一致的操作接口。

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();
    }
}

组合模式就很适合那种需要套娃的数据结构模式。

装饰模式

主要组成部分

  1. 抽象组件(Component)

    • 定义一个接口或抽象类,用于声明对象的行为。

  2. 具体组件(Concrete Component)

    • 实现了抽象组件接口的具体类,表示被装饰的对象。

  3. 装饰类(Decorator)

    • 继承自抽象组件,持有一个指向抽象组件的引用。装饰类可以在调用基本行为之前或之后添加额外的功能。

  4. 具体装饰类(Concrete Decorators)

    • 具体的装饰类,扩展了装饰类,添加具体的功能。

使用场景

  1. 动态增加功能

    • 当需要在运行时为对象动态地添加新功能时,装饰模式非常适用。例如,图形编辑器中可以为形状添加不同的边框或颜色。

  2. 避免类爆炸

    • 当功能的组合可能导致类的数量迅速增加时,可以使用装饰模式。它通过将功能分离到装饰类中,减少了子类的数量。

  3. 增强功能

    • 可以通过装饰模式来增强已有对象的功能,而不需要修改其代码。例如,给一个已有的文本框添加滚动条。

// 抽象组件接口
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());
    }
}

使用另外一个对象来改变整个对象的行为。

外观模式

主要组成部分

  1. 外观类(Facade)

    • 提供一个统一的接口,调用复杂子系统的功能。外观类通常包含对多个子系统的引用。

  2. 子系统类(Subsystem Classes)

    • 具体的功能类,负责实现系统的某些功能。子系统通常不会直接与客户端交互。

使用场景

  1. 简化复杂系统

    • 当系统包含多个类和功能时,使用外观模式可以简化客户端的调用,隐藏复杂性。

  2. 分离接口和实现

    • 当希望将接口与实现分离,使得客户端与子系统的解耦。

  3. 提高可维护性

    • 当系统的某些部分发生变化时,外观模式可以减少对客户端的影响,从而提高系统的可维护性。

// 子系统类
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()方法就可以调用那些用户不需要知道的复杂操作

享元模式

主要组成部分

  1. 享元接口(Flyweight)

    • 定义了可以共享的对象的接口,通常包括一些方法来访问内部状态。

  2. 具体享元(Concrete Flyweight)

    • 实现了享元接口,存储共享状态(内部状态)并可以接受外部状态(非共享状态)作为参数。

  3. 享元工厂(Flyweight Factory)

    • 负责管理享元对象的创建和共享。工厂类确保返回的享元对象是共享的。

使用场景

  1. 需要大量相似对象

    • 在应用中需要创建大量相似的对象,但这些对象的状态大部分是相同的,可以使用享元模式来共享相同的部分。

  2. 节省内存

    • 当对象数量较多时,使用享元模式可以显著减少内存占用,特别是在需要频繁创建和销毁对象的场合。

  3. 提高性能

    • 通过共享相同的对象,可以减少对象创建的时间和内存分配的开销,从而提高性能。

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,说明共享了同一个对象
    }
}

这样子,小兵就不用重复创建和销毁了,可以节约系统资源的损耗

代理模式

主要组成部分

  1. 抽象主题(Subject)

    • 定义了真实主题和代理对象的共同接口。

  2. 真实主题(RealSubject)

    • 实现了抽象主题接口,代表实际的业务对象。

  3. 代理(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();
    }
}

代码解析

  1. Image 接口:定义了显示图像的方法。

  2. RealImage 类:实现了 Image 接口,负责实际图像的加载和显示。

  3. ProxyImage 类:实现了 Image 接口,持有对 RealImage 的引用,控制图像的加载和显示。

  4. Main 类:客户端代码,使用代理对象来展示图像,真实图像在首次调用时才加载。

输出结果

当运行上述代码时,输出结果将是:

加载图像: image1.jpg
显示图像: image1.jpg
显示图像: image1.jpg
加载图像: image2.jpg
显示图像: image2.jpg

;