【HF设计模式】03-装饰者模式

news2024/12/16 3:15:07

声明:仅为个人学习总结,还请批判性查看,如有不同观点,欢迎交流。

摘要

《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)咖啡店的订单系统。

最开始,店里只提供黑咖啡,系统类图如下:

«abstract»
Beverage
description
getDescription()
cost()
HouseBlend
cost()
DarkRoast
cost()
Decaf
cost()
Espresso
cost()
  1. Beverage(饮料),是一个抽象类,咖啡店售卖的所有饮料都继承这个类
    • 定义 description 实例变量,用于保存饮料描述;
    • 定义 getDescription() 方法,用于获取 description
    • 声明抽象的 cost() 方法,用于获取饮料价格,由子类负责实现。
  2. 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
String description
boolean milk
boolean soy
boolean mocha
boolean whip
getDescription()
cost()
hasMilk()
setMilk()
hasSoy()
setSoy()
hasMocha()
setMocha()
hasWhip()
setWhip()
HouseBlend
cost()
DarkRoast
cost()
Decaf
cost()
Espresso
cost()

Beverage 类中:

  1. 定义 milksoymochawhip 实例变量,代表是否添加相应调料;
  2. 定义 has调料()set调料() 方法,用于获取和设置调料的布尔值;
  3. cost() 不再是抽象方法:
    • 在超类 cost() 中,计算当前饮料实例中所有调料的价格;
    • 在子类 cost() 中,通过超类 cost() 获得调料价格,再加上子类的基础饮料价格,就可以计算出总价格。

超类 Beveragecost() 方法实现如下:

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;
    }
}

子类 DarkRoastcost() 方法实现如下:

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
description
getDescription()
cost()
DarkRoastWithMocha
DarkRoast wrappedObj
  1. 定义 DarkRoastDarkRoastWithMocha 两个类(暂时不考虑其它类)
  2. DarkRoastWithMocha 组合 DarkRoast,即 HAS-A(有一个)DarkRoast 类型对象 wrappedObj
    • DarkRoastWithMocha 叫做 “装饰者”;
    • DarkRoast 类型的实例变量 wrappedObj 叫做 “被装饰对象”;
    • “装饰者” 可以将行为委托给 “被装饰对象”,并且在 “被装饰对象” 行为的基础上,“装饰” 新的行为。
  3. 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 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 完善“装饰者”设计

在订单系统中,要采用上述类图结构,我们还需要进行一些完善。

首先,类图中不能只有摩卡,还要添加其它调料:

  1. 由于调料的价格和类型可能会发生改变,所以需要对每种调料进行封装;
  2. 遵循“针对接口编程”原则,需要为所有调料定义统一的接口。

完善“装饰者”设计后的类图如下:

DarkRoast
description
getDescription()
cost()
«abstract»
CondimentDecorator
DarkRoast wrappedObj
getDescription()
cost()
Milk
getDescription()
cost()
Mocha
getDescription()
cost()
Soy
getDescription()
cost()
Whip
getDescription()
cost()

关于 “装饰者” 部分:

  1. 调料装饰者抽象类 CondimentDecorator
    • 继承 DarkRoast,并引用 DarkRoast 类型的 “被装饰对象” wrappedObj
    • 声明抽象的 getDescription()cost() 方法,由子类负责实现。
  2. 调料装饰者类 MilkMochaSoyWhip
    • 继承 CondimentDecorator,实现 getDescription()cost() 方法,
      在方法内部,委托 “被装饰对象” 执行行为,并在 “被装饰对象” 行为的基础上,“装饰” 新的行为(补充描述,增加价格)。

3.3 完善“被装饰对象”设计

接下来,类图中也不能只有深度烘焙,还要添加其它饮料:
和调料部分的设计一样,也将每种饮料封装在各自的类中,并定义统一的接口。

现在,我们获得了完整的、更新后的系统类图:

