【设计模式深度剖析】【2】【结构型】【装饰器模式】| 以去咖啡馆买咖啡为例 | 以穿衣服出门类比

news2024/12/22 14:37:51

👈️上一篇:代理模式

目 录

  • 装饰器模式
  • 定义
    • 英文原话
    • 直译
    • 如何理解呢?
    • 4个角色
    • 类图
      • 1. 抽象构件(Component)角色
      • 2. 具体构件(Concrete Component)角色
      • 3. 装饰(Decorator)角色
      • 4. 具体装饰(Concrete Decorator)角色
      • 说明:
    • 代码示例
  • 典型场景
  • 示例解析:以去咖啡馆买咖啡为例
    • 类图
    • 抽象构件
    • 具体构件
    • 装饰器类
    • 测试类:去咖啡馆买咖啡
    • 注意
  • Java I/O类库借鉴了装饰器模式
    • UML类图
    • 准备待读取的文件
    • 代码示例1:读取文件,并打印输出
      • notes1:
      • notes2:
    • 代码示例2:解决乱码问题
      • notes:
  • 适配器模式与装饰器模式的区别
  • 搞懂了?那我们玩点带有迷惑型的东西
    • 为什么说Java io类库借鉴了装饰模式,而不是等同于装饰模式
    • 类比
  • 拓展内容
    • 抽象类相关基础

装饰器模式

装饰器(Decorator)模式:

就是,是不一样的烟火,对进行任何的装饰之后依旧是那个,归来依旧是那个少年!

装饰模式是继承关系的一个替代方案。装饰类Decorator,不管装饰多少层,返回的对象还是原类型(记住加粗的这句话,默念三遍,很重要)。

不管装饰多少次,类型不要发生变化才是装饰器模式。

装饰者的出现也再一次的证明了面向对象的设计原则:多用组合,少用继承!对扩展开放,对修改关闭!

==> 本文示例源码 <==

定义

英文原话

Attach additional responsibilities to an object dynamically keeping the same interface. Decorators provide a flexible alternative to subclassing for extending functionality.

直译

动态地向对象附加额外的职责,同时保持相同的接口。装饰器为扩展功能提供了一种比子类化更灵活的替代方案。

如何理解呢?

装饰器模式是继承关系的替代:

装饰模式可以替代继承,解决类膨胀的问题。

下面会进行详细的阐述。

4个角色

类图

在这里插入图片描述

1. 抽象构件(Component)角色

该角色用于规范需要装饰的对象(原始对象)。

2. 具体构件(Concrete Component)角色

该角色实现抽象构件接口,定义一个需要装饰的原始类。

3. 装饰(Decorator)角色

该角色持有一个构件对象的实例,并定义一个与抽象构件接口一致的接口。

4. 具体装饰(Concrete Decorator)角色

该角色负责对构件对象进行装饰。

说明:

装饰类和被装饰类可以独立发展,而不会相互耦合。即Component 类无须知道Decorator类,Decorator类是从外部来扩展Component类的功能,而Decorator也不用知道具体的构件。

装饰模式是继承关系的一个替代方案。装饰类Decorator,不管装饰多少层,返回的对象还是Component(记住加粗的这句话,默念三遍,很重要)。

装饰模式可以动态地扩展一个实现类的功能

装饰模式的缺点:多层的装饰是比较复杂的。

代码示例

package com.polaris.designpattern.list2.structural.pattern2.decorator.proto;

// 抽象组件
interface Component {  
    void operate();  
}  
  
// 具体组件  
class ConcreteComponent implements Component {  
    @Override  
    public void operate() {  
        System.out.println("执行具体组件的操作");  
    }  
}  
  
// 抽象装饰器  
class Decorator implements Component {  
    protected Component component;  
  
    public Decorator(Component component) {  
        this.component = component;  
    }  
  
    @Override  
    public void operate() {  
        if (component != null) {  
            component.operate();  
        }  
    }  
}  
  
