【HF设计模式】03-装饰者模式
声明:仅为个人学习总结,还请批判性查看,如有不同观点,欢迎交流。
摘要
《Head First设计模式》第3章笔记:结合示例应用和代码,介绍装饰者模式,包括遇到的问题、遵循的 OO 原则、达到的效果。
目录
- 摘要
- 1 示例应用
- 2 遇到问题
- 3 引入设计模式
- 3.1 OO 原则:开闭原则
- 3.2 完善“装饰者”设计
- 3.3 完善“被装饰对象”设计
- 3.4 装饰者模式定义
- 4 示例代码
- 4.1 Java 示例
- 4.2 C++11 示例
- 5 设计工具箱
- 5.1 OO 基础
- 5.2 OO 原则
- 5.3 OO 模式
- 参考
1 示例应用
示例应用是星巴兹(Starbuzz)咖啡店的订单系统。
最开始,店里只提供黑咖啡,系统类图如下:
Beverage(饮料),是一个抽象类,咖啡店售卖的所有饮料都继承这个类- 定义
description实例变量,用于保存饮料描述; - 定义
getDescription()方法,用于获取description; - 声明抽象的
cost()方法,用于获取饮料价格,由子类负责实现。
- 定义
Beverage的子类:HouseBlend(混合咖啡)、DarkRoast(深度烘焙)、Decaf(低咖啡因)、Espresso(浓缩咖啡)- 实现
cost()方法,计算具体饮料价格; - 将具体饮料描述赋值给
description,例如“最优深度烘焙”。
- 实现
后来,咖啡店提供了牛奶等调味配料,每份调料会收取一点费用。
随着调料和饮料的种类不断增加,现在系统急需进行更新。下面是当前的系统类图:

