【设计模式】- 结构型模式

news2025/5/17 19:25:14

代理模式

给目标对象提供一个代理以控制对该对象的访问。外界如果需要访问目标对象,需要去访问代理对象。

分类:

  1. 静态代理:代理类在编译时期生成
  2. 动态代理:代理类在java运行时生成
    • JDK代理
    • CGLib代理

主要角色】:

  • 抽象主题类(Subject):通过接口或抽象类声明真实主题和代理对象
  • 真实主题类(Real Subject):实现抽象主题中的具体业务,是最终要引用的对象
  • 代理类(Proxy):提供了与真实主题相同的接口,内部含有对真实主题的引用

静态代理

火车站买票】:要买火车票需要去火车站买票。火车站是目标对象,代售点是代理对象。

卖票(抽象主题类):

public interface SellTickets {
    void sell();
}

火车站卖票(具体主题类):

public class TrainStation implements SellTickets {
    @Override
    public void sell() {
        System.out.println("火车站卖票");
    }
}

代售点类(代理类):

public class ProxyPoint implements SellTickets {
    /*火车站类对象*/
    private TrainStation trainStation = new TrainStation();
    @Override
    public void sell() {
        System.out.println("代售点收取服务费用");
        trainStation.sell(); // 代售点卖票其实调用的还是火车站里的sell()方法
    }
}

测试类:

public class Client {
    public static void main(String[] args) {
        // 1. 创建代售点类对象
        ProxyPoint proxyPoint = new ProxyPoint();
        // 2. 调用方法进行买票
        proxyPoint.sell();
    }
}

代售点作为访问对象和目标对象的中介,避免了访问类直接访问目标对象

JDK动态代理

代理对象工厂(获取代理对象):

public class ProxyFactory {
    /*目标对象*/
    private TrainStation station = new TrainStation();
    /*创建代理对象*/