// 具体装饰器  
class ConcreteDecorator extends Decorator {  
    public ConcreteDecorator(Component component) {  
        super(component);  
    }  
  
    @Override  
    public void operate() {  
        super.operate(); // 调用抽象装饰器的operation方法  
        // 添加额外的功能或行为  
        addedBehavior();  
    }  
  
    public void addedBehavior() {  
        System.out.println("执行具体装饰器的额外操作");  
    }  
}  
  
// 客户端代码  
public class ClassicalDecoratorDemo {
    public static void main(String[] args) {  
        Component component = new ConcreteComponent();  
  
        // 递归地组合装饰器对象  
        Component decoratedComponent = new ConcreteDecorator(new ConcreteDecorator(component));  
  
        // 执行操作  
        decoratedComponent.operate();  
    }  
}
/* Output:
执行具体组件的操作
执行具体装饰器的额外操作
执行具体装饰器的额外操作
*///~

典型场景

  1. ■ 需要扩展一个类的功能,或给一个类增加附加功能。
  2. ■ 需要动态地给一个对象增加功能,这些功能可以再动态地撤销。
  3. ■ 需要为一批类进行改装或加装功能。

NOTES:

  1. 装饰模式是对继承的有力补充。
  2. 单纯使用继承时,在一些情况下就会增加很多子类,而且灵活性差,维护也不容易。
  3. 装饰模式可以替代继承,解决类膨胀的问题,如Java基础类库中的io类库(输入输出流相关的类)大量借鉴了装饰模式(参考进一步分析)。

示例解析:以去咖啡馆买咖啡为例

类图

在这里插入图片描述

抽象构件

对咖啡馆的咖啡进行了抽象

package com.polaris.designpattern.list2.structural.pattern2.decorator;

// 咖啡接口
interface Coffee {
    String getDescription();
    double getCost();
}

具体构件

咖啡馆的最普通的咖啡,对抽象构件的一种实现。

package com.polaris.designpattern.list2.structural.pattern2.decorator;


// 基础咖啡实现
class SimpleCoffee implements Coffee {
    @Override  
    public String getDescription() {  
        return "美式咖啡";  
    }  
  
    @Override  
    public double getCost() {  
        return 2.5;  
    }  
}

装饰器类

抽象出装饰器角色,一个抽象类,实现了抽象构件Coffee接口,

它的实现在这里有两个,一个是加糖装饰器 SugarDecorator,加奶装饰器MilkDecorator ,还可以根据需要继续扩展…

package com.polaris.designpattern.list2.structural.pattern2.decorator;


// 咖啡装饰器接口
abstract class CoffeeDecorator implements Coffee {
    protected Coffee coffee;  
  
    public CoffeeDecorator(Coffee coffee) {  
        this.coffee = coffee;  
    }  
  
    @Override  
    public String getDescription() {  
        return coffee.getDescription();  
    }  
  
    @Override  
    public double getCost() {  
        return coffee.getCost();  
    }  
}  
  
// 加糖装饰器  
class SugarDecorator extends CoffeeDecorator {  
    public SugarDecorator(Coffee coffee) {  
        super(coffee);  
    }  
  
    @Override  
    public String getDescription() {  
        return coffee.getDescription() + ", 加糖";  
    }  
  
    @Override  
    public double getCost() {  
        return coffee.getCost() + 0.5;  
    }  
}  
  
// 加奶装饰器  
class MilkDecorator extends CoffeeDecorator {  
    public MilkDecorator(Coffee coffee) {  
        super(coffee);  
    }  
  
    @Override  
    public String getDescription() {  
        return coffee.getDescription() + ", 加奶";  
    }  
  
    @Override  
    public double getCost() {  
        return coffee.getCost() + 1.0;  
    }  
}  
  
// ... 可以添加更多装饰器,如肉桂粉装饰器等

测试类:去咖啡馆买咖啡

package com.polaris.designpattern.list2.structural.pattern2.decorator;

