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
相关推荐
feilieren10 分钟前
leetcode - 684. 冗余连接
java·开发语言·算法
The Future is mine21 分钟前
Java根据word模板导出数据
java·开发语言
一颗甜苞谷34 分钟前
开源一款前后端分离的企业级网站内容管理系统,支持站群管理、多平台静态化,多语言、全文检索的源码
java·开发语言·开源
星夜孤帆34 分钟前
Java面试题集锦
java·开发语言
论迹42 分钟前
【Java】-- 接口
java·开发语言
dawn1912282 小时前
Java 中的正则表达式详解
java·开发语言·算法·正则表达式·1024程序员节
葉A子2 小时前
poi处理excel文档时,与lombok的@Accessors(chain = true)注解冲突
java
鱼跃鹰飞2 小时前
大厂面试真题-简单描述一下SpringBoot的启动过程
java·spring boot·后端·spring·面试
大只因bug2 小时前
基于Springboot的在线考试与学习交流平台的设计与实现
java·spring boot·后端·学习·mysql·vue·在线考试与学习交流平台系统
想进大厂的小王2 小时前
Spring Boot⾃动配置
java·spring boot·后端