C++ 设计模式——策略模式

news2024/9/20 10:50:52

策略模式

    • 策略模式
      • 主要组成部分
      • 例一:逐步重构并引入策略模式
        • 第一步:初始实现
        • 第二步:提取共性并实现策略接口
        • 第三步:实现具体策略类
        • 第四步:实现上下文类
        • 策略模式 UML 图
          • 策略模式的 UML 图解析
      • 例二:逐步重构并引入策略模式
        • 第一步:初始实现
        • 第二步:提取共性并实现策略接口
        • 第三步:实现具体策略类
        • 第四步:实现上下文类:
        • 第五步:更新主函数
      • 策略模式 UML 图
        • 策略模式的 UML 图解析
      • 策略模式优缺点
      • 策略模式适用场景
      • 例一的完整代码
      • 例二的完整代码

策略模式

策略模式是一种行为设计模式,它定义了一系列算法,将每一个算法封装起来,并使它们可以互换。策略模式让算法的变化独立于使用算法的客户。

引入"策略“设计模式的定义:定义一些列算法类(策略类),将每个算法封装起来,让他们可以互相替换。换句话说,策略模式通常把一系列算法封装到一系列具体策略类中作为抽象策略类的字类,然后根据实际需要适用这些字类。

主要组成部分

  • 策略接口 (Strategy):定义一个统一的接口,声明所有支持的算法(策略)的方法。客户端通过这个接口调用具体策略。

  • 具体策略类 (Concrete Strategy):实现策略接口,定义具体的算法或行为。每个具体策略类实现不同的算法。

  • 上下文类 (Context):持有对策略接口的引用,负责调用具体策略的方法。上下文类可以在运行时动态地切换策略。

例一:逐步重构并引入策略模式

假设我们需要实现不同的排序算法(如冒泡排序和快速排序)。最初的实现可能是重复的代码。逐步重构的过程如下:

第一步:初始实现

为每种排序算法编写独立的处理逻辑:

#include <iostream>
#include <vector>

void bubbleSort(std::vector<int>& arr) {
    for (size_t i = 0; i < arr.size() - 1; ++i) {
        for (size_t j = 0; j < arr.size() - i - 1; ++j) {
            if (arr[j] > arr[j + 1]) {
                std::swap(arr[j], arr[j + 1]);
            }
        }
    }
}

void quickSort(std::vector<int>& arr, int low, int high) {
    if (low < high) {
        int pivot = arr[high];
        int i = low - 1;
        for (int j = low; j < high; ++j) {
            if (arr[j] < pivot) {
                ++i;
                std::swap(arr[i], arr[j]);
            }
        }
        std::swap(arr[i + 1], arr[high]);
        quickSort(arr, low, i);
        quickSort(arr, i + 2, high);
    }
}

int main() {
    std::vector<int> data = {5, 3, 8, 6, 2};

    bubbleSort(data);
    // 或者使用 quickSort(data, 0, data.size() - 1);

    return 0;
}
第二步:提取共性并实现策略接口

识别出排序的共性步骤,并创建一个策略接口 SortStrategy,定义排序方法:

class SortStrategy {
public:
    virtual void sort(std::vector<int>& arr) = 0; // 策略接口
};
第三步:实现具体策略类

为每种排序算法实现具体策略类,重写排序方法:

class BubbleSort : public SortStrategy {
public:
    void sort(std::vector<int>& arr) override {
        for (size_t i = 0; i < arr.size() - 1; ++i) {
            for (size_t j = 0; j < arr.size() - i - 1; ++j) {
                if (arr[j] > arr[j + 1]) {
                    std::swap(arr[j], arr[j + 1]);
                }
            }
        }
    }
};

class QuickSort : public SortStrategy {
public:
    void sort(std::vector<int>& arr) override {
        quickSort(arr, 0, arr.size() - 1);
    }

private:
    void quickSort(std::vector<int>& arr, int low, int high) {
        if (low < high) {
            int pivot = arr[high];
            int i = low - 1;
            for (int j = low; j < high; ++j) {
                if (arr[j] < pivot) {
                    ++i;
                    std::swap(arr[i], arr[j]);
                }
            }
            std::swap(arr[i + 1], arr[high]);
            quickSort(arr, low, i);
            quickSort(arr, i + 2, high);
        }
    }
};
第四步:实现上下文类