public class CoffeeShop {
    public static void main(String[] args) {  
        // 创建基础咖啡  
        Coffee coffee = new SimpleCoffee();
        System.out.println("你点了:" + coffee.getDescription() + ",价格是:" + coffee.getCost());  
  
        // 添加加糖装饰器  
        Coffee sweetCoffee = new SugarDecorator(coffee);
        System.out.println("你加了糖,现在是:" + sweetCoffee.getDescription() + ",价格是:" + sweetCoffee.getCost());  
  
        // 递归添加加奶装饰器(加在已经加糖的咖啡上)  
        Coffee latte = new MilkDecorator(sweetCoffee);
        System.out.println("你又加了奶,现在是:" + latte.getDescription() + ",价格是:" + latte.getCost());  
  
        // 你可以继续递归添加更多装饰器...  
    }  
}

/* Output:
你点了:美式咖啡,价格是:2.5
你加了糖,现在是:美式咖啡, 加糖,价格是:3.0
你又加了奶,现在是:美式咖啡, 加糖, 加奶,价格是:4.0
*///~

注意

从上面示例你也会发现,无论装饰几层,类型不变,都是Coffee类型

Coffee coffee = new SimpleCoffee();

Coffee sweetCoffee = new SugarDecorator(coffee);

Coffee latte = new MilkDecorator(sweetCoffee);

Java I/O类库借鉴了装饰器模式

UML类图

在这里插入图片描述

准备待读取的文件

ExampleFileForRead.txt

相对于当前工程的目录路径是:

src/main/java/com/polaris/designpattern/list2/structural/pattern2/decorator/inputstream/ExampleFileForRead.txt

文件内容如下:

设计模式类型:
1. ■ 创建型(creational)👈️
2. ■ 结构型(structural)👈️
3. ■ 行为型(behavioral)👈️

代码示例1:读取文件,并打印输出

进一步深究

控制台打印输出的中文、特殊字符乱码了

package com.polaris.designpattern.list2.structural.pattern2.decorator.inputstream;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

/**
 * 存在打印中文、特殊字符乱码问题
 */
public class DecoratorPatternDemo {
  
    public static void main(String[] args) {
        String exampleFile = System.getProperty("user.dir") +
                File.separator +
                "src/main/java/com/polaris/designpattern/list2/structural/pattern2/decorator/inputstreamapi" +
                "/ExampleFileForRead.txt";
        try (
                // 创建一个 FileInputStream 对象,用于从文件中读取数据
                FileInputStream fis = new FileInputStream(exampleFile);
                // 使用 BufferedInputStream 包装 FileInputStream,添加缓冲功能
                BufferedInputStream bis = new BufferedInputStream(fis);
            
                /*行不通:FilterInputStream构造函数受保护
                FileInputStream fis = new FileInputStream(exampleFile);
                FilterInputStream filteris= new FilterInputStream(fis);
                BufferedInputStream bis = new BufferedInputStream(filteris);*/
        ) {  
            // 读取并处理数据,这里只是简单地打印到控制台  
            int data;  
            while ((data = bis.read()) != -1) {  
                System.out.print((char) data);  
            }  
        } catch (IOException e) {
            e.printStackTrace();  
        }  
    }  
}

/* Output:
设计模式类型:
1. ■ 创建型(creational)👈️
2. ■ 结构型(structural)👈️
3. ■ 行为型(behavioral)👈️
*///~

notes1:

上面是一个简单的测试类示例,展示了如何使用 BufferedInputStreamFileInputStream 类似装饰模式的方式。

BufferedInputStreamFilterInputStreamInputStreamFileInputStream 这些类在 Java 的 I/O 框架中与装饰模式(Decorator Pattern)有关,但并不严格地完全遵循装饰模式的经典定义。然而,我们可以说它们的设计受到装饰模式的启发(详细分析见进一步分析),特别是 FilterInputStream 和它的子类 BufferedInputStream 展示了类似装饰模式的行为。