    public SellTickets getProxyObject() {
        /*
            获取代理对象的方法
            ClassLoader loader:类加载器,用于加载代理类(可以通过目标对象获取类加载器)
            Class<?>[] interfaces:代理类实现的接口的字节码对象
            InvocationHandler h:代理对象的调用处理程序(外界使用使用代理对象调用方法,实际上就是调用这里的invoke()方法)
        */
        SellTickets proxyObject = (SellTickets) Proxy.newProxyInstance(
                station.getClass().getClassLoader(),
                station.getClass().getInterfaces(),
                new InvocationHandler() {
                    /*
                        Object proxy:代理对象,和proxyObject是同一个对象(在invoke()方法中基本不用)
                        Method method:对接口中的方法进行封装的method对象
                        Object[] args:调用方法的实际参数(方法如果有)
                        返回值:就是方法的返回值(没有返回值,就是返回null)
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("代售点收取一定的服务费用(jdk动态代理)");
                        // 执行目标对象的方法
                        Object obj = method.invoke(station, args);
                        return obj;
                    }
                }
        );
        return proxyObject;
    }
}

测试类:

public class Client {
    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory();
        SellTickets proxyObject = proxyFactory.getProxyObject();
        proxyObject.sell();
    }
}

CGLIB动态代理

如果没有定义SellTickets,只定义了TrainStation,JDK代理就无法使用了。(因为JDK代理必须要求定义接口,对接口进行代理)

CGLIB:功能强大,高性能的代码生成包,为没有实现接口的类提供代理,为JDK的动态代理提供很好的补充

<dependency>
   <groupId>cglib</groupId>
   <artifactId>cglib</artifactId>
   <version>2.2.2</version>
 </dependency>

火车站类(没有实现SellTickets接口):

public class TrainStation {
    public void sell() {
        System.out.println("火车站卖票");
    }
}

代理对象工厂(获取代理对象):

public class ProxyFactory implements MethodInterceptor {
    /*火车站对象*/
    private TrainStation station = new TrainStation();
    public TrainStation getProxyObject() {
        // 1. 创建Enhancer对象,类似于JDK代理中的Proxy类
        Enhancer enhancer = new Enhancer();
        // 2. 设置父类的字节码(CGLIB的代理类属于目标类的子类)
        enhancer.setSuperclass(TrainStation.class);
        // 3. 设置回调函数
        enhancer.setCallback(this);
        // 4. 创建代理对象
        TrainStation proxyObject = (TrainStation) enhancer.create();
        return proxyObject;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        // 调用目标对象的方法
        System.out.println("代售点收取一定的费用(CGLIB代理)");
        Object obj = method.invoke(station, objects); // 调用sell()方法
        return obj;
    }
}

CGLIB的代理类属于:目标类的子类

测试类:

public class Client {
    public static void main(String[] args) {
        // 1. 创建代理工厂对象
        ProxyFactory proxyFactory = new ProxyFactory();
        // 2. 获取代理对象
        TrainStation proxyObject = proxyFactory.getProxyObject();
        // 3. 调用代理对象中的sell()方法卖票
        proxyObject.sell();
    }
}

三种代理的对比

JDK代理 vs CGLIB代理

CGLIB:不能对声明为final的类或方法进行代理(CGLIB原理是动态生成被代理的子类)
JDK代理效率高于CGLIB代理。
原理】如果有接口使用JDK动态代理,没有接口使用CGLIB代理

动态代理 vs 静态代理

动态代理:类或接口中声明的所有方法都被转移到调用的一个集中的方法中处理,接口数量较多的时候,可以灵活处理
静态代理:如果接口增加了一个方法,除了所有实现类要实现这个方法,所有的代理类也需要实现这个方法

使用场景

  1. 远程代理:本地服务通过网络请求远程服务,我们可以将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,不必过多关心通信的细节(RPC)
  2. 防火墙:当浏览器配置成使用代理功能时,防火墙就会将浏览器的请求转发给互联网,互联网返回响应时,服务器再把它转到浏览器。
  3. 保护代理:控制一个对象的访问,如果需要可以给不同的用户提供不同级别的使用权限。

适配器模式

将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。

主要角色】:

  • 目标接口(Target):当前业务系统锁期待的接口
  • 适配者类(Adaptee):被访问和适配的现存组件库的组件接口
  • 适配器类(Adapter):转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者

类适配器模式(应用少)

场景分析】:有一台电脑只能读取SD卡,如果要读取TF卡中的内容就需要用到适配器模式,创建一个读卡器,将TF卡中的内容读取出来。

目标接口(SD卡):

public interface SDCard {
    /*从SD卡中读取数据*/
    String readSD();
    /*往SD卡中写数据*/
    void writeSD(String msg);
}

public class SDCardImpl implements SDCard {
    @Override
    public String readSD() {
        return "SDCard read msg : hello world";
    }

    @Override
    public void writeSD(String msg) {
        System.out.println("SDCard write msg : " + msg);
    }
}

适配者类(TF卡):

public interface TFCard {
    /*从TF卡中读取数据*/
    String readTF();
    /*往TF卡中写数据*/
    void writeTF(String msg);
}

public class TFCardImpl implements TFCard {
    @Override
    public String readTF() {
        return "TFCard read msg : hello world";
    }

    @Override
    public void writeTF(String msg) {
        System.out.println("TFCard write msg : " + msg);
    }
}

计算机类:

public class Computer {
    /*从SD卡中读取数据*/
    public String readSD(SDCard sdCard) {
        Assert.isTrue(sdCard != null, ()-> new NullPointerException("sdCard is not null"));
        return sdCard.readSD();
    }
}

适配器类(继承适配者类,实现目标类接口):

public class SDAdapterTF extends TFCardImpl implements SDCard { // 继承适配者类,实现目标类接口
    @Override
    public String readSD() {
        System.out.println("adapter read TFCard");
        return readTF(); // TFCardImpl里实现的方法
    }

    @Override
    public void writeSD(String msg) {
        System.out.println("adapter write TFCard");
        writeTF(msg);
    }
}

测试类:

public class Client {
    public static void main(String[] args) {
        // 1. 创建计算机对象
        Computer computer = new Computer();
        // 2. 读取sd卡中的数据
        String msg = computer.readSD(new SDCardImpl());
        System.out.println(msg);
        System.out.println("===========");
        // 3. 使用该电脑读取TF卡中的数据
        SDAdapterTF sdAdapterTF = new SDAdapterTF();
        System.out.println(sdAdapterTF.readSD());
    }
}

局限性:由上边适配器的代码可知,适配器类需要继承适配者类,实现目标类接口。但是如果目标类没有接口只有一个类,而java又是单继承,就无法使用类适配器模式。所以实际多使用对象适配器模式

对象适配器模式(应用多)

对象适配器模式和类适配器模式基本一样,主要是在适配器类的不同。

  • 类适配器模式:集成适配者类
  • 对象适配器模式:聚合适配者对象

适配器类:

public class SDAdapterTF implements SDCard { // 继承适配器类,实现目标类接口
    private TFCard tfCard;
    public SDAdapterTF(TFCard tfCard) {
        this.tfCard = tfCard;
    }
    @Override
    public String readSD() {
        System.out.println("adapter read TFCard");
        return tfCard.readTF(); // 通过成员变量调用
    }

    @Override
    public void writeSD(String msg) {
        System.out.println("adapter write TFCard");
        tfCard.writeTF(msg);
    }
}

测试类:

public class Client {
    public static void main(String[] args) {
        // 1. 创建计算机对象
        Computer computer = new Computer();
        // 2. 读取sd卡中的数据
        String msg = computer.readSD(new SDCardImpl());
        System.out.println(msg);
        System.out.println("===========");
        // 3. 使用该电脑读取TF卡中的数据
        SDAdapterTF sdAdapterTF = new SDAdapterTF(new TFCardImpl());
        System.out.println(computer.readSD(sdAdapterTF));
    }
}

使用场景

  1. 以前开发的系统存在满足新系统功能需求的类,但是接口和新系统不一致
  2. 使用第三方提供的组件,但是接口定义和自己要求的接口定义不同

装饰者模式

在不改变现有对象的情况下,动态的给该对象添加一些职责的模式(添加额外功能)

主要角色】:

  • 抽象构建角色(Component):定义一个抽象接口以规范准备接收附加责任的对象
  • 具体构建角色(Concrete Component):实现抽象构建,通过装饰角色为其添加一些职责
  • 抽象装饰角色(Decorator):集成或实现抽象构建,并包含具体构建的实例,可以通过其子类扩展具体构建的功能
  • 具体装饰角色(COncrete Decorator):实现抽象装饰的相关方法,并给具体构建对象添加附加的责任

案例:快餐店

快餐类(抽象构建角色):

@Data
@AllArgsConstructor
@NoArgsConstructor
public abstract class FastFood {
    /*价格*/
    private float price;
    /*描述*/
    private String desc;
    /*计算价格*/
    public abstract float cost();
}

炒面、炒饭(具体构建角色)

public class FriedNoodles extends FastFood {
    public FriedNoodles() {
        super(12, "炒面");
    }
    @Override
    public float cost() {
        return getPrice();
    }
}

public class FriedRice extends FastFood {
    public FriedRice() {
        super(10, "炒饭");
    }
    @Override
    public float cost() {
        return getPrice();
    }
}

修饰类(抽象修饰者类):

@Data
public abstract class Garnish extends FastFood {
    private FastFood fastFood;
    public Garnish(FastFood fastFood, float price, String desc) {
        super(price, desc);
        this.fastFood = fastFood;
    }
}

鸡蛋、培根(具体修饰者类):

public class Egg extends Garnish {
    public Egg(FastFood fastFood) {
        super(fastFood, 1, "鸡蛋");
    }
    @Override
    public float cost() {
        return getPrice() + getFastFood().cost();
    }

    @Override
    public String getDesc() {
        return super.getDesc() + getFastFood().getDesc();
    }
}

public class Bacon extends Garnish{
    public Bacon(FastFood fastFood) {
        super(fastFood, 2, "培根");
    }
    @Override
    public float cost() {
        return getPrice() + getFastFood().cost();
    }

    @Override
    public String getDesc() {
        return super.getDesc() + getFastFood().getDesc();
    }
}

测试类:

public class Client {
    public static void main(String[] args) {
        // 1. 点一份炒饭
        FastFood food = new FriedRice();
        System.out.println(food.getDesc() + food.cost());
        // 2. 在炒饭里加鸡蛋
        food = new Egg(food);
        System.out.println(food.getDesc() + food.cost());
        // 3. 在炒饭里再加一个鸡蛋
        food = new Egg(food);
        System.out.println(food.getDesc() + food.cost());
        // 4. 在炒饭里再加一个培根
        food = new Bacon(food);
        System.out.println(food.getDesc() + food.cost());
    }
}

使用场景

装饰者模式是继承的一个替代模式,可以动态扩展一个实现类的功能

  1. 不能使用继承的方式对系统进行扩充。其中,不能采用继承的情况:
    • 系统中存在需要大量独立的扩展,为支持每一种组合将产生大量的子类,使子类数目呈爆炸性增长
    • 类不能被继承(被final修饰)
  2. 不影响其他对象的情况下,可以动态的给单个对象添加职责
  3. 对象的功能要求可以动态的添加、撤销时

静态代理 vs 装饰者模式

相同点:

  • 都要实现与目标类相同的业务接口
  • 在两个类中都要声明目标对象
  • 都可以在不修改目标类的前提下增强目标方法

不同点:

  • 装饰者模式是为了增强对象;静态代理是为了保护和隐藏目标对象
  • 装饰者获取对象的方式是通过外界传递过来(构造方法参数);静态代理是在代理类内部创建(new 实现类对象),来隐藏目标对象

桥接模式

在这里插入图片描述
问题】:圆、长方形、正方形都有黑色、灰色、白色三种颜色。如果新增一个颜色,需要新增好多实现类。

将抽象和实现分离,使他们可以独立变化。用组合关系代替继承关系。

比如上边的三种图形,抽象和实现就是:图形和颜色这两种不同维度的变化。

主要角色】:

  • 抽象化角色(Abstraction):定义一个抽象类,并包含一个对实现化对象的引用
  • 扩展抽象化角色(Refined Abstraction):抽象化角色的子类,是实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法
  • 实现化角色(Implementor):定义实现化角色的接口,供扩展抽象化角色调用
  • 具体实现化角色(Concrete Implementor):给出实现化角色接口的具体实现

案例:视频播放器

需求】:需要一个跨平台的视频播放器,可以在不同操作系统平台(windows、mac)上播放多种格式(rmvb、avi)的视频

视频文件格式(实现化角色):

public interface VideoFile {
    /*解码功能*/
    void decode(String fileName);
}

rmvb、avi视频格式文件(具体实现化角色):

public class RmvbFile implements VideoFile {
    @Override
    public void decode(String fileName) {
        System.out.println("rmvb视频文件:" + fileName);
    }
}

public class AviFile implements VideoFile {
    @Override
    public void decode(String fileName) {
        System.out.println("avi视频文件:" + fileName);
    }
}

抽象的操作系统类(抽象化角色):

@AllArgsConstructor
public abstract class OpratingSystem {
    protected VideoFile videoFile;
    public abstract void play(String fileName);
}

mac、windows操作系统(扩展抽象化角色):

public class Mac extends OpratingSystem {
    public Mac(VideoFile videoFile) {
        super(videoFile);
    }
    @Override
    public void play(String fileName) {
        videoFile.decode(fileName);
    }
}

public class Windows extends OpratingSystem {
    public Windows(VideoFile videoFile) {
        super(videoFile);
    }
    @Override
    public void play(String fileName) {
        videoFile.decode(fileName);
    }
}

桥接模式提高了系统的可扩展性,在两个变化的维度中任意扩展一个维度,都不需要修改原系统。
比如我添加了一个linux操作系统,只需要新增一个Linux类,继承OpratingSystem,不需要修改原有类

使用场景

  1. 一个类存在两个独立变化的维度,且这两个维度都要进行扩展
  2. 当一个系统不希望使用继承或因多层次继承导致系统类的个数急剧增加时
  3. 当一个系统需要在构建的抽象化角色和具体角色之间增加更多的灵活性时,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使他们在抽象层建立一个关联关系。

外观模式

通过为多个复杂子系统提供一个统一接口,该模式对外由一个统一接口,外部应用程序不需要关心内部子系统的具体细节。

外观模式是“迪米特法则”的典型应用

主要角色】:

  • 外观角色(Facade):为多个子系统对外提供一个共同的接口
  • 子系统角色(Sub System):实现系统的部分功能,客户可以通过外观角色访问它

案例:智能家电控制

电灯类、电视机类、空调类(子系统角色):

public class Light {
    /*开灯*/
    public void on() {
        System.out.println("打开电灯");
    }
    /*关灯*/
    public void off() {
        System.out.println("关闭电灯");
    }
}

public class Tv {
    /*开启电视机*/
    public void on() {
        System.out.println("开启电视机");
    }
    /*关闭电视机*/
    public void off() {
        System.out.println("关闭电视机");
    }
}

public class AirCondition {
    /*开启空调*/
    public void on() {
        System.out.println("开启空调");
    }
    /*关闭空调*/
    public void off() {
        System.out.println("关闭空调");
    }
}

智能家电类(外观类,用户主要和该类对象进行交互)

public class SmartAppliancesFacade {
    // 聚合电灯对象、电视机对象、空调对象
    private Light light;
    private Tv tv;
    private AirCondition airCondition;
    public SmartAppliancesFacade() {
        light = new Light();
        tv = new Tv();
        airCondition = new AirCondition();
    }
    // 通过语音控制
    public void say(String message) {
        if (message.contains("打开")) {
            on();
        }else if(message.contains("关闭")) {
            off();
        } else {
            System.out.println("我还听不懂你说的");
        }
    }

    /**
     * 一键打开
     */
    private void on() {
        light.on();
        tv.on();
        airCondition.on();
    }
    /**
     * 一键关闭
     */
    private void off() {
        light.off();
        tv.off();
        airCondition.off();
    }
}

测试类:

public class Client {
    public static void main(String[] args) {
        // 1. 创建智能音箱对象
        SmartAppliancesFacade facade = new SmartAppliancesFacade();
        // 2. 控制家电
        facade.say("打开家电");
    }
}
  1. 降低子系统和客户端之间的耦合,使子系统的变化不会影响调用它的客户类
  2. 对客户屏蔽了子系统,减少了子系统处理的对象数目

使用场景

  1. 对分层结构系统构建时,使用外观模式定义子系统中每层的入口可以简化子系统之间的依赖关系
  2. 当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界调用(框架就是这样做的,底层很多方法,但是给我们使用就只需要一个方法)
  3. 当客户端与多个子系统存在很大联系时,使用外观模式可以将他们分离

组合模式

组合模式用于把一组相似的对象当作单一的对象,组合模式依据树形结构来组合对象,用来表示部分和整体的层次。

主要角色】:

  • 抽象根节点(Component):定义系统各层次对象的共有属性和方法
  • 树枝节点(Composite):存储子节点,组合树枝节点和叶子节点形成一个树形结构
  • 叶子节点(Leaf):叶子节点对象下再无分支,是系统遍历的最小单位

案例:软件菜单

需求】:在访问管理系统时,一个菜单可能包含菜单项(叶子节点),也可能包含带有其他菜单项的菜单(树枝节点)。
在这里插入图片描述
菜单组件(抽象根节点):

@AllArgsConstructor
public abstract class MenuComponent {
    /*菜单组件的名称*/
    protected String name;
    /*菜单组件的层级*/
    protected int level;
    /*添加子菜单(菜单项不能添加)*/
    public void add(MenuComponent component) {
        throw new UnsupportedOperationException();
    }
    /*移除子菜单*/
    public void remove(MenuComponent component) {
        throw new UnsupportedOperationException();
    }
    /*获取指定的子菜单*/
    public MenuComponent getChild(int idx) {
        throw new UnsupportedOperationException();
    }
    /*获取菜单或菜单项的名称*/
    public String getName() {
        return name;
    }
    /*打印菜单项名称(包含菜单项和子菜单项)*/
    public abstract void print();
}

菜单项类(叶子节点):

public class MenuItem extends MenuComponent {

    public MenuItem(String name, int level) {
        super(name, level);
    }
    @Override
    public void print() {
        for(int i = 0; i < level; i++) {
            System.out.print("*");
        }
        System.out.println(name);
    }
}

菜单类(树枝节点):

public class Menu extends MenuComponent {
    /*菜单可以有多个子菜单 或 子菜单项*/
    private List<MenuComponent> menuComponentList = new ArrayList<>();

    public Menu(String name, int level) {
        super(name, level);
    }

    @Override
    public void add(MenuComponent component) {
        menuComponentList.add(component);
    }

    @Override
    public void remove(MenuComponent component) {
        menuComponentList.remove(component);
    }

    @Override
    public MenuComponent getChild(int idx) {
        return menuComponentList.get(idx);
    }

    @Override
    public void print() {
        // 打印菜单名称
        for(int i = 1; i < level; i++) {
            System.out.print("*");
        }
        System.out.println(name);
        // 打印子菜单 或 子菜单项名称
        menuComponentList.forEach(menu -> {
            menu.print();
        });
    }
}

测试类:

public class Client {
    public static void main(String[] args) {
        // 1. 创建菜单树
        MenuComponent menu1 = new Menu("菜单管理", 2);
        menu1.add(new MenuItem("页面访问", 3));
        menu1.add(new MenuItem("展开菜单", 3));
        menu1.add(new MenuItem("编辑菜单", 3));
        menu1.add(new MenuItem("删除菜单", 3));
        menu1.add(new MenuItem("新增菜单", 3));
        MenuComponent menu2 = new Menu("权限管理", 2);
        menu2.add(new MenuItem("页面访问", 3));
        menu2.add(new MenuItem("提交保存", 3));
        MenuComponent menu3 = new Menu("角色管理", 2);
        menu3.add(new MenuItem("页面访问", 3));
        menu3.add(new MenuItem("新增角色", 3));
        menu3.add(new MenuItem("修改角色", 3));
        // 2. 创建一级菜单
        MenuComponent component = new Menu("系统管理", 1);
        // 3. 将二级菜单添加到一级菜单
        component.add(menu1);
        component.add(menu2);
        component.add(menu3);
        // 打印菜单名称
        component.print();
    }
}

分类

  1. 透明组合模式(组合模式的标准形式):在上边的代码里,MenuComponent类(抽象根节点)里声明了add()、remove()、getChild()这些方法,并没有把他们声明成抽象方法,这样是为了确保所有的构建类都能有相同的接口

缺点:不够安全,叶子节点对象(MenuItem类)不可能有下一层次的对象,在MenuComponent类里提供add()、remove()这些方法也只是简单的抛出异常而已,并没有意义。

  1. 安全组合模式:在MenuComponent类(抽象根节点)没有任何用于管理成员对象的方法,这样会导致叶子节点和容器构建具有不同的方法,必须区别对待叶子构建和容器构建。

使用场景

树形结构很适合用组合模式。比如:文件目录显示、多级目录显示树形结构数据

享元模式

共享已存在的对象来大幅度减少需要创建的对象数量、避免大量相似对象的开销,提高资源的利用率。
两种状态】:

  • 内部状态:不会随着环境的改变而改变的可共享部分。
  • 外部状态:随环境改变而改变的不可以共享的部分。
    主要角色】:
  • 抽象享元角色(Flyweight):通常是接口 或 抽象类,在抽象享元类中声明了具体享元类的公共方法,可以向外界提供享元对象的内部数据,同时可以通过这些方法来设置外部数据
  • 具体享元角色(Concrete Flyweight):实现了抽象享元类,在具体享元类中为内部状态提供了空间(通常结合单例模式为每个具体享元类提供唯一的享元对象)
  • 非享元角色(Unsharable Flyweight):不能被共享的子类可以设计成非共享享元类
  • 享元工厂角色(Flyweight Factory):负责创建和管理享元角色,当客户请求一个享元对象,享元工厂先检查是否存在符合要求的享元对象,如果存在就直接提供给用户,不存在就创建一个新的享元对象

案例:俄罗斯方块

俄罗斯方块有不同的形状,可以对这些形状向上抽取AbstractBox,用来定义共性的属性和行为。
在这里插入图片描述
抽象享元角色:

public abstract class AbstractBox {
    /*获取图形*/
    public abstract String getShape();
    /*显示图形和颜色*/
    public void display(String color) {
        System.out.println("方块形状" + getShape() + ", 颜色:" + color);
    }
}

I、L、O图形类(具体享元角色):

public class IBox extends AbstractBox {
    @Override
    public String getShape() {
        return "I";
    }
}

public class LBox extends AbstractBox {
    @Override
    public String getShape() {
        return "L";
    }
}

public class OBox extends AbstractBox {
    @Override
    public String getShape() {
        return "O";
    }
}

工厂类(设计为单例):

public class BoxFactory {
    private HashMap<String, AbstractBox> map;
    private static BoxFactory factory = new BoxFactory();
    // 在构造方法中进行初始化操作
    private BoxFactory() {
        map = new HashMap<>();
        map.put("I", new IBox());
        map.put("O", new OBox());
        map.put("L", new LBox());
    }

    // 提供一个方法获取该工厂类对象
    public static BoxFactory getInstance() {
        return factory; // 饿汉式
    }


    // 根据名称获取图形对象
    public AbstractBox getShape(String name) {
        return map.get(name);
    }
}

测试类:

public class Client {
    public static void main(String[] args) {
        AbstractBox iBox = BoxFactory.getInstance().getShape("I");
        iBox.display("灰色");
        AbstractBox lBox1 = BoxFactory.getInstance().getShape("L");
        lBox1.display("蓝色");
        AbstractBox lBox2 = BoxFactory.getInstance().getShape("L");
        lBox2.display("绿色");
        System.out.println("lBox1 == lBox2:" + (lBox1 == lBox2));// true
    }
}

享元模式的外部状态相对独立,不影响内部状态;
极大减少内存中相似或相同对象数量,节约系统资源

使用场景

  1. 当一个系统有大量相同或相似的对象,使用享元模式可以减少内存浪费;
  2. 对象的大部分状态都可以外部化,并且可以将这些外部状态传到对象中;
  3. 使用享元模式需要额外维护一个存储享元对象的享元池,这需要耗费一定的系统资源。

JDK源码解析

public class Demo {
    public static void main(String[] args) {
        Integer i1 = 127, i2 = 127, i3 = 128, i4 = 128;
        System.out.println(i1 == i2); // true
        System.out.println(i3 == i4); // false
    }
}

Integer默认会先创建并缓存-128~127之间数的Integer对象,当调用valueOf,如果参数是在-128~127之间,则计算下标从缓存中返回;否则就会创建一个新对象。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2377924.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据服务共享平台方案

该文档聚焦数据服务共享平台方案,指出传统大数据管理存在数据定义不统一、开发困难、共享不足等挑战,提出通过自服务大数据平台实现数据 “采、存、管、用” 全流程优化,涵盖数据资产管理、自助数据准备、服务开发与共享、全链路监控等功能,并通过国家电网、东方航空、政府…

skywalking使用教程

skywalking使用教程 一、介绍 skywalking 1.1 概念 skywalking是分布式系统的应用程序性能监视工具&#xff0c;专为微服务、云原生架构和基于容器&#xff08;Docker、K8s、Mesos&#xff09;架构而设计。SkyWalking 是观察性分析平台和应用性能管理系统&#xff0c;提供分布…

C 语 言 - - - 简 易 通 讯 录

C 语 言 - - - 简 易 通 讯 录 代 码 全 貌 与 功 能 介 绍通 讯 录 的 功 能 说 明通 讯 录 效 果 展 示代 码 详 解contact.hcontact.ctest.c 总 结 &#x1f4bb;作 者 简 介&#xff1a;曾 与 你 一 样 迷 茫&#xff0c;现 以 经 验 助 你 入 门 C 语 言 &#x1f4a1;个 …

机器学习知识自然语言处理入门

一、引言&#xff1a;当文字遇上数学 —— 自然语言的数字化革命 在自然语言处理&#xff08;NLP&#xff09;的世界里&#xff0c;计算机要理解人类语言&#xff0c;首先需要将文字转化为数学向量。早期的 One-Hot 编码如同给每个词语分配一个唯一的 “房间号”&#xff0c;例…

MySQL数据库——支持远程IP访问的设置方法总结

【系列专栏】&#xff1a;博主结合工作实践输出的&#xff0c;解决实际问题的专栏&#xff0c;朋友们看过来&#xff01; 《项目案例分享》 《极客DIY开源分享》 《嵌入式通用开发实战》 《C语言开发基础总结》 《从0到1学习嵌入式Linux开发》 《QT开发实战》 《Android开发实…

Pageassist安装(ollama+deepseek-r1)

page-assist网站&#xff1a;https://github.com/n4ze3m/page-assist 首先电脑配置node.js&#xff0c;管理员打开命令窗口输入下面命令下载bun npm install -g buncd 到你想要安装page-assist的地方&#xff08;推荐桌面&#xff09; 输入下列命令 git clone https://gith…

2025年渗透测试面试题总结-安恒[实习]安全服务工程师(题目+回答)

网络安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 安恒[实习]安全服务工程师 1. SQLMap爆出当前库名的参数是什么&#xff1f; 2. Nmap探测系统的参数&am…

五月份嵌入式面试总结

目录 1、札记 1.1、芯片的bring up 主要做哪些工作&#xff1a; 2、Linux驱动八股文 中断与同步互斥 2.1.1 内核同步互斥的几种方式 2.1.2 互斥锁和自旋锁的区别 2.1.3 spin_lock 和 spin_lock_irqsave 的区别 2.1.4 进程上下文和中断上下文有什么区别 2.1.5 进行上下…

数据库行业竞争加剧,MySQL 9.3.0 企业版开始支持个人下载

最新发现&#xff0c;Oracle 官方网站放开了 MySQL 9.3.0 企业版下载链接&#xff0c;个人用户也可以免费下载&#xff0c;不过只能用于学习、开发或者原型测试&#xff0c;不能用于生产环境。 通常我们都是下载 MySQL 社区版&#xff0c;不过 MySQL 企业版可以支持更多高级功能…

Tcping详细使用教程

Tcping详细使用教程 下载地址 https://download.elifulkerson.com/files/tcping/0.39/在windows环境下安装tcping 在以上的下载地中找到exe可执行文件&#xff0c;其中tcping.exe适用于32位Windows系统&#xff0c;tcping64.exe适用于64位Windows操作系统。 其实tcping是个…

【GAN网络入门系列】一,手写字MINST图片生成

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 博主简介&#xff1a;努力学习的22级本科生一枚 &#x1f31f;​&#xff1b;探索AI算法&#xff0c;C&#xff0c;go语言的世界&#xff1b;在迷茫中寻找光芒…

ubuntu22鼠键失灵恢复记录笔记chatgpt解决

ChatGPT 说&#xff1a; 你提到“Ubuntu 22 鼠键失灵”&#xff0c;这个问题可能涉及以下几方面&#xff1a; &#x1f9ed; 先确认问题 是鼠标问题还是键盘问题&#xff0c;还是触控板&#xff1f; “鼠键”一般理解为“鼠标键”&#xff0c;请确认你是指鼠标左键/右键失灵&a…

智能呼入:云蝠大模型赋能政府热线

政府热线作为连接政府与民众的重要桥梁&#xff0c;提升智能化水平&#xff0c;成为政府热线亟待解决的问题。 大模型呼入 大模型呼入技术基于先进的自然语言处理和机器学习算法&#xff0c;能够实现对海量语音数据的处理和理解。通过构建大规模的语言模型&#xff0c;系统可…

STM32 ADC+DMA+TIM触发采样实战:避坑指南与源码解析

知识点1【TRGO的介绍】 1、TRGO的概述 TRGO&#xff1a;Trigger Output&#xff08;触发输出&#xff09;&#xff0c;是定时器的一种功能。 它可以作为外设的启动信号&#xff0c;比如ADC转换&#xff0c;DAC输出&#xff0c;DMA请求等。 对于ADC来说&#xff0c;可以通过…

(1-4)Java Object类、Final、注解、设计模式、抽象类、接口、内部类

目录 1. Object类 1.1 equals 1.2 toString&#xff08;&#xff09; 2.final关键字 3.注解 4. 设计模式 4.1 单例模式 4.1.1 饿汉式 4.1.3 饿汉式 VS 懒汉式 5. 抽象类&抽象方法 6. 接口 7.内部类 7.1 成员内部类 7.2 静态内部类 7.3 方法内部类 7.4 匿名内…

在服务器上安装AlphaFold2遇到的问题(3)_cat: /usr/include/cudnn_version.h: 没有那个文件或目录

[rootlocalhost ~]# cat /usr/include/cudnn_version.h cat: /usr/include/cudnn_version.h: 没有那个文件或目录这个错误表明系统找不到 cudnn_version.h 头文件&#xff0c;说明 cuDNN 的开发文件&#xff08;头文件&#xff09;没有正确安装。以下是完整的解决方案&#xff…

实验-实现向量点积-RISC-V(计算机组成原理)

目录 一、实验内容 二、实验步骤 三、源代码 四、实现效果 五、实验环境 六、实验小结与思考 一、实验内容 首先&#xff0c;我们用一个简单的“向量点积”运算作为热身。你将拿到一个不完整的汇编代码“task2-向量点积”&#xff0c;我们的目标是按照C语言描述的功能&a…

描述性统计工具 - AxureMost 落葵网

描述性统计工具是用于汇总和分析数据&#xff0c;以更好地了解数据特征的工具1。以下是一些常见的描述性统计工具简介&#xff1a; 描述性统计工具 Excel 基本统计函数&#xff1a;提供了丰富的函数用于计算描述性统计量。例如&#xff0c;AVERAGE 函数用于计算平均值&#xf…

麒麟桌面系统文件保险箱快捷访问指南:让重要文件夹一键直达桌面!

往期文章链接&#xff1a;统信操作系统自定义快捷键配置音量调节功能指南 Hello&#xff0c;大家好啊&#xff0c;今天给大家带来一篇麒麟桌面操作系统上配置文件保险箱内文件夹桌面快捷方式的文章&#xff0c;欢迎大家分享点赞&#xff0c;点个在看和关注吧&#xff01;在日常…

从硬件角度理解“Linux下一切皆文件“,详解用户级缓冲区

目录 前言 一、从硬件角度理解"Linux下一切皆文件" 从理解硬件是种“文件”到其他系统资源的抽象 二、缓冲区 1.缓冲区介绍 2.缓冲区的刷新策略 3.用户级缓冲区 这个用户级缓冲区在哪呢&#xff1f; 解释关于fork再加重定向“>”后数据会打印两份的原因 4.内核缓冲…