创建一个上下文类 Sorter,用于使用策略:

class Sorter {
private:
    SortStrategy* strategy;

public:
    Sorter(SortStrategy* strategy) : strategy(strategy) {}

    void setStrategy(SortStrategy* strategy) {
        this->strategy = strategy;
    }

    void sort(std::vector<int>& arr) {
        strategy->sort(arr);
    }
};
策略模式 UML 图

策略模式 UML 图1

策略模式的 UML 图解析
  • 上下文类 (Context)

    • Sorter:它是上下文类,负责使用策略并持有策略的引用。Sorter 类通过 setStrategy 方法设置具体的排序策略,并通过 sort 方法调用该策略进行排序。
  • 抽象策略类 (Strategy)

    • SortStrategy:这是策略接口,定义了一个公共方法 sort,该方法由具体策略类实现。它确保所有具体策略类都有一致的接口。
  • 具体策略类 (Concrete Strategy)

    • BubbleSort:实现了 SortStrategy 接口,提供了冒泡排序的具体实现。
    • QuickSort:实现了 SortStrategy 接口,提供了快速排序的具体实现。

例二:逐步重构并引入策略模式

第一步:初始实现

首先,从一个简单的实现开始,其中战斗者类直接实现道具使用逻辑。

#include <iostream>
using namespace std;

class Fighter {
public:
    Fighter(int life) : m_life(life) {}
    
    void UseBXD() { // 使用补血丹
        m_life += 200;
        cout << "使用补血丹,生命值增加200。" << endl;
    }

    void UseDHD() { // 使用大还丹
        m_life += 300;
        cout << "使用大还丹,生命值增加300。" << endl;
    }

    void UseSHD() { // 使用守护丹
        m_life += 500;
        cout << "使用守护丹,生命值增加500。" << endl;
    }

    int GetLife() {
        return m_life;
    }

private:
    int m_life;
};

int main() {
    Fighter warrior(1000);
    cout << "初始生命值:" << warrior.GetLife() << endl;

    warrior.UseDHD();
    cout << "当前生命值:" << warrior.GetLife() << endl;

    warrior.UseBXD();
    cout << "当前生命值:" << warrior.GetLife() << endl;

    warrior.UseSHD();
    cout << "当前生命值:" << warrior.GetLife() << endl;

    return 0;
}
第二步:提取共性并实现策略接口

在该实现中,Fighter 类包含所有道具使用的逻辑。可以提取出道具使用的共性,定义一个道具策略接口。

class ItemStrategy {
public:
    virtual void UseItem(Fighter* fighter) = 0;
    virtual ~ItemStrategy() {}
};
第三步:实现具体策略类

为每种道具创建具体的策略类,实现 ItemStrategy 接口。

  • 补血丹策略类
class ItemStrategy_BXD : public ItemStrategy {
public:
    void UseItem(Fighter* mainobj) override {
        mainobj->SetLife(mainobj->GetLife() + 200); // 补充200点生命值
    }
};
  • 大还丹策略类
class ItemStrategy_DHD : public ItemStrategy {
public:
    void UseItem(Fighter* mainobj) override {
        mainobj->SetLife(mainobj->GetLife() + 300); // 补充300点生命值
    }
};
  • 守护丹策略类
class ItemStrategy_SHD : public ItemStrategy {
public:
    void UseItem(Fighter* mainobj) override {
        mainobj->SetLife(mainobj->GetLife() + 500); // 补充500点生命值
    }
};
第四步:实现上下文类:

Fighter 类中添加设置道具策略的方法,并移除具体的道具使用逻辑,并定义战斗者的子类(可以不需要字类)。

class Fighter {
public:
    Fighter(int life) : m_life(life), itemStrategy(nullptr) {}

    void SetItemStrategy(ItemStrategy* strategy) {
        itemStrategy = strategy;
    }

    void UseItem() {
        if (itemStrategy) {
            itemStrategy->UseItem(this);
        }
    }

    int GetLife() {
        return m_life;
    }

