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

发布于:2025-05-17 ⋅ 阅读:(57) ⋅ 点赞:(0)

代理模式

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

分类:

  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之间,则计算下标从缓存中返回;否则就会创建一个新对象。


网站公告

今日签到

点亮在社区的每一天
去签到