«abstract»
Beverage
description
getDescription()
cost()
«abstract»
CondimentDecorator
Beverage beverage
getDescription()
HouseBlend
cost()
DarkRoast
cost()
Decaf
cost()
Espresso
cost()
Milk
getDescription()
cost()
Mocha
getDescription()
cost()
Soy
getDescription()
cost()
Whip
getDescription()
cost()

关于 “被装饰对象” 部分:

  1. 饮料抽象类 Beverage
    • 定义 getDescription() 方法,用于获取饮料描述 description
    • 声明抽象的 cost() 方法,由子类负责实现,包括饮料子类和调料子类。
  2. 饮料具体类 HouseBlendDarkRoastDecafEspresso
    • 继承 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()

将订单系统中的类图抽象化,就可以得到设计模式中的类图:

«abstract»
Component
operation()
«abstract»
Decorator
Component wrappedObj
operation() : CallOperationOfWrappedObj
ConcreteComponent
operation()
ConcreteDecoratorA
operation() : CallAddedBehaviorAndOperation
addedBehavior()
ConcreteDecoratorB
newState
operation()
newBehavior()
  1. Component 抽象类
    • 为 “被装饰对象” 定义统一的接口,声明抽象方法 operation()
  2. ConcreteComponent “被装饰对象” 类
    • 继承 Component,实现 operation() 方法;
    • “被装饰对象” 可以单独使用;也可以被 “装饰者” 加上新行为(装饰,wrapped)后再使用。
  3. Decorator “装饰者” 抽象类
    • 继承 Component,即 IS-A(是一个)Component,和 “被装饰对象” 具有相同的超类型;
      • 继承的目的是:达到类型匹配(这样 “装饰者” 可以充当 “被装饰对象”),而不是复用超类的行为。
    • 组合 Component,即 HAS-A(有一个)Component 类型的 “被装饰对象” wrappedObj
      • 组合的目的是:将行为委托给 “被装饰对象”。
    • 实现 operation() 方法,将行为委托给 “被装饰对象” wrappedObj
      注:由于当前 mermaid 类图不支持 note,所以方法(method)的返回类型都被用于作为注释,如 CallOperationOfWrappedObj
  4. ConcreteDecoratorA “装饰者” 具体类
    • 继承 Decorator,和 “被装饰对象” 具有相同的超类型;
    • 定义 addedBehavior() 方法,实现向 “被装饰对象” 添加的新行为;
    • 实现 operation() 方法,在委托 “被装饰对象” 执行 operation() 的基础上,附加 addedBehavior() 行为;
      • 对于附加行为,即可以放在 “被装饰对象” 行为的前面,也可以放在其后面。
  5. 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 基础回顾

  1. 抽象(Abstraction)
  2. 封装(Encapsulation)
  3. 继承(Inheritance)
  4. 多态(Polymorphism)

5.2 OO 原则

5.2.1 新原则

类应该对扩展开放,对修改关闭。
Classes should be open for extension, but closed for modification.