装饰模式允许向一个对象动态地添加职责(即功能),同时保持相同的接口

在 Java I/O 中,InputStream 是所有输入流的基类,定义了一个读取字节的通用接口。

FilterInputStreamInputStream 的一个子类,用于包装另一个 InputStream 并向其添加额外的功能,而不改变其基本的读取接口。

BufferedInputStreamFilterInputStream 的一个子类,它提供了一个带有缓冲区的输入流,用于提高读取性能。它通过包装另一个 InputStream(可能是 FileInputStream 或其他任何 InputStream 的子类)来添加缓冲功能。

notes2:

在这个示例中,FileInputStream 是原始的数据源,而 BufferedInputStream 则作为一个装饰器,为原始数据源添加了缓冲功能。这种关系与装饰模式中的组件(Component)和装饰器(Decorator)之间的关系相似。 FilterInputStream 和它的子类,通过组合(即包装另一个 InputStream 对象)来扩展功能。

可以从源码看出,FilterInputStream 持有一个InputStream的实例,通过构造函数进行的初始化,即FilterInputStream 和子类组合了一个InputStream 对象,

public class FilterInputStream extends InputStream {
/**
     * The input stream to be filtered.
     */
    protected volatile InputStream in;

    protected FilterInputStream(InputStream in) {
        this.in = in;
    }
    
    //... 省略其余代码 ....
}

总的来说,虽然 BufferedInputStreamFilterInputStreamInputStreamFileInputStream 的设计受到装饰模式的启发,但它们并不完全遵循装饰模式的经典定义。不过,这种设计确实展示了如何动态地向对象添加功能而不改变其接口的思想。

代码示例2:解决乱码问题

进一步深究

package com.polaris.designpattern.list2.structural.pattern2.decorator.inputstream;

import java.io.*;
import java.nio.charset.StandardCharsets;



public class DecoratorPatternDemoOk {
  
    public static void main(String[] args) {
        String exampleFile = System.getProperty("user.dir") +
                File.separator +
                "src/main/java/com/polaris/designpattern/list2/structural/pattern2/decorator/inputstream" +
                "/ExampleFileForRead.txt";
  
        try (
                // 创建一个 FileInputStream 对象,用于从文件中读取数据
                FileInputStream fis = new FileInputStream(exampleFile);
                // 使用 InputStreamReader 读取字符,并指定编码为 UTF-8
                Reader isr = new InputStreamReader(fis, StandardCharsets.UTF_8);
                // 使用 BufferedReader 包装 InputStreamReader,添加缓冲功能
                BufferedReader br = new BufferedReader(isr);
        ) {
            // 读取并处理数据,这里只是简单地打印到控制台  
            String line;  
            while ((line = br.readLine()) != null) {  
                System.out.println(line);  
            }  
        } catch (IOException e) {
            e.printStackTrace();  
        }  
    }  
}

/* Output:
设计模式类型:
1. ■ 创建型(creational)👈️
2. ■ 结构型(structural)👈️
3. ■ 行为型(behavioral)👈️
*///~

notes:

遇到的乱码问题是因为尝试将文件的字节直接转换为字符((char) data)进行打印,

但是文件的编码(UTF-8)与 Java 默认使用的字符编码(通常是系统默认的,可能不是 UTF-8)不匹配。

在 Java 中,FileInputStream 读取的是原始字节,需要指定正确的字符编码来将这些字节转换为字符串。

为了正确地读取和显示文件内容,应该使用 InputStreamReaderBufferedReader,并指定文件的字符编码,在这个示例,它使用 UTF-8 编码来读取和打印文件内容。

示例中,使用了 InputStreamReader 来读取字符,并指定了编码为 “UTF-8”。

然后,使用BufferedReader包装了 InputStreamReader来提供按行读取的功能

最后,我使用 readLine() 方法读取每一行,并直接打印到控制台。

能够正确地看到文件中的内容,而不会出现乱码。

适配器模式与装饰器模式的区别