    void SetLife(int life) {
        m_life = life;
    }

private:
    int m_life;
    ItemStrategy* itemStrategy;
};
class F_Warrior : public Fighter {
public:
    F_Warrior(int life, int magic, int attack) 
        : Fighter(life, magic, attack) {}
    // 其他战士特有的方法
};

class F_Mage : public Fighter {
public:
    F_Mage(int life, int magic, int attack) 
        : Fighter(life, magic, attack) {}
    // 其他法师特有的方法
};
第五步:更新主函数

更新主函数以使用新的策略模式结构。

int main() {
    Fighter warrior(1000);
    cout << "初始生命值:" << warrior.GetLife() << endl;

    ItemStrategy* strategy1 = new ItemStrategy_DHD();
    warrior.SetItemStrategy(strategy1);
    warrior.UseItem();
    cout << "当前生命值:" << warrior.GetLife() << endl;

    ItemStrategy* strategy2 = new ItemStrategy_BXD();
    warrior.SetItemStrategy(strategy2);
    warrior.UseItem();
    cout << "当前生命值:" << warrior.GetLife() << endl;

    ItemStrategy* strategy3 = new ItemStrategy_SHD();
    warrior.SetItemStrategy(strategy3);
    warrior.UseItem();
    cout << "当前生命值:" << warrior.GetLife() << endl;

    delete strategy1;
    delete strategy2;
    delete strategy3;

    return 0;
}

策略模式 UML 图

策略模式 UML 图2

策略模式的 UML 图解析
  • Context(环境类):也叫上下文类,是使用算法的角色,该类中维持着一个对抽象策略类的指针或引用。这里指Fighter类。
  • Stategy(抽象策略类):定义所支持的算法的公共接口,是所有策略类的父类。这里指 ItemStrategy 类。
  • ConcreteStrategy(具体策略类):抽象策略类的子类,实现抽象策略类中声明的接口。这里指ItemStrategy_BXDItemStrategy_DHDItemStrategy_SHD类。

策略模式优缺点

优点

  • 灵活性:可以在运行时选择不同的策略,增加了程序的灵活性和可扩展性。

  • 开放-关闭原则:新的策略可以通过实现策略接口而无需修改现有代码,符合开放-关闭原则。

  • 清晰的职责分离:将不同的算法封装在不同的类中,使得代码更加清晰,易于维护。

  • 减少条件语句:避免了使用大量的条件语句(如 if-elseswitch),使代码结构更加简洁。

  • 易于测试:每个策略类可以独立测试,便于单元测试和调试。

缺点

  • 类的数量增加:每种策略都需要一个新的类,可能导致类的数量增加,增加了系统的复杂性。

  • 客户端必须了解所有策略:客户端需要了解所有可用的策略,以便选择合适的策略,这可能增加了使用的复杂性。

  • 性能开销:在某些情况下,频繁地创建和销毁策略对象可能导致性能开销。

  • 不适合简单的算法:对于简单的算法,使用策略模式可能显得过于复杂,增加了不必要的抽象。

策略模式适用场景

  • 多种算法:当有多个算法可以选择时,策略模式可以将它们封装起来,方便切换。
  • 避免条件语句:当使用大量条件语句来选择算法时,可以使用策略模式来简化代码结构。
  • 动态选择算法:当需要在运行时选择算法时,策略模式提供了灵活性。
  • 算法复用:当多个类需要使用同一算法时,可以将算法封装成策略类,促进代码复用。

例一的完整代码

以下是完整的实现代码:

#include <iostream>
#include <vector>

// 策略接口
class SortStrategy
{
public:
    virtual void sort(std::vector<int>& arr) = 0; // 策略接口
};

// 具体策略:冒泡排序
class BubbleSort : public SortStrategy {
public:
    void sort(std::vector<int>& arr) override {
        for (size_t i = 0; i < arr.size() - 1; ++i) {
            for (size_t j = 0; j < arr.size() - i - 1; ++j) {
                if (arr[j] > arr[j + 1]) {
                    std::swap(arr[j], arr[j + 1]);
                }
            }
        }
    }
};