类爆炸!
调料的种类包括 Steamed Milk(蒸牛奶)、Soy(豆奶)、Mocha(摩卡,也被称为巧克力)、Whip(奶油泡沫)等,系统为每个“饮料和调料的组合”都创建了类。
我们来近距离观察一下 DarkRoastWithMochaAndWhip(深焙摩卡奶泡咖啡)类的定义:
public class DarkRoastWithMochaAndWhip extends Beverage {public DarkRoastWithMochaAndWhip() {description = "Dark Roast with Mocha and Whip";}public double cost() {return 0.99 + 0.2 + 0.1; // 基础饮料价格 + 各种调料价格}
}
订购一杯“深焙摩卡奶泡咖啡”的方式如下:
Beverage beverage = new DarkRoastWithMochaAndWhip();
System.out.println(beverage.getDescription() + " $" + beverage.cost());
思考题:
很明显,星巴兹为自己制造了一个维护噩梦。请思考如下问题:【参考答案在第 20 行】
It’s pretty obvious that Starbuzz has created a maintenance nightmare for themselves. The Brain Power exercises:1. 在牛奶价格上涨时,系统需要怎样调整? What happens when the price of milk goes up?
2. 当新增一种焦糖调料时,系统需要怎样调整? What do they do when they add a new caramel topping?
3. 系统设计明显违反了下面哪些设计原则?分离变与不变、针对接口编程、优先使用组合、松耦合设计(下文 “5.2.2 原则回顾” 部分有详细些的原则介绍)Which of the design principles that we’ve covered so far are they violating? (Hint: they’re violating two of them in a big way!)书上没有提供答案,以下是我的理解,不知和你的是否一样。
1. 对于所有带牛奶调料的饮料类,都需要修改其 cost() 方法;(如果价格在每个类中硬编码)
2. 对于所有可搭配焦糖调料的饮料,都需要创建新的类;(类就是这样爆炸的)
3. 违反的原则1)分离变与不变:变化的方面包括调料价格、调料类型、为基础饮料添加不同的调料2)优先使用组合:系统完全采用继承方式进行设计
2 遇到问题
为了解决“类爆炸”,系统有了第1个版本的新设计,类图如下:
在 Beverage 类中:
- 定义
milk、soy、mocha、whip实例变量,代表是否添加相应调料; - 定义
has调料()和set调料()方法,用于获取和设置调料的布尔值; cost()不再是抽象方法:- 在超类
cost()中,计算当前饮料实例中所有调料的价格; - 在子类
cost()中,通过超类cost()获得调料价格,再加上子类的基础饮料价格,就可以计算出总价格。
- 在超类
超类 Beverage 的 cost() 方法实现如下:
public class Beverage {public double cost() {double condimentCost = 0.0; // 调料(condiment)总价格if (hasMilk()) { condimentCost += 0.10; }if (hasSoy()) { condimentCost += 0.15; }if (hasMocha()) { condimentCost += 0.20; }if (hasWhip()) { condimentCost += 0.10; }return condimentCost;}
}
子类 DarkRoast 的 cost() 方法实现如下:
public class DarkRoast extends Beverage {public double cost() {return 0.99 + super.cost(); // 基础饮料价格 + 所有调料价格}
}
订购一杯“深焙摩卡奶泡咖啡”的方式如下:
Beverage beverage = new DarkRoast();
beverage.setMocha(true);
beverage.setWhip(true);
System.out.println(beverage.getDescription() + " $" + beverage.cost());
在第1版的新设计中,一共只有5个类,已经解决了“类爆炸”的问题。
思考题:
哪些需求或其它因素的改变会影响这个设计?(多选)【答案在第 20 行】
What requirements or other factors might change that will impact this design? (Choose all that apply.)A. 任意一种调料的价格改变,都需要修改超类代码。Price changes for condiments will force us to alter existing code.
B. 每次增加新的调料时,都需要在超类中添加新的方法,并修改其 cost() 方法。New condiments will force us to add new methods and alter the cost method in the superclass.
C. 未来可能会有某些饮料,并不适合某些调料,但是在这些饮料子类中,仍然要继承超类中的“那些不适合调料的”相关方法。We may have new beverages. For some of these beverages (iced tea?), the condiments may not be appropriate, yet the Tea subclass will still inherit methods like hasWhip().
D. 如果顾客想添加双份摩卡,该怎么办?What if a customer wants a double mocha?答案:A B C D
3 引入设计模式
3.1 OO 原则:开闭原则
设计原则(Design Principle)
类应该对扩展开放,对修改关闭。
Classes should be open for extension, but closed for modification.
- 对修改关闭
- 抱歉,我们的类已经关闭,不能被修改。
Sorry, our classes must remain closed to modification. - 我们花了许多时间得到了正确的代码,解决了所有的bug,所以不能让你修改现有代码。如果你不喜欢,可以找经理谈。
We spent a lot of time getting this code correct and bug free, so we can’t let you alter the existing code. If you don’t like it, you can speak to the manager.
- 抱歉,我们的类已经关闭,不能被修改。
- 对扩展开放
- 欢迎扩展我们的类,加入任何你想要的新行为。
Feel free to extend our classes with any new behavior you like. - 如果你的要求或需求有所改变(我们知道这一定会发生),那就开始吧,创建你自己的扩展。
If your needs or requirements change (and we know they will), just go ahead and make your own extensions.
- 欢迎扩展我们的类,加入任何你想要的新行为。
“开闭原则”的目标是允许类能够轻松地扩展,在不修改现有代码的情况下,就可以加入新的行为。
Our goal is to allow classes to be easily extended to incorporate new behavior without modifying existing code.
这样的设计具有弹性,可以应对改变;并且足够灵活,能够添加新的功能以满足不断变化的需求。
Designs that are resilient to change and flexible enough to take on new functionality to meet changing requirements.
结合“深焙摩卡奶泡咖啡”,我们来了解一种符合“开闭原则”的设计方式:
- 定义
DarkRoast和DarkRoastWithMocha两个类(暂时不考虑其它类) DarkRoastWithMocha组合DarkRoast,即 HAS-A(有一个)DarkRoast类型对象wrappedObj- 将
DarkRoastWithMocha叫做 “装饰者”; - 将
DarkRoast类型的实例变量wrappedObj叫做 “被装饰对象”; - “装饰者” 可以将行为委托给 “被装饰对象”,并且在 “被装饰对象” 行为的基础上,“装饰” 新的行为。
- 将
DarkRoastWithMocha继承DarkRoast,即 IS-A(是一个)DarkRoast类型- “装饰者” 和 “被装饰对象” 具有相同的类型;
- 当 “装饰者” 是 “被装饰对象” 类型(
DarkRoast)变量时:- 使用变量的用户并不知道 “装饰者” 类型的存在(“装饰者” 对用户透明);
- 用户可以像使用 “被装饰对象” 一样,使用 “装饰者”;
- 用户可以将 “装饰者” 当做 “被装饰对象” 再次进行 “装饰” ,即 “递归装饰”。
这样,就可以在不改变 “被装饰对象” 的情况下,通过 “装饰者” 对 “被装饰对象” 进行扩展。即 “对修改关闭,对扩展开放”。
下面结合示例代码,了解具体的实现过程。
“被装饰对象”类的定义:
public class DarkRoast {String description;public DarkRoast() { description = "Dark Roast"; }public String getDescription() { return description; }public double cost() { return 0.99; }
}
“装饰者”类的定义:
public class DarkRoastWithMocha extends DarkRoast {DarkRoast wrappedObj; // “被装饰对象”// 创建“装饰者”时,需要指定“被装饰对象”public DarkRoastWithMocha(DarkRoast darkRoast) { wrappedObj = darkRoast; }// “装饰者”将行为委托给“被装饰对象”,并且在“被装饰对象”行为的基础上,“装饰”新的行为public String getDescription() {return wrappedObj.getDescription() + ", Mocha";}public double cost() {return wrappedObj.cost() + 0.2;}
}
订购一杯“深焙摩卡奶泡咖啡”的方式如下:

// 生成一份“深焙”
DarkRoast darkRoast = new DarkRoast();
System.out.println(darkRoast.getDescription() // "Dark Roast"+ " $" + darkRoast.cost()); // 0.99// 为“深焙”(被装饰对象)添加摩卡装饰,生成“深焙摩卡”(既是装饰者,又可以作为被装饰对象)
darkRoast = new DarkRoastWithMocha(darkRoast);
System.out.println(darkRoast.getDescription() // "Dark Roast, Mocha"+ " $" + darkRoast.cost()); // 0.99 + 0.2 = 1.19// 如果有奶泡装饰者 DarkRoastWithWhip 类,我们就可以
// 为“深焙摩卡”(被装饰对象)添加奶泡装饰,生成“深焙摩卡奶泡”(递归装饰)
// darkRoast = new DarkRoastWithWhip(darkRoast);
// System.out.println(darkRoast.getDescription() // "Dark Roast, Mocha, Whip"
// + " $" + darkRoast.cost()); // 1.19 + 0.1 = 1.29// 目前还没有奶泡装饰者,但是我们可以
// 为“深焙摩卡”(被装饰对象)继续添加摩卡装饰,生成“深焙双摩卡”(重复装饰)
darkRoast = new DarkRoastWithMocha(darkRoast);
System.out.println(darkRoast.getDescription() // "Dark Roast, Mocha, Mocha"+ " $" + darkRoast.cost()); // 1.19 + 0.2 = 1.39
上述示例在不改变 DarkRoast 对象的情况下,通过 DarkRoastWithMocha,以动态(运行时)、透明(类型不变)的方式,扩展了对象的 getDescription() 和 cost() 行为。
3.2 完善“装饰者”设计
在订单系统中,要采用上述类图结构,我们还需要进行一些完善。
首先,类图中不能只有摩卡,还要添加其它调料:
- 由于调料的价格和类型可能会发生改变,所以需要对每种调料进行封装;
- 遵循“针对接口编程”原则,需要为所有调料定义统一的接口。
完善“装饰者”设计后的类图如下:
关于 “装饰者” 部分:
- 调料装饰者抽象类
CondimentDecorator- 继承
DarkRoast,并引用DarkRoast类型的 “被装饰对象”wrappedObj; - 声明抽象的
getDescription()和cost()方法,由子类负责实现。
- 继承
- 调料装饰者类
Milk、Mocha、Soy、Whip- 继承
CondimentDecorator,实现getDescription()和cost()方法,
在方法内部,委托 “被装饰对象” 执行行为,并在 “被装饰对象” 行为的基础上,“装饰” 新的行为(补充描述,增加价格)。
- 继承
3.3 完善“被装饰对象”设计
接下来,类图中也不能只有深度烘焙,还要添加其它饮料:
和调料部分的设计一样,也将每种饮料封装在各自的类中,并定义统一的接口。
现在,我们获得了完整的、更新后的系统类图:
关于 “被装饰对象” 部分:
- 饮料抽象类
Beverage- 定义
getDescription()方法,用于获取饮料描述description; - 声明抽象的
cost()方法,由子类负责实现,包括饮料子类和调料子类。
- 定义
- 饮料具体类
HouseBlend、DarkRoast、Decaf、Espresso- 继承
Beverage,为description赋值,实现cost()方法。
- 继承
采用最新的设计之后,第1版设计 存在的问题 已经得到解决,系统 可以应对改变并易于扩展。
3.4 装饰者模式定义
刚刚我们使用装饰者模式重新设计了咖啡店的订单系统,装饰者模式的正式定义如下:
装饰者模式(Decorator Pattern)
动态地给一个对象添加一些额外的职责。就增加功能来说,装饰者模式相比生成子类更为灵活。
Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
增加功能的方式:生成子类 vs 装饰者模式
| 序号 | 生成子类 | 装饰者模式 | 装饰者模式【优缺点】 |
|---|---|---|---|
| 1 | 给整个类添加功能 | 给某个对象添加功能 | 优点:让“被装饰对象”的类保持简单; 缺点:系统会产生许多看上去相似的“装饰者”类,不便于学习和调试; 缺点:在使用时,不仅要实例化“被装饰对象”,还要实例化各种需要的“装饰者” |
| 2 | 编译时静态添加功能 | 运行时动态添加功能 | 优点:可以在运行时动态添加功能、组合多种不同功能、重复添加一种功能 |
| 3 | 直接执行功能 | 委托“被装饰对象” 执行功能 | 优点:因为“装饰者”与“被装饰对象”具有相同的“超类型”,所以可以对用户透明; 缺点:不适用于依赖具体类型的代码,例如“被装饰对象”定义了某个方法 f(),而“超类型”并没有定义 f(),则不能通过“装饰者”执行 f() |
将订单系统中的类图抽象化,就可以得到设计模式中的类图:
Component抽象类- 为 “被装饰对象” 定义统一的接口,声明抽象方法
operation()。
- 为 “被装饰对象” 定义统一的接口,声明抽象方法
ConcreteComponent“被装饰对象” 类- 继承
Component,实现operation()方法; - “被装饰对象” 可以单独使用;也可以被 “装饰者” 加上新行为(装饰,wrapped)后再使用。
- 继承
Decorator“装饰者” 抽象类- 继承
Component,即 IS-A(是一个)Component,和 “被装饰对象” 具有相同的超类型;- 继承的目的是:达到类型匹配(这样 “装饰者” 可以充当 “被装饰对象”),而不是复用超类的行为。
- 组合
Component,即 HAS-A(有一个)Component类型的 “被装饰对象”wrappedObj;- 组合的目的是:将行为委托给 “被装饰对象”。
- 实现
operation()方法,将行为委托给 “被装饰对象”wrappedObj;
注:由于当前 mermaid 类图不支持 note,所以方法(method)的返回类型都被用于作为注释,如 CallOperationOfWrappedObj
- 继承
ConcreteDecoratorA“装饰者” 具体类- 继承
Decorator,和 “被装饰对象” 具有相同的超类型; - 定义
addedBehavior()方法,实现向 “被装饰对象” 添加的新行为; - 实现
operation()方法,在委托 “被装饰对象” 执行operation()的基础上,附加addedBehavior()行为;- 对于附加行为,即可以放在 “被装饰对象” 行为的前面,也可以放在其后面。
- 继承
ConcreteDecoratorB“装饰者” 具体类- 继承
Decorator,和 “被装饰对象” 具有相同的超类型; - 定义
newState实例变量和newBehavior()方法- 为 “当前类的特定功能” 添加新状态和新行为;
- 例如,“被装饰对象” 是文本视图
TextView,“装饰者” 是滚动条ScrollDecorator,
为了实现视图滚动功能,就会在ScrollDecorator中定义滚动状态scrollPosition和滚动行为ScrollTo()。
- 实现
operation()方法,在委托 “被装饰对象” 执行operation()的基础上,根据需要附加额外行为。
- 继承
注:在设计模式中,重要的是模式的核心思想;在实际设计时,选择定义为接口还是抽象类,以及是否提供默认的方法实现等,可以根据具体的情境来决定。
延伸阅读:《设计模式:可复用面向对象软件的基础》 4.4 Decorator(装饰)— 对象结构型模式 [P132-139]
4 示例代码
4.1 Java 示例
抽象饮料和调料类定义:
// Beverage.java
public abstract class Beverage {String description = "Unknown Beverage";public String getDescription() { return description;}public abstract double cost();
}// CondimentDecorator.java
public abstract class CondimentDecorator extends Beverage {Beverage beverage;public abstract String getDescription();
}
具体饮料类定义:
// DarkRoast.java
public class DarkRoast extends Beverage {public DarkRoast() { description = "Dark Roast Coffee"; }public double cost() { return .99; }
}
具体调料类定义:
// Mocha.java
public class Mocha extends CondimentDecorator {public Mocha(Beverage beverage) {this.beverage = beverage;}public String getDescription() {return beverage.getDescription() + ", Mocha";}public double cost() {return .20 + beverage.cost();}
}// Whip.java
public class Whip extends CondimentDecorator {public Whip(Beverage beverage) {this.beverage = beverage;}public String getDescription() {return beverage.getDescription() + ", Whip";}public double cost() {return .10 + beverage.cost();}
}
测试代码:
// StarbuzzCoffee.java
public class StarbuzzCoffee {public static void main(String args[]) {Beverage beverage = new DarkRoast();System.out.println(beverage.getDescription() + " $" + beverage.cost());beverage = new Mocha(beverage);System.out.println(beverage.getDescription() + " $" + beverage.cost());beverage = new Mocha(beverage);beverage = new Whip(beverage);System.out.println(beverage.getDescription() + " $" + beverage.cost());}
}
4.2 C++11 示例
抽象饮料和调料类定义:
struct Beverage {virtual ~Beverage() = default;virtual std::string getDescription() const { return description; }virtual double cost() = 0;protected:std::string description = "Unknown Beverage";
};struct CondimentDecorator : public Beverage {virtual ~CondimentDecorator() = default;virtual std::string getDescription() const override { return beverage->getDescription(); }virtual double cost() override { return beverage->cost(); }protected:CondimentDecorator(std::shared_ptr<Beverage> beverage) : beverage(beverage) {}std::shared_ptr<Beverage> beverage;
};
具体饮料类定义:
struct DarkRoast : public Beverage {DarkRoast() { description = "Dark Roast Coffee"; }double cost() override { return .99; }
};
具体调料类定义:
struct Mocha : public CondimentDecorator {Mocha(std::shared_ptr<Beverage> beverage) : CondimentDecorator(beverage) {}std::string getDescription() const override {return CondimentDecorator::getDescription() + ", Mocha";}double cost() override { return CondimentDecorator::cost() + .20; }
};struct Whip : public CondimentDecorator {Whip(std::shared_ptr<Beverage> beverage) : CondimentDecorator(beverage) {}std::string getDescription() const override {return CondimentDecorator::getDescription() + ", Whip";}double cost() override { return CondimentDecorator::cost() + .10; }
};
测试代码:
#include <iostream>
#include <memory>
#include <string>// 在这里添加相关接口和类的定义int main() {std::shared_ptr<Beverage> beverage = std::make_shared<DarkRoast>();std::cout << beverage->getDescription() << " $" << beverage->cost() << "\n";beverage = std::make_shared<Mocha>(beverage);std::cout << beverage->getDescription() << " $" << beverage->cost() << "\n";beverage = std::make_shared<Mocha>(beverage);beverage = std::make_shared<Whip>(beverage);std::cout << beverage->getDescription() << " $" << beverage->cost() << "\n";
}
5 设计工具箱
5.1 OO 基础
OO 基础回顾
- 抽象(Abstraction)
- 封装(Encapsulation)
- 继承(Inheritance)
- 多态(Polymorphism)
5.2 OO 原则
5.2.1 新原则
类应该对扩展开放,对修改关闭。
Classes should be open for extension, but closed for modification.
5.2.2 原则回顾
- 封装变化。
Encapsulate what varies. - 针对接口编程,而不是针对实现编程。
Program to interfaces, not implementations. - 优先使用组合,而不是继承。
Favor composition over inheritance. - 尽量做到交互对象之间的松耦合设计。
Strive for loosely coupled designs between objects that interact.
5.3 OO 模式
5.3.1 新模式
装饰者模式(Decorator Pattern)
- 装饰者模式动态地给一个对象添加一些额外的职责。
The Decorator Pattern attaches additional responsibilities to an object dynamically. - 就增加功能来说,装饰者模式相比生成子类更为灵活。
Decorators provide a flexible alternative to subclassing for extending functionality.
5.3.2 模式回顾
- 策略模式(Strategy Pattern)
- 定义一个算法家族,把其中的算法分别封装起来,使得它们之间可以互相替换。
Strategy defines a family of algorithms, encapsulates each one, and makes them interchangeable. - 让算法的变化独立于使用算法的客户。
Strategy lets the algorithm vary independently from clients that use it.
- 定义一个算法家族,把其中的算法分别封装起来,使得它们之间可以互相替换。
- 观察者模式(Observer Pattern)
- 定义对象之间的一对多依赖,
The Observer Pattern defines a one-to-many dependency between objects - 这样一来,当一个对象改变状态时,它的所有依赖者都会被通知并自动更新。
so that when one object changes state, all of its dependents are notified and updated automatically.
- 定义对象之间的一对多依赖,
参考
- [美]弗里曼、罗布森著,UMLChina译.Head First设计模式.中国电力出版社.2022.2
- [美]伽玛等著,李英军等译.设计模式:可复用面向对象软件的基础.机械工业出版社.2019.3
- wickedlysmart: Head First设计模式 Java 源码
Hi, I’m the ENDing, nice to meet you here! Hope this article has been helpful.
相关文章:
【HF设计模式】03-装饰者模式
声明:仅为个人学习总结,还请批判性查看,如有不同观点,欢迎交流。 摘要 《Head First设计模式》第3章笔记:结合示例应用和代码,介绍装饰者模式,包括遇到的问题、遵循的 OO 原则、达到的效果。 …...
【人工智能-中级】模型部署与优化:从本地实验到云端与边缘部署
模型部署与优化:从本地实验到云端与边缘部署 在机器学习和深度学习模型训练完成后,如何高效、稳定地将模型部署到生产环境中,是实际应用中的关键环节。模型部署不仅涉及技术实现,还需要考虑性能优化、资源管理和安全性等多方面因素。本文将全面探讨模型部署与优化的相关内…...
Jenkins 编写Pipeline 简介及使用初识详解
一、Jenkins Pipeline简介 Jenkins Pipeline是Jenkins的一个重要功能,Jenkins 2.0 以上才会有,一系列 Jenkins 插件将整个持续集成用解释性代码 Jenkinsfile 来描述,它允许开发者以代码的方式定义整个持续集成和交付(CI/CD)流程,包括构建、测试、部署和监控等步骤。Jenk…...
uboot移植网络驱动过程,无法ping通mx6ull和ubuntu问题解决方案
开发板:mx6ull-ALPHA_V2.4 ubuntu版本:20.04 1.现在虚拟机设置中添加网路适配器用于开启桥接模式 2.在编辑中打开“虚拟网络编辑器” 我的电脑本身只有VMnet1和VMnet8,需要底下“添加网络”,增加这个VMnet0 ,并且进行…...
精准预测美国失业率和贫困率,谷歌人口动态基础模型PDFM已开源,可增强现有地理空间模型
疾病、经济危机、失业、灾害……人类世界长期以来被各种各样的问题「侵扰」,了解人口动态对于解决这类复杂的社会问题至关重要。 政府相关人员可以通过人口动态数据来模拟疾病的传播,预测房价和失业率,甚至预测经济危机。然而,在过…...
C#速成(文件读、写操作)
导包 using System.IO;1、写入文件(重要) StreamWriter sw new StreamWriter("C:\Users\29674\Desktop\volumn.txt");//创建一个TXT的文件 sw.WriteLine(textBox2.Text);//写入文件的内容 sw.Close();//关闭2、读取文件(不重要&…...
SQL server学习03-创建和管理数据表
目录 一,SQL server的数据类型 1,基本数据类型 2,自定义数据类型 二,使用T-SQL创建表 1,数据完整性的分类 2,约束的类型 3,创建表时创建约束 4,任务 5,由任务编写…...
【UE5 “RuntimeLoadFbx”插件】运行时加载FBX模型
前言 为了解决在Runtime时能够直接根据FBX模型路径直接加载FBX的问题,推荐一款名为“RuntimeLoadFBX”的插件。 用法 插件用法如下,只需要指定fbx的地址就可以在场景中生成Actor模型 通过指定输入参数“Cal Collision”来设置FBX模型的碰撞 还可以通过…...
【潜意识Java】深入理解 Java 面向对象编程(OOP)
目录 什么是面向对象编程(OOP)? 1. 封装(Encapsulation) Java 中的封装 2. 继承(Inheritance) Java 中的继承 3. 多态(Polymorphism) Java 中的多态 4. 抽象&…...
windows同时使用多个网卡
windows同时链接了有线网络,多个无线网卡,默认会使用有线网络,如果想要局域网内使用某个特定的网络,可以设置静态ip 1. 首先删除原来的静态网络(不冲突可以不删除),我这里usb无线网卡切换过usb插口,这里需要删除原来的. 使用 route print 查看接口列表及静态路由信息 route p…...
Spark执行计划解析后是如何触发执行的?
在前一篇Spark SQL 执行计划解析源码分析中,笔者分析了Spark SQL 执行计划的解析,很多文章甚至Spark相关的书籍在讲完执行计划解析之后就开始进入讲解Stage切分和调度Task执行,每个概念之间没有强烈的关联,因此这中间总感觉少了点…...
B4X编程语言:B4X控件方法汇总
1、AddNode、AddView方法 AddNode(Node As javafx.scence.Node,Left As Double,Top As Double,Width As Double,Height As Double) B4J控件 AddView(View As javafx.scence.Node,Left As Double,Top As Double,Width As Double,Height As Double) B4J的B4XView …...
基于XML配置Bean和基于XML自动装配
目录 基于XML配置Bean id分配规则 通过id获取bean 通过类型获取bean 通过C命名空间配置bean 使用C命名空间 通过P命名空间配置bean 通过util:list进行配置bean 指定id,直接ref引用过来 通过外部属性文件配置Bean Bean信息重用(继承)…...
全排列 dfs
给定一个由不同的小写字母组成的字符串,输出这个字符串的所有全排列。 我们假设对于小写字母有 a<b<…<y<z ,而且给定的字符串中的字母已经按照从小到大的顺序排列。 输入格式 输入只有一行,是一个由不同的小写字母组成的字符串…...
linux内存相关命令的尝试
文章目录 前言freeMem 部分的解释Swap 部分的解释 vmstatProcs (进程)Memory (内存)Swap (交换)IO (磁盘 I/O)System (系统)CPU (处理器) pidstat标题行解释数据列解释 sar字段含义解释示例分析 总结 前言 菜就多练,昨天看了一篇有关剖析 RocksDB 内存超限问题的文…...
Vue2 基础
Vue 2 是 Vue.js 的第二个主要版本,于 2016 年发布。它是一个渐进式的 JavaScript 框架,以其简单、灵活、易用性高而广受欢迎。Vue 2 主要专注于构建用户界面(UI),并且非常适合用于构建单页应用(SPA&#x…...
递归问题(c++)
递归设计思路 数列递归 : 如果一个数列的项与项之间存在关联性,那么可以使用递归实现 ; 原理 : 如果一个函数可以求A(n),那么该函数就可以求A(n-1),就形成了递归调用 ; 注意: 一般起始项是不需要求解的,是已知条件 这就是一个典型…...
系统思考—战略决策
别用管理上的勤奋,来掩盖经营上的懒惰。 日本一家物业公司,因经营不善,面临生死存亡的危机。老板为了扭转局面,采取了很多管理手段——提高员工积极性,推行业绩与绩效挂钩,实施各种考核制度。然而…...
wxwidgets xml插入图片的两种方案
wxwidgets xml插入图片的两种方案: 1.使用wxWidgets中的wxRichTextCtrl插入图片,将wxRichTextCtrl的buffer导出为xml文件 参考richtextctrl例子 if (wxTheClipboard->IsSupported(wxDF_BITMAP)){wxBitmapDataObject data;wxTheClipboard->GetData(data);wxBitmap bi…...
大模型呼入机器人如何赋能呼叫中心?(转)
大模型呼入机器人如何赋能呼叫中心?(转) 原作者:开源呼叫中心FreeIPCC 大模型呼入机器人在赋能呼叫中心方面发挥着重要作用,主要体现在以下几个方面: 一、提升服务效率与质量 24小时不间断服务: 大模型呼入机器人能…...
MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...
Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级
在互联网的快速发展中,高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司,近期做出了一个重大技术决策:弃用长期使用的 Nginx,转而采用其内部开发…...
css3笔记 (1) 自用
outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size:0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格ÿ…...
聊一聊接口测试的意义有哪些?
目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开,首…...
Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理
引言 Bitmap(位图)是Android应用内存占用的“头号杀手”。一张1080P(1920x1080)的图片以ARGB_8888格式加载时,内存占用高达8MB(192010804字节)。据统计,超过60%的应用OOM崩溃与Bitm…...
Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)
在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马(服务器方面的)的原理,连接,以及各种木马及连接工具的分享 文件木马:https://w…...
uniapp 开发ios, xcode 提交app store connect 和 testflight内测
uniapp 中配置 配置manifest 文档:manifest.json 应用配置 | uni-app官网 hbuilderx中本地打包 下载IOS最新SDK 开发环境 | uni小程序SDK hbulderx 版本号:4.66 对应的sdk版本 4.66 两者必须一致 本地打包的资源导入到SDK 导入资源 | uni小程序SDK …...
Git常用命令完全指南:从入门到精通
Git常用命令完全指南:从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...