装饰器与适配器模式都有一个别名就是包装模式(Wrapper),它们的作用看似都是起到包装一个类或对象的作用,但是使用它们的目的是很不一样的。

  1. 适配器模式的意义是要将一个接口转变成另外一个接口,它的目的是通过改变接口来达到重复使用的目的;
  2. 而装饰器模式不是要改变被装饰对象的接口,而恰恰要保持原有的接口,但是增强原有对象的功能,或者改变原有对象的处理方法而提升性能。所以这两个模式设计的目的是不同的。

搞懂了?那我们玩点带有迷惑型的东西

为什么说Java io类库借鉴了装饰模式,而不是等同于装饰模式

我们之前说Java I/O包下的类库借鉴了装饰器模式

但是它并不完全等同于经典的装饰器模式

装饰器模式通常允许递归地组合装饰器对象,而Java I/O中的FilterInputStream通常只包装一个InputStream对象。

如果我们区分这一差别,他们实际上是不同的;

严格意义上装饰器模式是可以对一个对象进行多次装饰,之后依旧是原类型,即允许递归的组合装饰器对象。

而java io包中类库却不是多次装饰。

以下进一步分析,下图是之前的类图,如果有必要可以回到之前的代码示例

  • 返回代码示例1

  • 返回代码示例2

在这里插入图片描述

那java io类库不是使用的装饰器模式么?大家不都说java io类库使用的是装饰器模式么?

其实准确的说java io类库借鉴了装饰器模式比较合理些

从上面的类图或者回到之前代码示例1,FilterInputStream的构造方法是protected修饰的,我们无法直接通过构造函数构造他的实例,因此直接使用它的子类构造,如这里的BufferedInputStream

通过传入文件路径字符串构造了FileInputStream对象,通过该FileInputStream实例构造了一个BufferedInputStream实例,BufferedInputStream可以看作是对该InputStream装饰了一层,即增加缓冲功能,就完事了,没有后续的装饰了;

而严格意义上的装饰器模式呢,是允许装饰多层的,装饰完毕依旧是原始的类型。

如果我们区分这一差别,即允许递归地组合装饰器对象(装饰完之后还是之前的类型才对,不能改变类型,不是去构造一个新的类型的实例),java io类库就不符合装饰器模式的要求

也就是如果按照严格意义上来说,对InputStream对象装饰多次,最终依旧是InputStream对象,才算是装饰器模式。

显然这里不是,这里经过了String =1=> InputStream(FileInputStream) =2=> InputStream(BufferedInputStream)第1步之后就不再是String类型了。只是这里形式上看上去和递归地组合装饰器对象非常相像,new BufferedInputStream(new FileInputStream("path_of_file"))

因此这里说是借鉴装饰器模式,且借鉴装饰器模式的部分只就是InputStream(FileInputStream) => InputStream(BufferedInputStream)这个过程。

略微延伸一点,我们打个比方,

java io类库由扩展了,增加了一个XxxInputStream类,以实现对InputStream增加Xxx功能,它需要传入InputStream类型实例来构造,那么它可能的调用方式是这样的:

InputStream fis = new FileInputStream("path_of_file");
XxxInputStream xis = new XxxInputStream(new BufferedInputStream(fis));

它允许递归地组合装饰器对象,这样他就是严格的装饰器模式了。

如果你说,上面代码示例2,形式上很像递归的组合装饰器对象呐,

InputStream fis = new FileInputStream(exampleFile);
BufferedReader br = new BufferedReader(new InputStreamReader(fis, StandardCharsets.UTF_8));

但是实际不是。

即使解决乱码问题,但是那是又引入了另外的接口Reader,与InputStream不是一回事,构造InputStreamReader的实例,通过传入FileInputStream实例(也就是一个InputStream实例),就转换成了Reader类型,就不再是InputStream类型,然后,通过传入该InputStreamReader,并指定编码格式UTF-8,构造了一个带有缓冲功能的BufferedReader,这里可以看简单看作是BufferedReaderInputStreamReader装饰了一层,即缓冲功能,就完事了,没有后续的装饰了,