// 具体策略:快速排序
class QuickSort : public SortStrategy {
public:
    void sort(std::vector<int>& arr) override {
        quickSort(arr, 0, arr.size() - 1);
    }

private:
    void quickSort(std::vector<int>& arr, int low, int high) {
        if (low < high) {
            int pivot = arr[high];
            int i = low - 1;
            for (int j = low; j < high; ++j) {
                if (arr[j] < pivot) {
                    ++i;
                    std::swap(arr[i], arr[j]);
                }
            }
            std::swap(arr[i + 1], arr[high]);
            quickSort(arr, low, i);
            quickSort(arr, i + 2, high);
        }
    }
};

// 上下文类
class Sorter {
private:
    SortStrategy* strategy;

public:
    Sorter(SortStrategy* strategy) : strategy(strategy) {}

    void setStrategy(SortStrategy* strategy) {
        this->strategy = strategy;
    }

    void sort(std::vector<int>& arr) {
        strategy->sort(arr);
    }
};

// 示例用法
int main() {
    std::vector<int> data = {5, 3, 8, 6, 2};

    Sorter sorter(new BubbleSort());
    sorter.sort(data); // 使用冒泡排序

    sorter.setStrategy(new QuickSort());
    sorter.sort(data); // 使用快速排序

    return 0;
}

例二的完整代码

#include <iostream>
using namespace std;

class Fighter; // 类前向声明

//道具策略类的父类
class ItemStrategy
{
public:
    virtual void UseItem(Fighter* mainobj) = 0;
    virtual ~ItemStrategy() {}
};

//战斗者父类
class Fighter
{
public:
    Fighter(int life, int magic, int attack) :m_life(life), m_magic(magic), m_attack(attack) {}
    virtual ~Fighter() {}

public:
    void SetItemStrategy(ItemStrategy* strategy) //设置道具使用的策略
    {
        itemstrategy = strategy;
    }
    void UseItem() //使用道具(吃药)
    {
        itemstrategy->UseItem(this);
    }
    int  GetLife() //获取人物生命值
    {
        return m_life;
    }
    void SetLife(int life) //设置人物生命值
    {
        m_life = life;
    }

private:
    ItemStrategy* itemstrategy = nullptr; //C++11中支持这样初始化

protected:
    int m_life;
    int m_magic;
    int m_attack;
};

//“战士”类,父类为Fighter
class F_Warrior :public Fighter
{
public:
    F_Warrior(int life, int magic, int attack) :Fighter(life, magic, attack) {}
};

//“法师”类,父类为Fighter
class F_Mage :public Fighter
{
public:
    F_Mage(int life, int magic, int attack) :Fighter(life, magic, attack) {}
};


//补血丹策略类
class ItemStrategy_BXD : public ItemStrategy
{
public:
    virtual void UseItem(Fighter* mainobj)
    {
        mainobj->SetLife(mainobj->GetLife() + 200); //补充200点生命值
        cout << "使用补血丹,生命值增加200。" << endl;
    }
};

//大还丹策略类
class ItemStrategy_DHD : public ItemStrategy
{
public:
    virtual void UseItem(Fighter* mainobj)
    {
        mainobj->SetLife(mainobj->GetLife() + 300); //补充300点生命值
        cout << "使用大还丹,生命值增加300。" << endl;
    }
};

//守护丹策略类
class ItemStrategy_SHD : public ItemStrategy
{
public:
    virtual void UseItem(Fighter* mainobj)
    {
        mainobj->SetLife(mainobj->GetLife() + 500); //补充500点生命值
        cout << "使用守护丹,生命值增加500。" << endl;
    }
};

int main()
{
    // 创建战斗者对象
    Fighter* prole_war = new Fighter(1000, 0, 200);

    // 打印初始生命值
    cout << "初始生命值:" << prole_war->GetLife() << endl;

    // 使用大还丹
    ItemStrategy* strategy1 = new ItemStrategy_DHD();
    prole_war->SetItemStrategy(strategy1);
    prole_war->UseItem();
    cout << "当前生命值:" << prole_war->GetLife() << endl;

    // 使用补血丹
    ItemStrategy* strategy2 = new ItemStrategy_BXD();
    prole_war->SetItemStrategy(strategy2);
    prole_war->UseItem();
    cout << "当前生命值:" << prole_war->GetLife() << endl;

    // 使用守护丹
    ItemStrategy* strategy3 = new ItemStrategy_SHD();
    prole_war->SetItemStrategy(strategy3);
    prole_war->UseItem();
    cout << "当前生命值:" << prole_war->GetLife() << endl;

    // 释放资源
    delete strategy1;
    delete strategy2;
    delete strategy3;
    delete prole_war;
    
    F_Mage mage(800, 300, 100);
    cout << "法师初始生命值:" << mage.GetLife() << endl;

    // 可以为法师设置道具策略并使用
    // ...

    return 0;
}

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

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