5.2.2 原则回顾

  1. 封装变化。
    Encapsulate what varies.
  2. 针对接口编程,而不是针对实现编程。
    Program to interfaces, not implementations.
  3. 优先使用组合,而不是继承。
    Favor composition over inheritance.
  4. 尽量做到交互对象之间的松耦合设计。
    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 模式回顾

  1. 策略模式(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.
  2. 观察者模式(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.

参考

  1. [美]弗里曼、罗布森著,UMLChina译.Head First设计模式.中国电力出版社.2022.2
  2. [美]伽玛等著,李英军等译.设计模式:可复用面向对象软件的基础.机械工业出版社.2019.3
  3. wickedlysmart: Head First设计模式 Java 源码

Hi, I’m the ENDing, nice to meet you here! Hope this article has been helpful.

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

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

相关文章

Linux查看是否有www-data用户,如果没有添加一个

在 Linux 系统中&#xff0c;www-data 用户通常是用来运行 Web 服务&#xff08;如 Nginx 或 Apache&#xff09;的。如果你想检查系统中是否已经存在 www-data 用户&#xff0c;并在没有的情况下添加一个&#xff0c;可以按照以下步骤操作&#xff1a; ### 1. 检查 www-data …

23.模块和包

模块 模块Module,是一个python文件&#xff0c;以.py结尾。 模块能定义函数、类和变量。 模块导入 模块在使用前需要先导入 [from 模块名] import [模块 | 类 | 变量 | 函数 | *] [as 别名] import 模块 import time print("start...") time.sleep(5) print(&…

IDEA报错:无效的源发行版、无效的目标发行版

1. 无效的源发行版 创建项目的时候&#xff0c;会遇见这个报错&#xff0c;原因就是编译的JDK版本与发布版本不一致。 解决方法&#xff1a; 1.1. 找到问题所在地 英文&#xff1a;File -> Project Structure ->Project Settings 中文&#xff1a;文件->项目结构 …

2025年,客服知识库与人工智能的结合

随着人工智能&#xff08;AI&#xff09;技术的飞速发展&#xff0c;传统客服模式正在经历前所未有的变革。特别是在2025年&#xff0c;客服知识库与AI的深度融合&#xff0c;不仅极大地提升了客服处理的效率与准确性&#xff0c;还为用户带来了更加个性化、高效的服务体验。 …

JVM 双亲委派模型以及垃圾回收机制

目录 1. JVM 内存区域划分 2. JVM 中类加载的过程 1) 类加载的基本流程 2) 双亲委派模型 3. JVM 中垃圾回收机制 1) 找到垃圾 a) 引用计数 b) 可达性分析 2) 释放垃圾 1. JVM 内存区域划分 一个运行起来的 Java 进程&#xff0c;其实就是一个 JVM 虚拟机。 而进程是…

微信小程序跳转其他小程序以及跳转网站