类比

你大概也发现了,通过上面的比较较真的分析之后,我们对装饰器模式有了更深的认识,就是不管装饰多少次,类型不要发生变化才是装饰器模式。

并不是说只要像这样调用new C(new B(new A()))形似递归的组合装饰器,就是装饰器模式,只要其中发生了类型变化就不再是严格意义上的装饰器模式了。

可以用穿衣服出门类比:

当前的java io类库中的设计可以视为借鉴了装饰器模式,可以看作只包装一层的装饰器模式,就好比穿外套就出门了;

而严格的讲,**经典的装饰器模式(允许递归地组合装饰器对象)**是说你穿上内裤穿上袜子穿上衬衫穿上西服打上领带,这每一步操作都是对(一个对象,人类的一个实例,即一个人类对象)的装饰

穿上内裤之后是你,

穿上袜子是你,

穿上衬衫是你,

穿上西服是你,

打上领带是你。

就是,是不一样的烟火,对进行任何的装饰之后依旧是那个,归来依旧是那个少年!

拓展内容

抽象类相关基础

这部分内容属于总结,不需要死记硬背,用的多了回过头来看都是很简单的内容。

  1. 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类

    这里提一下:不考虑默认方法(使用default关键字定义)和静态方法(使用static关键字定义)情况下,接口中的方法都是抽象方法。

    在Java 8及之前的版本中,接口中只能包含抽象方法和常量(即静态且final的变量)

    public interface MyInterface {  
        // 抽象方法  
        void method1();  
      
        // Java 8及以后:默认方法  
        default void method2() {  
            System.out.println("Default implementation of method2");  
        }  
      
        // Java 8及以后:静态方法  
        static void method3() {  
            System.out.println("Static method in interface");  
        }  
    }
    
  2. 构造方法,类方法(用static修饰的方法)不能声明为抽象方法

  3. 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类

  4. 实例化

    1. 抽象类和接口均不能实例化,但是可以指向(引用)具体实现(非抽象子类)
    2. 抽象类虽然自身不可以实例化,但是其子类覆盖了所有的抽象方法后,是可以实例化的,所以抽象类的构造函数适用于给其子类对象进行初始化
  5. 构造器:构造函数是对象的基本,没有构造函数就没有对象。

    1. 若在抽象类中显式的声明了有参构造器子类继承时就必须写一个构造函数来调用父类的有参构造器

    2. 具体子类继承抽象父类,父类的有参构造器子类必须显示调用,即在子类的构造器中显式地super(param_object)调用

    3. 这是因为
      构造函数用于构造对象,但是抽象类不能被实例化,
      一旦抽象类声明了有参构造器,而此构造器不能直接被调用(除非子类显式调用),就不能被客户端将参数直接通过调用该抽象类的有参构造器传参进去,
      这个参数对象只能通过具体子类在构造对象时传入,即子类构造器中必须要显式地调用父类有参构造器super(param_object)

    4. 构造器参数类型可以放大,这样会更通用些,比如你要传入某个实现类的实例,可以将构造器参数类型设置为接口类型,这是多态的一种表现,你可以传入任何属于该类型或该类型的子类型对象。

    5. 如果父类中有无参构造器,在子类中可以不写构造器或显式的调用父类的构造函器,Java会自动调用父类无参构造器。

      以下代码是对抽象类构造器部分解释的编码

      abstract class Parent{
          public Parent(Param param){
      
          }
          public abstract void m();
      }
      
      class Son extends Parent{
      
          // param type can be larger.
          public Son(Param param) {
              // must invoke manually.
              super(param);
          }
      
          @Override
          public void m() {
      
          }
      }
      
      interface Param{
      
      }
      
      class ParamTypeA implements Param{
      
      }
      
      public class Demo {
          public static void main(String[] args) {
              Param param = new ParamTypeA();
              Son son = new Son(param);
          }
      }
      