相关文章

主成分分析SPSS步骤+Matlab程序

SPSS 导入数据 主成分分析 参数设置 选择要压缩的变量 输出结果 越陡说明信息差越大&#xff0c;反之信息差越小 导出数据 双击可以复制 粘贴到matlab 计算 Matlab clc,clear data readmatrix(例2.xlsx); %将数据保存在txt文件中 data zscore(data); %数据的标准化 …

使用java加载、调用onnx模型(二)

目录 1、摘要 2、实现过程 2.1、依赖 2.2、imread 2.3、contiguous函数 2.3.1、转化示例 2.3.3、核心代码 2.4、Flatten拉直 2.5、最终结果 3、完整代码 1、摘要 在上一篇文章中 使用java加载、调用onnx模型_onnx java-CSDN博客 发现使用Java加载调用模型的分类结…

计算组合数:从n个不同元素中,选k个元素的方式数量math.comb()

【小白从小学Python、C、Java】 【考研初试复试毕业设计】 【Python基础AI数据分析】 计算组合数&#xff1a; 从n个不同元素中&#xff0c; 选k个元素的方式数量 math.comb() 请问关于以下代码表述正确的选项是&#xff1f; import math print("【执行】math.comb(3, 2)…

线性代数证明:把行列式的某一行(列)的k倍加到另一行(列),行列式的值不变

线性代数证明 把行列式的某一行&#xff08;列&#xff09;的k倍加到另一行&#xff08;列&#xff09;&#xff0c;行列式的值不变&#xff1a; 注意五角星的位置要用到另一条性质&#xff1a;若行列式的某一行&#xff08;列&#xff09;的元素都是两数之和&#xff0c;则可以…

Ajax笔记总结(Xmind格式):第一天

Xmind鸟瞰图&#xff1a; 简单文字总结&#xff1a; ajax知识总结&#xff1a; 网络的参考模型&#xff1a; 1.物理层&#xff1a;源设备到目的设备 底层传输就是比特流 2.数据链路层 进行电信号的处理 进行数据的分组 3.网路层 进行数据包的传递 进行不同网络的…

菱形继承和虚继承

菱形继承&#xff08;Diamond Inheritance&#xff09;是指在多重继承的情况下&#xff0c;某个类继承自两个类&#xff0c;而这两个类又都继承自同一个基类的情况。 在这个结构中&#xff0c;D 直接从 A 继承了 A 的所有特性&#xff0c;但通过 B 和 C 继承&#xff0c;这会导…

Avue实现动态查询与数据展示(附Demo)

目录 前言1. 基本知识2. Demo 前言 此框架为Avue-crud&#xff0c;推荐阅读&#xff1a; 【vue】avue-crud表单属性配置&#xff08;表格以及列&#xff09;Avue实现批量删除等功能&#xff08;附Demo&#xff09;Avue实现选择下拉框的多种方式Avue框架实现图表的基本知识 | …

凌晨突发!核心系统瘫痪,通过Signleton单例模式轻松搞定,但还是被裁员了...

&#x1f345; 作者简介&#xff1a;哪吒&#xff0c;CSDN2021博客之星亚军&#x1f3c6;、新星计划导师✌、博客专家&#x1f4aa; &#x1f345; 哪吒多年工作总结&#xff1a;Java学习路线总结&#xff0c;搬砖工逆袭Java架构师 &#x1f345; 技术交流&#xff1a;定期更新…

selenium底层原理详解

目录 1、selenium版本的演变 1.1、Selenium 1.x&#xff08;Selenium RC时代&#xff09; 1.2、Selenium 2.x&#xff08;WebDriver整合时代&#xff09; 1.3、Selenium 3.x 2、selenium原理说明 3、源码说明 3.1、启动webdriver服务建立连接 3.2、发送操作 1、seleni…