一、跳转其他小程序 1.1 知道appid和页面路径 wx.navigateToMiniProgram({appId: appid, // 替换为目标小程序 AppIDpath: pathWithParams, // 小程序路径envVersion: release, // 开发版、体验版或正式版success(res) {console.log("跳转到其他小程序成功&#xff01;&q…

学习笔记:从ncsi/nc-si协议和代码了解网络协议的设计范式

学习笔记&#xff1a;从ncsi/nc-si协议和代码了解网络协议的设计范式 参考文档&#xff1a; https://www.dmtf.org/standards/published_documents https://www.dmtf.org/dsp/DSP0222 https://www.dmtf.org/sites/default/files/standards/documents/DSP0222_1.2.0.pdf参考代…

深度学习训练参数之学习率介绍

学习率 1. 什么是学习率 学习率是训练神经网络的重要超参数之一&#xff0c;它代表在每一次迭代中梯度向损失函数最优解移动的步长&#xff0c;通常用 η \eta η 表示。它的大小决定网络学习速度的快慢。在网络训练过程中&#xff0c;模型通过样本数据给出预测值&#xff0…

【数据结构——线性表】单链表的基本运算(头歌实践教学平台习题)【合集】

目录&#x1f60b; 任务描述 相关知识 测试说明 我的通关代码: 测试结果&#xff1a; 任务描述 本关任务&#xff1a;编写一个程序实现单链表的基本运算。 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a;初始化线性表、销毁线性表、判定是否为空表、求线性…

利用ROS的Camera Calibration工具进行D435相机标定

一、安装ROS Camera Calibration sudo apt-get install ros-melodic-camera-calibration 二、安装realsense-ros 安装ROS Wrapper for Intel RealSense&#xff08;realsense-ros&#xff09; 三、启动数据读取节点 ctrlaltt打开终端 cd catkin_ws_ur source devel/setu…

让文案生成更具灵活性/chatGPT新功能canvas画布编辑

​ ​ OpenAI最近在2024年12月发布了canvas画布编辑功能&#xff0c;这是一项用途广泛的创新工具&#xff0c;专为需要高效创作文案的用户设计。 无论是职场人士、学生还是创作者&#xff0c;这项功能都能帮助快速生成、优化和编辑文案&#xff0c;提升效率的同时提高内容质量…

C# 网络编程--关于UDP 通信(二)

UDP (User Datagram Protocol) 是一种无连接的传输层协议&#xff0c;主要用于支持数据报文的传输。它的主要特点包括简单、高效、不保证可靠性和顺序。 1.UDP协议基本概念 1.udp基于IP的简单的协议&#xff0c;不可靠的协议 2.优点&#xff1a;简单、 轻量化、 传输速度高、…

Spring Boot 集成 MyBatis 全面讲解

Spring Boot 集成 MyBatis 全面讲解 MyBatis 是一款优秀的持久层框架&#xff0c;与 Spring Boot 集成后可以大大简化开发流程。本文将全面讲解如何在 Spring Boot 中集成 MyBatis&#xff0c;包括环境配置、基础操作、高级功能和最佳实践。 一、MyBatis 简介 1. SqlSession …

解决Jmeter HTTP Cookie管理器cookie不生效

解决Jmeter HTTP Cookie管理器cookie不生效问题 解决Jmeter HTTP Cookie管理器cookie不生效问题1、设置Jmeter HTTP Cookie管理器cookie后&#xff0c;发起的请求显示[no cookies]jmeter问题复现&#xff1a;这里同样使用postman进行重试&#xff0c;发现是可以正常获取数据的&…

freeswitch(开启支持MCU视频会议,使用mod_av模块)

亲测版本centos 7.9系统–》 freeswitch1.10.9 本人freeswitch安装路径(根据自己的路径进入) /usr/local/freeswitch/etc/freeswitch场景说明: 有些场景想使用视频会议MCU融合画面进行开会使用方法: 第一步:下载插件 yum install -y epel-release yum install

IntelliJ IDEA 使用技巧与插件推荐

目录 常用使用技巧 1. 使用快捷键提升开发效率 2. 多光标编辑 3. 代码自动补全 4. 使用 Find Action 快速执行操作 5. 集成版本控制系统&#xff08;VCS&#xff09; 6. 快速查看代码文档 推荐插件 1. Lombok Plugin 2. Rainbow Brackets 3. Key Promoter X 4. Chec…

Centos7环境下安装Flink1.20

目录 介绍1、涉及安装包2、节点3、修改hostname4、将flink安装包上传并解压5、修改配置文件6、修改masters和workers&#xff08;所有节点&#xff09;7、集群启停 介绍 Flink 是一个分布式系统&#xff0c;需要有效分配和管理计算资源才能执行流应用程序。它集成了所有常见的…

CTFHub 技能树 Web RCE eval执行(学习记录)

eval执行 源代码 <?php if (isset($_REQUEST[cmd])) {eval($_REQUEST["cmd"]); } else {highlight_file(__FILE__); } ?> PHP代码显示&#xff0c;要求将命令赋值给cmd然后执行 先查看一下根目录文件 /?cmdsystem("ls"); 查看上一级目录找flag文…

人员离岗监测摄像机智能人员睡岗、逃岗监测 Python 语言结合 OpenCV

在安全生产领域&#xff0c;人员的在岗状态直接关系到生产流程的顺利进行和工作环境的安全稳定。人员离岗监测摄像机的出现&#xff0c;为智能人员睡岗、逃岗监测提供了高效精准的解决方案&#xff0c;而其中的核心技术如AI识别睡岗脱岗以及相关的算法盒子和常见的安全生产AI算…

Web day11 SpringBoot原理

目录 1.配置优先级&#xff1a; 2.Bean的管理&#xff1a; bean的作用域&#xff1a; 第三方bean&#xff1a; 方案一&#xff1a; 方案二&#xff1a; SpringBoot原理&#xff1a; 扫描第三方包&#xff1a; 方案1&#xff1a;ComponentScan 组件扫描 方案2&#xff1…