👈️上一篇:代理模式

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

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

相关文章

vue + SpringBoot + flowable 实现工作流审批功能 (流程图部署)

目录 搭建前端vue项目 vue init webpack project_name 初始化项目 导入 element-ui 框架 npm install element-ui -s 设置 element-ui 全局配置 编辑 main.js 文件 import ElementUI from "element-ui"; // ui框架导入 import element-ui/lib/theme-chal…

基于PID控制器的天线方位角位置控制系统simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 基于PID控制器的天线方位角位置控制系统simulink建模与仿真。通过零极点配置的方式实现PID控制器的参数整定。 2.系统仿真结果 3.核心程序与模型 版本&#xff1a;MATLAB202…

【面试干货】杨辉三角形

【面试干货】杨辉三角形 1、实现思想2、代码实现 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 杨辉三角形&#xff08;也称帕斯卡三角形&#xff09;是一个规则的数字三角形&#xff0c;它的构造方法是&#xff0c;第一行只有一个数字1&a…

python-鸡兔同笼问题:已知鸡和兔的总头数与总脚数。求笼中鸡和兔各几只?

【问题描述】典型的鸡兔同笼问题。 【输入形式】输入总头数和总脚数两个实数&#xff1a;h&#xff0c;f 【输出形式】笼中鸡和兔的个数&#xff1a;x&#xff0c;y 【样例输入】16 40 【样例输出】鸡12只&#xff0c;兔4只 【样例说明】输入输出必须保证格式正确。…

FL Studio2025中文最新版本专业编曲软件有哪些新功能?

FL Studio 21&#xff0c;也被音乐制作爱好者亲切地称为“水果编曲软件”&#xff0c;是比利时的Image-Line公司研发的一款完整的音乐制作环境或数字音频工作站&#xff08;DAW&#xff09;。自从1990年代推出以来&#xff0c;FL Studio 以其直观的用户界面、丰富的插件支持和强…

苹果CMS:怎么重新安装

当我们安装好苹果CMS之后苹果cms&#xff1a;介绍及安装&#xff0c;但是最好我们在安装的时候配置好对应配置后&#xff0c;备份一份&#xff0c;如果不记得哪里配置出了问题&#xff0c;出现一些不可预料的问题&#xff0c;那我们可以简单暴力的直接重新安装&#xff0c;我们…

PointCloudLib 点云半径滤波实现 C++版本

0.展示效果 滤波之前 1.算法原理 半径滤波原理非常直观,主要用于平滑三维点云数据并去除离群点。 设定搜索半径:首先,为每个点设定一个搜索半径r。这个半径定义了该点周围的一个球形区域。计算邻域点数:接着,计算每个点在其搜索半径r内的邻近点的数量。判断与过滤:根据…

vue 展示svg矢量图可缩放拖动

使用插件&#xff1a;svg-pan-zoom <template> <!-- svg图--><div id"svgContainer"></div> </template><script> import svgPanZoom from svg-pan-zoom import svgFile from ../datav/img/220kVscb.svg // 路径根据实际情况调…

大模型实战讲师叶梓:通过视频生成实现基于物理的3D对象交互——PhysDreamer

随着虚拟现实(VR)和增强现实(AR)技术的飞速发展&#xff0c;用户对于虚拟体验的真实性提出了更高的要求。在这样的背景下&#xff0c;PhysDreamer应运而生&#xff0c;它是一项创新的技术&#xff0c;能够为静态3D对象赋予逼真的物理交互动态&#xff0c;极大地丰富了虚拟环境的…

Vue的router.addRoutes不起作用

Vue的router.addRoutes()不起作用解决方案 最近在学习制作后台管理系统的时候&#xff0c;涉及到了权限&#xff0c;在通过后台获取到数据后使用router.addRoutes()时不起作用。 最终发现左侧菜单组件中的路由是根据this.$router.options.routes来渲染的&#xff0c;最终使用…

C++ | Leetcode C++题解之第110题平衡二叉树