flink车联网项目:维表离线同步(第69天)

系列文章目录 3.3 维表离线同步 3.3.1 思路 3.3.2 示例 3.3.3 其他表开发 3.3.4 部署 3.3.1.1 将表提交到生成环境 3.3.1.2 添加虚拟节点 3.3.1.3 配置计算节点 3.3.1.4 添加虚拟结束节点 3.3.1.5 提交到生产环境 3.3.1.6 发布 3.3.1.7 运维中心 3.3.1.8 补数据 3.3.1.9 补数据…

c++进阶------多态

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

机器学习/数据分析--通俗语言带你入门线性回归(结合案例)

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 前言 机器学习是深度学习和数据分析的基础&#xff0c;接下来将更新常见的机器学习算法注意&#xff1a;在打数学建模比赛中&#xff0c;机器学习用的也很多&a…

探索GitLab:从搭建到高效使用的实用指南

企业里为什么喜欢使用GitLab 一、GitLab简介二、搭建GitLab三、GitLab的权限管理3.1、用户注册3.2、创建用户组3.3、为用户组添加用户3.4、为工程添加访问权限 四、GitLab的code review五、团队知识管理六、总结 一、GitLab简介 GitLab是利用 Ruby on Rails 一个开源的版本管理…

Adobe Media Encoder ME 2023-23.6.6.2 解锁版下载安装教程 (专业的视频和音频编码渲染工具)

前言 Adobe Media Encoder&#xff08;简称Me&#xff09;是一款专业的音视频格式转码软件&#xff0c;文件格式转换软件。主要用来对音频和视频文件进行编码转换&#xff0c;支持格式非常多&#xff0c;使用系统预设设置&#xff0c;能更好的导出与相关设备兼容的文件。 一、…

网站怎么做敏感词过滤,敏感词过滤的思路和实践

敏感词过滤是一种在网站、应用程序或平台中实现内容审查的技术&#xff0c;用于阻止用户发布包含不适当、非法或不符合政策的内容。我们在实际的网站运营过程中&#xff0c;往往需要担心某些用户发布的内容中包含敏感词汇&#xff0c;这些词汇往往会导致我们的网站被用户举报&a…

JVM的组成

JVM 运行在操作系统之上 java二进制字节码文件的运行环境 JVM的组成部分 java代码在编写完成后编译成字节码文件通过类加载器 来到运行数据区,主要作用是加载字节码到内存 包含 方法区/元空间 堆 程序计数器,虚拟机栈,本地方法栈等等 随后来到执行引擎,主要作用是翻译字…

系统工程与信息系统(上)

系统工程 概念 【系统工程】是一种组织管理技术。 【系统工程】是为了最好的实现系统的目的&#xff0c;对系统的组成要素、组织结构、信息流、控制机构进行分析研究的科学方法。 【系统工程】从整体出发、从系统观念出发&#xff0c;以求【整体最优】 【系统工程】利用计算机…

信息搜集--敏感文件Banner

免责声明:本文仅做分享参考... git安装: Windows10下安装Git_win10安装git好慢-CSDN博客 git目录结构: Git 仓库目录 .git 详解-CSDN博客 敏感目录泄露 1-git泄露 Git是一个开源的分布式版本控制系统,我们简单的理解为Git 是一个*内容寻址文件系统*&#xff0c;也就是说Gi…

二十四、解释器模式

文章目录 1 基本介绍2 案例2.1 Instruction 接口2.2 StartInstruction 类2.3 PrimitiveInstruction 类2.4 RepeatInstruction 类2.5 InstructionList 类2.6 Context 类2.7 Client 类2.8 Client 类的运行结果2.9 总结 3 各角色之间的关系3.1 角色3.1.1 AbstractExpression ( 抽象…

Nexpose漏扫

免责声明:本文仅做分享参考... nexpose官网: Nexpose On-Premise Vulnerability Scanner - Rapid7 Rapid7的Nexpose是一款非常专业的漏洞扫描软件。有community版本和enterprise版本。 其中community版是免费的&#xff0c;但是功能简单&#xff1b;enterprise版本功能强大.…