题目&#xff1a; 题解&#xff1a; class Solution { public:int height(TreeNode* root) {if (root NULL) {return 0;}int leftHeight height(root->left);int rightHeight height(root->right);if (leftHeight -1 || rightHeight -1 || abs(leftHeight - rightH…

将Surface的分辨率减半以省电(二合一本\笔记本电脑适用)

【完全自定义分辨率教程】这篇教程用于将Surface之类的高分屏&#xff08;高分辨率&#xff09;的二合一本或笔记本等的分辨率调整为原来的一半&#xff0c;以实现省电等目的。 下载CRU&#xff08;Custom Resolution Utility&#xff09;解压后&#xff0c;打开CRU.exe选择当…

交叉编译——

什么是交叉编译 交叉编译 是在一个平台上生成临海一个平台可执行代码. eg.在windows上面编写C51代码&#xff0c;并编译生成可执行代码。如xx.hex 我们在Ubuntu上编写树莓派的代码&#xff0c;并编译成可执行代码。a.out. 是在树莓派上运行&#xff0c;不在Ubuntu Linux上面运…

每日练习之排序——链表的合并;完全背包—— 兑换零钱

链表的合并 题目描述 运行代码 #include<iostream> #include<algorithm> using namespace std; int main() { int a[31];for(int i 1;i < 30;i)cin>>a[i];sort(a 1,a 1 30);for(int i 1;i < 30;i)cout<<a[i]<<" ";cout&…

Redis实现热点数据排行榜或游戏积分排行榜

数据库中的某张表中存储着文章的浏览量&#xff0c;或者点赞数等&#xff0c;或者游戏积分等数据...... 这些数据的更新在redis中完成&#xff0c;并定时同步到mysql数据库中。 而如果要对这些数据进行排序的话&#xff1a; Redis中的Sorted Set(有序集合)非常适合用于实现排…

​​​【收录 Hello 算法】第 10 章 搜索

目录 第 10 章 搜索 本章内容 第 10 章 搜索 搜索是一场未知的冒险&#xff0c;我们或许需要走遍神秘空间的每个角落&#xff0c;又或许可以快速锁定目标。 在这场寻觅之旅中&#xff0c;每一次探索都可能得到一个未曾料想的答案。 本章内容 10.1 二分查找10.2 二…

Obsidian Git 多端同步

2023年6月&#xff0c;某云笔记限制了免费用户最多同时登录 2 台设备&#xff0c;想要增加设备数量需要付费开通会员。之后我一直想找一款合适的笔记本软件&#xff0c;年底尝试了Obsidian&#xff0c;断断续续摸索了好几天终于成功了。将那时的笔记拿来分享一下。 相关地址&am…

如何部署一个基本符合ERC20的智能合约

运行genache-cli 运行以下命令genache-cli下载MetaMask浏览器拓展钱包 添加账户 导入账户 输入genache-cli生成其中的密钥 选择【显示测试网络】点击【添加网络】 添加自己本地的网络 选择该测试网络&#xff0c;账号里就会有100RETH 在remix里接通这个测试帐号 …

【SQL Server001】SQLServer2016常用函数实战总结(已更新)

1.熟悉、梳理、总结下SQL Server相关知识体系。 2.日常研发过程中使用较少&#xff0c;随着时间的推移&#xff0c;很快就忘得一干二净&#xff0c;所以梳理总结下&#xff0c;以备日常使用参考 3.欢迎批评指正&#xff0c;跪谢一键三连&#xff01; 总结源文件资源下载地址&am…

《基于Jmeter的性能测试框架搭建》改进一

《基于Jmeter的性能测试框架搭建》文末笔者提到了不少待改进之处&#xff0c;如下所示。 Grafana性能图表实时展现&#xff0c;测试过程中需实时截图形成测试报告&#xff0c;不够人性化。解决方案&#xff1a;自动生成测试报告并邮件通知。 Grafana性能图表需测试人员实时监控…