常用设计模式
一、什么是设计模式
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的代码设计经验总结,旨在解决面向对象设计中反复出现的问题,提升代码的可重用性、可理解性和可靠性。以下从多个维度详细讲解:
一、设计模式的核心价值
- 代码复用:避免重复造轮子,直接应用经过验证的解决方案。
- 易维护性:遵循设计原则,降低代码耦合,使系统更灵活,修改或扩展时影响范围小。
- 沟通便利:提供公共术语,便于开发者交流设计思路(如提到 “单例模式”,大家都知道是确保一个类仅有一个实例)。
二、起源与经典著作
1994 年,Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 合著《Design Patterns: Elements of Reusable Object-Oriented Software》,首次系统提出 23 种设计模式,四位作者被称为 “四人帮(GOF)”,该书成为设计模式领域的奠基之作。
三、设计模式的分类
根据目的和行为方式,分为三大类:
- 创建型模式
- 目标:灵活创建对象,隐藏创建逻辑。
- 包括:工厂模式、抽象工厂模式、单例模式(确保类只有一个实例)、建造者模式(复杂对象分步构建)、原型模式(通过拷贝原型创建新对象)。
- 结构型模式
- 目标:关注类与对象的组合,优化结构。
- 包括:适配器模式(转换接口使不兼容类协作)、代理模式(为对象提供代理控制访问)、桥接模式(分离抽象与实现)、装饰器模式(动态添加对象职责)等。
- 行为型模式
- 目标:处理对象间的交互与行为分配。
- 包括:观察者模式(对象状态变化时通知依赖者)、策略模式(封装算法并可互换)、命令模式(将请求封装为对象)、责任链模式(请求沿链传递直至处理)等。
二、设计原则
1.依赖倒置
自动驾驶系统公司是高层,汽车生产厂商为低层,它们不应该互 相依赖,一方变动另一方也会跟着变动;而应该抽象一个自动驾 驶行业标准,高层和低层都依赖它;这样以来就解耦了两方的变 动;自动驾驶系统、汽车生产厂商都是具体实现,它们应该都依赖自动驾驶行业标准(抽象);
2.开放封闭
一个类应该对扩展(组合和继承)开放,对修改关闭;
3.面向接口
4.封装变化点
5.单一职责
一个类应该仅有一个引起它变化的原因;
6.里氏替换
子类型必须能够替换掉它的父类型;主要出现在子类覆盖父类实现,原来使用父类型的程序可能出现错误;覆盖了父类方法却没有实现父类方法的职责;
7.接口隔离
8.组合优于继承
继承耦合度高,组合耦合度低。
三、常用设计模式
1.模版方法
模板模式是一种行为设计模式,它定义了一个算法的框架,将算法的具体步骤延迟到子类中实现。模板模式通过定义一个算法的骨架,将算法中不变的部分放在父类中实现,而将可变的部分延迟到子类中实现,从而实现代码复用和减少重复。
模板模式解决了以下问题:
1. **代码复用**:模板模式通过将算法的通用部分放在父类中实现,实现了代码的复用。子类只需要实现特定的步骤,从而避免了重复编写相似的代码。
2. **提高扩展性**:模板模式通过定义算法的框架,使得子类可以灵活地扩展或修改算法中的特定步骤,而不影响算法的整体结构。
3. **实现开闭原则**:模板模式符合开闭原则,通过在父类中定义算法的骨架,可以在不修改父类的情况下扩展或修改子类的行为。
4. **约束子类行为**:模板模式限制了子类可以实现的算法的范围,确保了算法的执行顺序和逻辑。
总的来说,模板模式适用于当算法中存在一组共同的步骤,但其中某些步骤的具体实现可能会有所不同的情况。通过模板模式,可以将通用的算法步骤放在父类中实现,而将可变的部分延迟到子类中实现,从而提高代码的复用性和灵活性。
#include <iostream>// AbstractClass 是抽象类,定义了模板方法和抽象方法
class AbstractClass {
public:// 模板方法,定义了算法的骨架void templateMethod() {step1();step2();step3();}// 抽象方法,需要子类实现virtual void step1() = 0;virtual void step2() = 0;virtual void step3() = 0;
};// ConcreteClass 是具体子类,实现了抽象方法
class ConcreteClass : public AbstractClass {
public:void step1() override {std::cout << "Step 1" << std::endl;}void step2() override {std::cout << "Step 2" << std::endl;}void step3() override {std::cout << "Step 3" << std::endl;}
};int main() {AbstractClass* myObject = new ConcreteClass();myObject->templateMethod();delete myObject;return 0;
}
2.观察者模式
定义对象间的一种一对多(变化)的依赖关系,以便当一个对象(Subject)的状态发生改变时,所有依赖于它的对象都得到通 知并自动更新。
观察者模式是一种行为设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听并收到目标对象状态的变化。在这种模式中,当目标对象的状态发生变化时,所有依赖它的观察者对象都会得到通知并自动更新。
观察者模式解决了以下问题:
1. **解耦观察者和目标**:观察者模式使得目标对象和观察者对象之间的耦合度降低,目标对象无需知道观察者的具体实现,只需要维护一个观察者列表进行状态通知即可。
2. **支持广播通知**:目标对象状态的变化可以同时通知多个观察者对象,使得系统中的不同部分能够及时响应变化。
3. **实现开闭原则**:通过观察者模式,可以在不修改目标对象和观察者对象的情况下,动态地添加新的观察者或删除现有的观察者,符合开闭原则。
4. **降低系统的复杂性**:观察者模式将目标对象和观察者对象分离,使得系统中各个部分之间的交互变得简单,提高了系统的可维护性和扩展性。
总的来说,观察者模式适用于当一个对象的状态发生变化需要通知其他对象,并且这些对象需要根据状态的变化做出相应的处理时。这种模式在实现事件处理、GUI开发、消息通知等场景中被广泛应用。
//是把客户端加入到对应的主题里,主题提供接口,当主题的状态变化,就通知所有加入的客户端#include <iostream>
#include <vector>// 观察者类 Observer
class Observer {
public:virtual void update() = 0;
};
/
// 主题类 Subject
class Subject {
public:// 添加观察者void attach(Observer* observer) {observers.push_back(observer);}// 移除观察者void detach(Observer* observer) {for (auto it = observers.begin(); it != observers.end(); ++it) {if (*it == observer) {observers.erase(it);break;}}}// 通知观察者void notify() {for (Observer* observer : observers) { //observers容器里的每一个元素赋值给observer,并调用update() observer->update();}}// 设置状态void setState(int state) {this->state = state;notify();}int getState() {return state;}
private:std::vector<Observer*> observers;int state;
};
///
// 具体观察者类 ConcreteObserver
class ConcreteObserver : public Observer {
public:ConcreteObserver(Subject* subject) : subject(subject) { //上面的成员初始化列表等价于://this->subject=subject;subject->attach(this); //这里的this是当前对象,observer1或者observer2,也就是说Subject类创建的subject对象中的observers容器尾插新元素}~ConcreteObserver() {subject->detach(this);}void update() override {//这块的代码逻辑是:当主函数调用setState函数的时候,将subject类中的state赋值,并通知观察者//被通知后,调用这个代码,打印state的值std::cout << "Observer received update. New state: " << subject->getState() << std::endl; }private:Subject* subject;
};int main() {Subject subject;ConcreteObserver observer1(&subject);ConcreteObserver observer2(&subject);subject.setState(1);return 0;
}
3.策略模式
定义一系列算法,把它们一个个封装起来,并且使它们可互相 替换。该模式使得算法可独立于使用它的客户程序而变化。
策略模式是一种行为设计模式,它定义了一系列算法,并将每种算法封装成单独的策略类,使得这些算法可以相互替换,而且客户端代码不受影响。在策略模式中,算法的选择可以在运行时动态改变,从而实现算法的灵活性和可扩展性。
策略模式的目的是将算法的定义、使用和管理分离,以便能够独立地变更和切换不同的算法策略,而不需要修改客户端代码。通过策略模式,我们可以将算法的实现细节封装在独立的策略类中,从而提高代码的可维护性、可读性和灵活性。
策略模式通常用于以下情况:
1. 当一个类的行为取决于多种算法中的一种,且这些算法可以独立于该类变化时。
2. 当需要在运行时动态地选择算法时,即使在相同接口下也可以选择不同的算法。
3. 当一个类有多个行为变体,可以使用策略模式将这些变体实现为不同的策略类。
总的来说,策略模式的目的是使算法的选择和使用与算法的实现相分离,从而提高代码的灵活性、可扩展性和可维护性。
//客户端使用统一的上下文接口,而不同的策略使用统一的接口供上下文类调用#include <iostream>// 策略接口
class TravelStrategy {
public:virtual void travel() = 0;virtual ~TravelStrategy() {}
};// 具体策略:公交出行策略
class BusTravelStrategy : public TravelStrategy {
public:void travel() override {std::cout << "乘坐公交出行" << std::endl;}
};// 具体策略:地铁出行策略
class SubwayTravelStrategy : public TravelStrategy {
public:void travel() override {std::cout << "乘坐地铁出行" << std::endl;}
};// 具体策略:打车出行策略
class TaxiTravelStrategy : public TravelStrategy {
public:void travel() override {std::cout << "打车出行" << std::endl;}
};// 上下文:出行规划器
class TravelPlanner {
private:TravelStrategy* strategy;
public:TravelPlanner(TravelStrategy* strategy) : strategy(strategy) {}void setStrategy(TravelStrategy* strategy) {delete this->strategy;this->strategy = strategy;}void planTravel() {strategy->travel();}~TravelPlanner() {delete strategy;}
};int main() {TravelPlanner planner(new BusTravelStrategy());planner.planTravel();planner.setStrategy(new SubwayTravelStrategy());planner.planTravel();planner.setStrategy(new TaxiTravelStrategy());planner.planTravel();return 0;
}
4、单例模式
保证一个类仅有一个实例,并提供一个该实例的全局访问点。
(1)版本一
//单例模式仅有一个实例,并提供一个全局访问点
#include<iostream>
using namespace std;class singleton{
public:static singleton *GetInstance(){ //首次创建实例,会一直持续到函数结束调用析构函数释放if(_instance==NULL){_instance=new singleton();}return _instance;}
private:singleton(){};~singleton(){};//单例模式,就是一个类中只允许初始化一个实例,那就要禁用其他singleton(const singleton&)=delete;singleton& operator=(const singleton&)=delete;singleton(singleton&&)=delete;singleton& operator=(singleton&&)=delete;static singleton *_instance; //静态全局变量,所有类的对象可以共享访问这个资源
};
singleton *singleton::_instance=NULL;#if 0
//如果不禁用:
Singleton* s1 = Singleton::getInstance();// 使用拷贝构造函数创建新实例Singleton* s2 = new Singleton(*s1);// 使用拷贝赋值运算符创建新实例Singleton* s3 = Singleton::getInstance();*s3 = *s1;// 使用移动构造函数创建新实例Singleton* s4 = new Singleton(std::move(*s1));// 使用移动赋值运算符创建新实例Singleton* s5 = Singleton::getInstance();*s5 = std::move(*s1);
#endifint main(){return 0;
}
(2)版本二
#include <memory>
#include <iostream>
using namespace std;class Singleton {
public:static shared_ptr<Singleton> GetInstance() {if (!_instance) {_instance = shared_ptr<Singleton>(new Singleton());}return _instance;}// 将析构函数改为公有~Singleton() {};private:Singleton() {};Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;Singleton(Singleton&&) = delete;Singleton& operator=(Singleton&&) = delete;// 声明 shared_ptr 为友元,这样智能指针就能够访问类中的私有成员friend shared_ptr<Singleton>;static shared_ptr<Singleton> _instance;
};shared_ptr<Singleton> Singleton::_instance = NULL;int main() {shared_ptr<Singleton> instance = Singleton::GetInstance();// 在程序结束时会自动调用析构函数释放资源return 0;
}//该智能指针的作用是确保singleton类的实例在不需要的时候能够正确释放内存,避免内存泄漏。
//同时,share_ptr智能指针还可以方便的管理类的生命周期,因为当所有指向该实例的智能指针都超出作用域的时候,实例会被自动销毁
//如果只有一个智能指针独享这个实例,那就用unique_ptr//代码中没有使用 `std::make_shared`,而是直接使用 `new` 关键字来创建 `Singleton` 对象并将其包装在 `std::shared_ptr` 中。虽然 `std::make_shared` 在很多情况下是推荐的方式,但直接使用 `new` 也是有效的。在某些情况下,直接使用 `new` 可能更适合,例如需要传递额外的参数给构造函数时。//在你的代码中,`Singleton::GetInstance()` 方法负责返回 `Singleton` 类的实例,并在需要时创建该实例。在 `main()` 函数中,你获取了 `Singleton` 的实例并存储在 `shared_ptr` 中,当程序结束时,`shared_ptr` 会调用析构函数来释放资源。
//总的来说,尽管没有使用 `std::make_shared`,你的代码仍然是有效的并且实现了单例模式。使用 `std::make_shared` 主要是为了避免直接使用 `new` 和 `delete`,从而提高代码的安全性和可读性。//如果不使用智能指针,见singleton3.cpp
(3)版本三
//那么对于之前的问题(析构函数不调用),我们就使用atexit()函数,在程序退出的时候调用传入的函数#include<iostream>
#include<cstdlib>
using namespace std;class singleton{
public:static singleton* GetInstance(){if(_instance==NULL){_instance=new singleton();atexit(Destructor);}return _instance;}private:static void Destructor(){if(_instance!=NULL){delete _instance;_instance=NULL;}}singleton(){};~singleton(){};//单例模式,就是一个类中只允许初始化一个实例,那就要禁用其他singleton(const singleton&)=delete;singleton& operator=(const singleton&)=delete;singleton(singleton&&)=delete;singleton& operator=(singleton&&)=delete;static singleton *_instance;
};singleton *singleton::_instance=NULL;int main(){return 0;
}//但是这个代码又面临一个问题,就是单线程
(4)版本四
#include<iostream>
#include<mutex>
using namespace std;class singleton{
public:static singleton* GetInstance(){if(_instance==NULL){ //RAII 类的声明周期进行资料管理 双重检测lock_guard<std::mutex> lock(_mutex); //当创建的时候,多线程可能会同时访问资源,这个时候就需要加锁if(_instance==NULL){_instance=new singleton();atexit(Destructor);}}return _instance;}private:static void Destructor(){if(_instance!=NULL){delete _instance;_instance=NULL;}} singleton(){};~singleton(){};//单例模式,就是一个类中只允许初始化一个实例,那就要禁用其他singleton(const singleton&)=delete;singleton& operator=(const singleton&)=delete;singleton(singleton&&)=delete;singleton& operator=(singleton&&)=delete;static singleton *_instance;static mutex _mutex;
};int main(){return 0;
}//在c++98版本,代码的语义是基于单线程的,而现在出现了多核cpu
//而在c++11会对代码进行优化,例如编译器重排,cpu重排,可能会违反顺序一致性(前面的代码会在后面的代码之前执行),目的是让代码执行的速度更快
//因此会出现几种问题,比如可见性问题执行序问题。而在c++11中提供了解决办法,同步原语(原子变量和内存栅栏)
//见singleton.cpp5
(5)版本五
#include<iostream>
#include<mutex>
#include<atomic>
using namespace std;//load可以看见其他线程最新操作的数据,store修改数据让其他线程可见
//memory_order_acquire和memory_order_release之间所夹的代码既不会被优化到上面,也不会被优化到下面
//
class singleton{
public:static singleton* GetInstance(){singleton *tmp=_instance.load(memory_order_relaxed); //这个_instance定义是原子变量,就代表这条语句不可拆分,load是可见,获取最新数据,而relaxed指松散型可以随便优化atomic_thread_fence(memory_order_acquire); //内存栅栏解决了可见性问题和执行序问题,在这里使用,如果在构造函数里还使用了原子变量,就可以使用relaxed,因为这个是最快的//内存栅栏防止后面的代码编译到前面的代码的前面,保证了之前的读操作对后面的读操作可见if(tmp==NULL){lock_guard<mutex> lock(_mutex); //即使是原子操作,也无法完全替代互斥锁的作用。原子操作可以确保数据的一致性和可见性,但并不能解决并发访问带来的竞态条件。tmp=_instance.load(memory_order_relaxed);if(tmp==NULL){tmp=new singleton;}atomic_thread_fence(memory_order_release);_instance.store(tmp,memory_order_relaxed);atexit(Destructor);}return tmp;}
private:static void Destructor(){singleton* tmp=_instance.load(memory_order_relaxed);if(tmp!=NULL){delete tmp; }}singleton(){};~singleton(){};//单例模式,就是一个类中只允许初始化一个实例,那就要禁用其他singleton(const singleton&)=delete;singleton& operator=(const singleton&)=delete;singleton(singleton&&)=delete;singleton& operator=(singleton&&)=delete;static atomic<singleton*> _instance;static mutex _mutex;
};atomic<singleton*> singleton::_instance;
mutex singleton::_mutex;int main(){return 0;
}//这个代码虽然霸气,但是有些复杂
(6)版本六
//上一个版本的代码太长了,接下来的代码直接使用静态全局变量
//这个代码主要利用了c++11的magic static特性:如果当变量在初始化的时候,并发同时进入声明语句,并发线程将会阻塞等待初始化结束
//C++11引入了Magic Statics特性,它允许在函数内部的静态变量第一次被使用时进行初始化,而不是在程序启动时。这种特性的好处是可以延迟静态变量的初始化,直到它们被需要为止,从而提高程序的性能和效率。
#include<iostream>
using namespace std;class singleton{
public:static singleton& GetInstance(){static singleton instance;return instance;}
private:singleton(){};~singleton(){};//单例模式,就是一个类中只允许初始化一个实例,那就要禁用其他singleton(const singleton&)=delete;singleton& operator=(const singleton&)=delete;singleton(singleton&&)=delete;singleton& operator=(singleton&&)=delete;
};int main(){return 0;
}//下一个版本使用类模板,提高代码的复用性
(7)版本七
#include<iostream>
using namespace std;template<typename T>
class singleton{
public:static T& GetInstance(){static T instance;return instance;}
protected: //为了让子类能够调用得到父类中的构造和析构函数,必须放在protected里面virtual ~singleton(){}singleton(){}
private:singleton(const singleton&)=delete;singleton& operator=(const singleton&)=delete;singleton(singleton&&)=delete;singleton& operator=(singleton&&)=delete;
};class DesignPattern:public singleton<DesignPattern>{friend class singleton<DesignPattern>; //由于子类继承的父类中使用模板构建子类实例,所以必须让父类可以使用子类的构造函数,所以这里必须声明友元
private:DesignPattern(){}~DesignPattern(){}
};int main(){DesignPattern& instance = DesignPattern::GetInstance();return 0;
}
5.工厂方法
定义一个用于创建对象的接口,让子类决定实例化哪一个类。 Factory Method使得一个类的实例化延迟到子类。
工厂模式是一种创建型设计模式,它提供了一种创建对象的接口,但允许子类决定实例化哪个类。工厂模式通过定义一个创建对象的接口,将对象的实例化过程封装在工厂类中,从而实现对象的创建和使用代码的分离。
工厂模式解决了以下问题:
1. **封装对象的创建过程**:工厂模式将对象的实例化过程封装在工厂类中,客户端代码只需要关注如何使用对象,而无需关心对象的创建过程。
2. **解耦对象的使用和创建**:工厂模式将对象的创建和使用解耦,客户端代码不需要直接依赖具体的对象类,只需要通过工厂类来获取所需的对象实例。
3. **支持代码扩展**:工厂模式通过定义一个创建对象的接口,可以轻松地扩展和添加新的对象类型,而不需要修改现有的客户端代码。
4. **隐藏对象的具体实现**:工厂模式可以隐藏对象的具体实现细节,只暴露给客户端一个抽象的接口,从而提高代码的安全性和可维护性。
总的来说,工厂模式适用于当客户端需要根据不同的条件或参数来创建不同类型的对象时,或者当对象的创建过程比较复杂时。通过工厂模式,可以实现对象的动态创建、对象的实例化过程的封装,以及客户端与具体对象类的解耦,从而提高代码的灵活性和可维护性。
//把工厂类当做客户端暴漏给用户,创建新产品类的代码封装在工厂类中,避免在使用对象的地方出现大量的创建逻辑代码#include <iostream>
#include <memory>// 产品类
class Product {
public:virtual void operation() = 0;
};// 具体产品类 A
class ConcreteProductA : public Product {
public:void operation() override {std::cout << "ConcreteProductA operation" << std::endl;}
};// 具体产品类 B
class ConcreteProductB : public Product {
public:void operation() override {std::cout << "ConcreteProductB operation" << std::endl;}
};// 工厂类
class Factory {
public:std::shared_ptr<Product> createProduct(int type) {switch (type) {case 1:return std::make_shared<ConcreteProductA>();case 2:return std::make_shared<ConcreteProductB>();default:return nullptr;}}
};int main() {Factory factory;// 创建产品 Astd::shared_ptr<Product> productA = factory.createProduct(1);productA->operation();// 创建产品 Bstd::shared_ptr<Product> productB = factory.createProduct(2);productB->operation();return 0;
}
6.抽象工厂
提供一个接口,让该接口负责创建一系列“相关或者相互依赖的 对象”,无需指定它们具体的类。
抽象工厂模式是一种创建型设计模式,它提供了一种创建一系列相关或依赖对象的接口,而无需指定具体类。在抽象工厂模式中,客户端通过调用工厂接口来创建一组相关的产品对象,而不需要关心具体的产品类。这种方式可以使系统更具灵活性,能够轻松切换整个产品家族而不影响客户端代码。
抽象工厂模式通常涉及以下角色:
1. **抽象工厂(Abstract Factory):** 定义了创建产品对象的接口,包括一组创建不同类型产品的方法。
2. **具体工厂(Concrete Factory):** 实现了抽象工厂接口,在具体工厂中实现了创建一组相关产品的方法。
3. **抽象产品(Abstract Product):** 定义了产品对象的接口,具体产品类将实现这些接口。
4. **具体产品(Concrete Product):** 实现了抽象产品接口的具体产品类。
抽象工厂模式主要解决以下问题:
1. **封装变化:** 抽象工厂模式将对象的创建过程封装在工厂接口中,客户端不需要了解具体的产品类,从而使客户端代码与具体产品类解耦,可以灵活地替换整个产品家族。
2. **产品族的一致性:** 抽象工厂模式确保创建的产品是属于同一产品族的,保持产品之间的一致性,例如在创建界面元素时,保持按钮、文本框等元素的风格统一。
3. **易于扩展:** 当需要添加新的产品族时,只需要创建新的具体工厂类和具体产品类,而不需要修改现有代码,符合开闭原则。
总的来说,抽象工厂模式提供了一种创建一组相关对象的接口,帮助我们在需要创建多个相关对象时保持代码的灵活性和可扩展性。
#include <iostream>
/
// 抽象形状类
class Shape {
public:virtual void draw() = 0;
};// 具体形状类:矩形
class Rectangle : public Shape {
public:void draw() override {std::cout << "Inside Rectangle: draw() method" << std::endl;}
};// 具体形状类:圆形
class Circle : public Shape {
public:void draw() override {std::cout << "Inside Circle: draw() method" << std::endl;}
};
/
// 抽象颜色类
class Color {
public:virtual void fill() = 0;
};// 具体颜色类:红色
class Red : public Color {
public:void fill() override {std::cout << "Inside Red: fill() method" << std::endl;}
};// 具体颜色类:蓝色
class Blue : public Color {
public:void fill() override {std::cout << "Inside Blue: fill() method" << std::endl;}
};
/
// 抽象工厂类
class AbstractFactory {
public:virtual Shape* create_shape() = 0;virtual Color* create_color() = 0;
};// 具体形状工厂类
class ShapeFactory : public AbstractFactory {
public:Shape* create_shape() override {return new Rectangle();}Color* create_color() override {return nullptr; // 不负责创建颜色对象}
};// 具体颜色工厂类
class ColorFactory : public AbstractFactory {
public:Shape* create_shape() override {return nullptr; // 不负责创建形状对象}Color* create_color() override {return new Blue();}
};int main() {AbstractFactory* shape_factory = new ShapeFactory();Shape* rectangle = shape_factory->create_shape();rectangle->draw();AbstractFactory* color_factory = new ColorFactory();Color* blue = color_factory->create_color();blue->fill();delete rectangle;delete blue;delete shape_factory;delete color_factory;return 0;
}
7.责任链
使多个对象都有机会处理请求,从而避免请求的发送者和接收 者之间的耦合关系。将这些对象连成一条链,并沿着这条链传 递请求,直到有一个对象处理它为止。
责任链模式(Chain of Responsibility Pattern)是一种行为设计模式,它允许多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的直接耦合关系。在责任链模式中,将请求沿着一个处理链传递,直到有一个对象处理该请求为止。每个处理者对象都包含对下一个处理者对象的引用,请求在处理链上传递,直到有处理者能够处理请求或者请求达到链的末端。
责任链模式通常涉及以下角色:
1. **抽象处理者(Handler):** 定义了处理请求的接口,包含一个指向下一个处理者的引用.
2. **具体处理者(Concrete Handler):** 实现了抽象处理者接口,负责处理请求,如果自己无法处理,则将请求传递给下一个处理者。
责任链模式主要解决以下问题:
1. **解耦请求发送者和接收者:** 责任链模式通过将请求发送者和接收者解耦,使得发送者无需知道具体的处理者,只需将请求发送给第一个处理者,由处理者链自行决定谁来处理请求。
2. **动态指定处理者:** 可以动态改变处理链,新增或移除处理者,灵活调整处理流程,而不需要修改客户端代码。
3. **处理者无需知道整个处理流程:** 每个处理者只需关注自己能否处理请求,不需要了解整个处理流程,增强了模块化和可维护性。
总的来说,责任链模式提供了一种将请求沿着处理链传递的机制,使得多个对象都有机会处理请求,从而实现请求的发送者和接收者之间的解耦,提高系统的灵活性和可扩展性。
#include <iostream>
#include <string>
using namespace std;// 抽象处理者
class Handler {
public:virtual void handleRequest(const std::string request) = 0;virtual void setNextHandler(Handler* next) = 0;
};// 具体处理者 A
class ConcreteHandlerA : public Handler {
private:Handler* nextHandler;public:ConcreteHandlerA() : nextHandler(nullptr) {} //初始值必须置空void handleRequest(const std::string request) override {if (request == "A") {std::cout << "ConcreteHandlerA handles the request." << std::endl;} else if (nextHandler != NULL) {nextHandler->handleRequest(request);} else {std::cout << "No handler available for the request." << std::endl;}}void setNextHandler(Handler* next) override {nextHandler = next;}
};// 具体处理者 B
class ConcreteHandlerB : public Handler {
private:Handler* nextHandler;public:ConcreteHandlerB() : nextHandler(nullptr) {} void handleRequest(const std::string request) override {if (request == "B") {std::cout << "ConcreteHandlerB handles the request." << std::endl;} else if (nextHandler != NULL) {nextHandler->handleRequest(request);} else {std::cout << "No handler available for the request." << std::endl;}}void setNextHandler(Handler* next) override {nextHandler = next;}
};int main() {ConcreteHandlerA handlerA;ConcreteHandlerB handlerB;handlerA.setNextHandler(&handlerB);handlerA.handleRequest("A");handlerA.handleRequest("B");handlerA.handleRequest("C");return 0;
}
8.装饰器
动态地给一个对象增加一些额外的职责。就增加功能而言,装 饰器模式比生产子类更为灵活。
装饰模式(Decorator Pattern)是一种结构型设计模式,它允许在不改变现有对象结构的情况下,动态地将新行为附加到对象上。该模式通过创建一个包装类(装饰器),来包裹原始的类,然后在不影响原始类结构的情况下,增加新的功能或责任。
装饰模式的主要目的是为了:
1. **动态地扩展对象的功能**:通过使用装饰器,可以在运行时动态地为对象添加新的行为,而无需修改现有代码。
2. **避免类爆炸**:装饰模式避免了创建大量子类来处理各种组合情况,从而避免类的数量急剧增加。
装饰模式通常用于以下情况:
- 当需要在不修改现有代码的情况下,动态地添加新功能或责任时。
- 当需要对对象的行为进行多次扩展,而子类的组合会导致类爆炸时。
- 当类的功能需要被分离成不同的层次时,可以使用装饰模式来实现这种分层。
总的来说,装饰模式提供了一种灵活的方式来动态地添加功能,同时避免了代码修改和维护的复杂性。它使得代码更具扩展性和可维护性。
#include <iostream>// Component
class Coffee {
public:virtual void brew() = 0;
};// ConcreteComponent
class SimpleCoffee : public Coffee {
public:void brew() override {std::cout << "Brewing simple coffee" << std::endl;}
};// Decorator
class CoffeeDecorator : public Coffee {
protected:Coffee* decoratedCoffee;public:CoffeeDecorator(Coffee* coffee) : decoratedCoffee(coffee) {}void brew() override {decoratedCoffee->brew();}
};// ConcreteDecorator
class MilkDecorator : public CoffeeDecorator {
public:MilkDecorator(Coffee* coffee) : CoffeeDecorator(coffee) {}void brew() override {CoffeeDecorator::brew();addMilk();}void addMilk() {std::cout << "Adding milk" << std::endl;}
};#if 0
在我之前提供的示例代码中,装饰模式的具体实现如下:- `Coffee` 是组件接口,定义了咖啡的基本行为。- `SimpleCoffee` 是具体组件,实现了 `Coffee` 接口,表示简单的咖啡。- `CoffeeDecorator` 是装饰器类,实现了 `Coffee` 接口,并持有一个 `Coffee` 对象的引用,在 `brew()` 方法中调用被装饰对象的 `brew()` 方法。- `MilkDecorator` 是具体装饰器类,继承了 `CoffeeDecorator`,在 `brew()` 方法中扩展了添加牛奶的功能。在这个示例中,`MilkDecorator` 类包裹了 `SimpleCoffee` 类。通过将 `SimpleCoffee` 对象传递给 `MilkDecorator` 的构造函数,我们可以在不改变 `SimpleCoffee` 类的情况下,动态地为其添加额外的功能(添加牛奶)。
#endifint main() {Coffee* simpleCoffee = new SimpleCoffee();Coffee* coffeeWithMilk = new MilkDecorator(simpleCoffee);coffeeWithMilk->brew();delete simpleCoffee;delete coffeeWithMilk;return 0;
}
9.组合模式
将对象组合成树型结构以表示“部分-整体”的层次结构。组合模 式使得用户对单个对象和组合对象的使用具有一致性。
组合模式(Composite Pattern)是一种结构型设计模式,它允许客户端统一处理单个对象和对象组合(包含子对象)的情况,使得客户端在处理单个对象和组合对象时具有一致性。组合模式将对象组织成树形结构,其中叶节点表示单个对象,而分支节点表示对象组合。
组合模式的主要目的是:
1. **统一处理对象和对象组合**:组合模式允许客户端以统一的方式处理单个对象和对象组合,无需区分它们。
2. **简化客户端代码**:客户端代码不需要关心对象是单个对象还是组合对象,从而简化了客户端代码的复杂性。
3. **递归组合结构**:通过组合模式,可以递归地组合对象,形成树形结构,使得处理复杂的嵌套结构变得简单。
组合模式通常用于以下情况:
- 当对象具有树形结构,并且需要以统一的方式处理单个对象和对象组合时。
- 当希望客户端代码能够对单个对象和组合对象进行一致的操作时。
- 当需要构建包含对象组合的层次结构,并希望以递归方式处理这种结构时。
总的来说,组合模式通过将对象组织成树形结构,使得客户端可以一致地处理单个对象和对象组合,从而简化了代码的复杂性并提高了代码的可维护性。
#include <iostream>
#include <vector>class Component {
public:virtual void operation() = 0;virtual void add(Component* component) {}virtual void remove(Component* component) {}virtual Component* getChild(int index) { return nullptr; }
};class Leaf : public Component {
public:void operation() override {std::cout << "Leaf operation\n";}
};class Composite : public Component {
private:std::vector<Component*> children;public:void operation() override {std::cout << "Composite operation\n";for (Component* child : children) {child->operation();}}void add(Component* component) override {children.push_back(component);}void remove(Component* component) override {// Remove the component from children}Component* getChild(int index) override {if (index < children.size()) {return children[index];}return nullptr;}
};int main() {Composite* composite = new Composite();composite->add(new Leaf());Composite* subComposite = new Composite();subComposite->add(new Leaf());subComposite->add(new Leaf());composite->add(subComposite);composite->operation();delete composite;return 0;
}
0voice · GitHub
相关文章:
常用设计模式
一、什么是设计模式 设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的代码设计经验总结,旨在解决面向对象设计中反复出现的问题,提升代码的可重用性、可理解性和可靠性。以下从多个维度详细讲解ÿ…...
20242817-李臻-课下作业:Qt和Sqlite
实验内容 阅读附件内容,编译运行附件中第一章,第三章的例子。 实验过程 第一章 t1实践 #include <QApplication> #include <QWidget> #include <QPushButton> #include <QVBoxLayout>int main(int argc, char *argv[]) {QA…...
嵌入式机器学习平台Edge Impulse图像分类 – 快速入门
陈拓 2025/05/08-2025/05/11 1. 简介 官方网址 https://edgeimpulse.com/ 适用于任何边缘设备的人工智能: Gateways - 网关 Sensors & Cameras - 传感器和摄像头 Docker Containers - Docker容器 MCUs, NPUs, CPUs, GPUs 构建数据集、训练模型并优化库以…...
JavaWeb, Spring, Spring Boot
出现时间 JavaWeb - Spring - Spring Boot 一、JavaWeb 的发展历程 Servlet 和 JSP: Servlet:1997 年首次发布,用于处理 HTTP 请求和响应。 JSP:1999 年首次发布,用于动态生成 HTML 页面。 特点:提供了基…...
upload-labs靶场通关详解:第五关
一、分析源代码 $is_upload false; $msg null; if (isset($_POST[submit])) {if (file_exists(UPLOAD_PATH)) {$deny_ext array(".php",".php5",".php4",".php3",".php2",".html",".htm",".ph…...
【问题】Watt加速github访问速度:好用[特殊字符]
前言 GitHub 是全球知名的代码托管平台,主要用于软件开发,提供 Git 仓库托管、协作工具等功能,经常要用到,但是国内用户常因网络问题难以稳定访问 。 Watt Toolkit(原名 Steam)是由江苏蒸汽凡星科技有限公…...
GitHub打开缓慢甚至失败的解决办法
在C:\Windows\System32\drivers\etc的hosts中增加如下内容: 20.205.243.166 github.com 199.59.149.236 github.global.ssl.fastly.net185.199.109.153 http://assets-cdn.github.com 185.199.108.153 http://assets-cdn.github.com 185.199.110.153 http://asset…...
【25软考网工】第六章(3)数字签名和数字证书
博客主页:christine-rr-CSDN博客 专栏主页:软考中级网络工程师笔记 大家好,我是christine-rr !目前《软考中级网络工程师》专栏已经更新二十多篇文章了,每篇笔记都包含详细的知识点,希望能帮助到你!…...
Android Native 函数 Hook 技术介绍
版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/ 前言 Android Native 函数 Hook 技术是一种在应用运行时拦截或替换系统或自身函数行为的手段,常见实现包括 PLT Hook、Inline Hook。 PLT Hook 和…...
代码随想录算法训练营第60期第三十二天打卡
大家好,今天是我们贪心算法章节的第三阶段,前面我们讲过的几道题不知道大家理解的情况如何,还是那句话,贪心算法没有固定的套路与模板,一道题一个思路,我们要多思考这样慢慢地我就就可以水到渠成。今天我们…...
Problem C: 异常1
1.题目描述 检测年龄,其中若为负数或大于等于200岁皆为异常,请将下列代码补充完整。 // 你的代码将被嵌入这里 class Main{ public static void main(String[] args){ Person p1new Person("John",80); Person p2new Pers…...
Ollama部署使用以及模型微调和本地部署
ollama是一款开源的本地大语言模型管理工具,专注于简化大语言模型(LLM)的本地部署和使用。以下是关于 Ollama 应用的详细介绍: Ollama 的主要功能 本地化部署: Ollama 支持在本地运行模型,无需依赖外部云…...
汇编学习——iOS开发对arm64汇编的初步了解
汇编学习——iOS开发对arm64汇编的初步了解 文章目录 汇编学习——iOS开发对arm64汇编的初步了解前言栈 指令 寄存器寄存器指令运算指令寻址指令前变基 与 后变基 栈堆(Heap)内存机制三、栈(Stack)内存机制 3. 多级调用示例 例子A…...
前端代理问题
在前后端联调的时候,有一次因为前端项目代理配置有问题,导致请求接口对不上, transpileDependencies: true,devServer: {hot: true,port: 8081,proxy: {/api: {target: http://localhost:8080,changeOrigin: true,ws: true,pathRewrite: {^/a…...
E+H流量计通过Profibus DP主站转Modbus TCP网关与上位机轻松通讯
EH流量计通过Profibus DP主站转Modbus TCP网关与上位机轻松通讯 在现代工业自动化的广阔舞台上,Profibus DP与Modbus TCP这两种通信协议各领风骚,它们在不同的应用场景中发挥着举足轻重的作用。但工业生产的复杂性往往要求不同设备、系统之间能够顺畅沟…...
TCP/IP 模型每层的封装格式
TCP/IP 模型是一个四层网络架构,每一层在数据传输时都会对数据进行封装,添加相应的头部(和尾部)信息。以下是各层的封装格式及关键字段说明: 1. 应用层(Application Layer) 封装格式:…...
openjdk底层汇编指令调用(一)——汇编指令及指令编码基础
汇编指令 计算机在执行过程时只识别代表0或者1的电信号。因此为了让计算机能够执行则须向计算机输入一系列01构成的指令。 例如在x64平台下,0x53,二进制为01010011,表示将rbx寄存器中的值压栈。 但是,对于程序员而言,…...
5G-A来了!5G信号多个A带来哪些改变?
5G-A来了!5G信号多个A带来哪些改变? 随着科技不断进步,通信网络的迭代升级也在加速。自4G、5G的推出以来,我们见证了通信技术的飞跃式发展。最近,越来越多的用户发现自己手机屏幕右上角的5G标识已经变成了“5G-A”。那…...
探索虚拟化:云计算时代的资源优化之道
前言 如果您想知道云提供商如何在全球范围内运行无数应用程序,而每个应用程序都没有机架服务器,那么答案就在于虚拟化。 它是为云提供支持的核心技术之一,在幕后悄悄工作,使现代计算高效、可扩展且具有成本效益。 在本文中&#x…...
用户登录构件示例
目录 一、登录构件概述 二、构件内部结构 1. 构件组成元素(表格形式) 2. 组件连接件设计...
【软件测试】基于项目驱动的功能测试报告
目录 一、项目的介绍 1.1 项目背景 二、测试目标 2.1 用户服务模块 2.1.1 用户注册模块 2.1.1.1 测试点 2.1.1.2 边界值分析法(等价类+边界值) 2.1.1.2.1 有效等价类 2.1.1.2.2 无效等价类 2.1.1.2.3 边界值 2.1.1.2.4 测试用例设计 2.2 文章标签模块 2.3 文章模…...
【QT】UDP通讯本地调试
qt已经写好了udp通讯代码,现在要进行测试。 1、终端输入ipconfig查看本机网卡的ipv4地址 2、 用udpBind函数,绑定到此ip和自定义的端口号。 3、 打开网络调试助手,自动检测到本机的ip地址,输入任意一个和程序里不一样的端口号。 …...
web animation API 锋利的css动画控制器 (更新中)
什么是web animation api 以及为什么要使用web animation api? web animation API 是web页面中控制DOM元素动画效果的javascript原生API。 它能够逐个关键帧控制动画效果,具有Timeline 机制能通过javascript来实现动画的暂停,播放&#x…...
Nginx的增强与可视化!OpenResty Manager - 现代化UI+高性能反向代理+安全防护
以下是对OpenResty Manager的简要介绍: OpenResty Manager (Nginx 增强版),是一款容易使用、功能强大且美观的反向代理工具 ,可以作为OpenResty Edge 的开源替代品基于 OpenResty 开发,支持并继承 OpenRes…...
Spring Boot 中的重试机制
Retryable 注解简介 Retryable 注解是 Spring Retry 模块提供的,用于自动重试可能会失败的方法。在微服务架构和分布式系统中,服务之间的调用可能会因为网络问题、服务繁忙等原因失败。使用 Retryable 可以提高应用的稳定性和容错能力 1。 使用步骤 &…...
[Java实战]Spring Boot 整合 Freemarker (十一)
[Java实战]Spring Boot 整合 Freemarker (十一) 引言 Apache FreeMarker 作为一款高性能的模板引擎,凭借其简洁语法、卓越性能和灵活扩展性,在 Java Web 开发中占据重要地位。结合 Spring Boot 的自动化配置能力,开发者能快速构建动态页面、…...
现有预测式外呼系统如何接入AI系统,使用AI辅助,判断出意向客户再转人工
很多用户还在使用老旧的预测式外呼系统,只能外呼接通后播放一个提示音,播放完提示音后在转给人工坐席, 如果重新部署一套AI外呼系统,涉及到业务系统的迁移,非常不方便。 现在我就做一个如何让现有外呼系统,…...
实战项目3(04)
目录 任务场景一 【r1配置】 【sw1配置】 任务场景二 【r1配置】 【sw1配置】 【sw2配置】 任务场景一 某公司网络为了减少广播包对网络的影响,网络管理员对网络进行了VLAN划分,完成VLAN划分后,为了不影响VL…...
[Java实战]Spring Boot 静态资源配置(十三)
[Java实战]Spring Boot 静态资源配置(十三) 引言 静态资源(如 HTML、CSS、JavaScript、图片等)是 Web 应用的基石。Spring Boot 通过自动化配置简化了静态资源管理,但面对复杂场景(如多模块项目、CDN 集成…...
ESP-ADF wifi_service子模块wifi_ssid_manager凭证管理函数详解
目录 ESP-ADF wifi_service子模块wifi_ssid_manager凭证管理函数详解WiFi凭证管理函数分析wifi_ssid_manager_savewifi_ssid_manager_erase_all 内部实现机制存储策略分析内部数据流向关键辅助函数分析重要的内部辅助函数详解get_stored_id_by_ssidnvs_get_write_idnvs_set_cou…...
数据分析预备篇---NumPy数组
NumPy是数据分析时常用的库,全称为Numerical Python,是很多数据或科学相关Python包的基础,包括pandas,scipy等等,常常被用于科学及工程领域。NumPy最核心的数据结构是ND array,意思是N维数组。 #以下是一个普通列表的操作示例:arr = [5,17,3,26,31]#打印第一个元素 prin…...
解决VirtualBox中虚拟机(ubuntu)与主机(windows)之间互相复制粘贴(文本)
一.开始的设置 1.在VirtualBox中打开设置,常规中修改主机与虚拟机交互设置 2.虚拟机关闭状态下,存储中选中控制器SATA,勾选‘使用主机输入输出’ 3.选中操作系统对应的虚拟文件,.vdi文件,勾选右边的固态驱动器。 4.启…...
单细胞RNA测序数据分析与可视化:从基础原理到高级应用
引言 单细胞RNA测序(scRNA-seq)技术的出现彻底改变了我们研究复杂生物系统的方式,使科学家能够在前所未有的精细水平上解析细胞异质性。传统的bulk RNA测序只能捕获细胞群体的平均表达特征,而单细胞转录组测序允许我们检测每个细…...
嵌入式硬件篇---UART
文章目录 前言1. UART协议基础1.1 物理层特性两根信号线无时钟信号电平标准TTL UARTRS-232 1.2 数据帧格式1.3 波特率计算波特率 2. STM32F103RCT6的UART配置2.1 硬件连接2.2 CubeMX配置启用USART1引脚分配中断启用(可选) 3. HAL库代码实现3.1 UART初始化…...
C# 通过ConfigurationManager读写配置文件App.Config
目录 简述代码描述一、构建App.config二、调用代码1、代码步骤说明2、输出结果说明 简述 App.config 是 C#中最常用的配置文件类型。 通常位于项目的根目录中,以 XML 格式存储配置信息。App.config 文件可以包含多个配置节,如 appSettings、connectionS…...
Python3安装HTMLTestRunner
1.下载HTMLTestRunner地址:http://tungwaiyip.info/software/HTMLTestRunner.html 2.下载的HTMLTestRunner.py是针对python2写的,所以需要改成python3适合的内容: 问题1:No module named StringIO, 原因:py…...
图形化编程革命:iVX携手AI 原生开发范式
一、技术核心:图形化编程的底层架构解析 1. 图形化开发的效率优势:代码量减少 72% 的秘密 传统文本编程存在显著的信息密度瓶颈。以 "按钮点击→条件判断→调用接口→弹窗反馈" 流程为例,Python 实现需定义函数、处理缩进并编写 …...
GC垃圾回收
Gc是语言提供的自动的内存管理机制,自动释放不需要的内存对象,让出存储器资源。 Go语言变革: V1.5的三色并发标记法 V1.5的三色并发标记为什么需要STW V1.5的三色标记为什么需要屏障机制(“强-弱”,三色不变式、插入屏障、删除屏障…...
“多端多接口多向传导”空战数据链体系——从异构融合架构到抗毁弹性网络的系统性设计
“多端多接口多向传导”空战数据链体系——从异构融合架构到抗毁弹性网络的系统性设计 文章目录 “多端多接口多向传导”空战数据链体系——从异构融合架构到抗毁弹性网络的系统性设计第一章 引言:空战数据链体系的范式革新1.1 空战数据链的演进逻辑1.2 新架构的核心理论价值1…...
Unity3D仿星露谷物语开发42之粒子系统
1、目标 使用例子系统,实现割草后草掉落的特效。 通过PoolManager获取特效预制体,通过VFXManager来触发特效。 2、配置例子特效 在Hierarchy -> PersistentScene下创建新物体命名为Reaping。 给该物体添加Particle System组件。 配置例子系统参数…...
python打卡训练营打卡记录day22
复习日 仔细回顾一下之前21天的内容,没跟上进度的同学补一下进度。 作业: 自行学习参考如何使用kaggle平台,写下使用注意点,并对下述比赛提交代码 kaggle泰坦尼克号人员生还预测 导入数据 # 导入所需库 import pandas as pd impor…...
网络编程(一)网络编程入门
本节课学习TCP客户端和服务器端编程架构,其分为分为C/S(客户端/服务器模式)和B/S(浏览器/服务器架构模式)两种模式。接下来我们分别了解这两种模式 C/S模式 C/S模式:服务器首先先启动,并根据客…...
华为IP(6)
VLAN聚合 VLAN聚合产生的技术背景 在一般是三层交换机中,通常采用一个VLAN接口的方式实现广播域之间的互通,这在某些情况下导致了IP地址的浪费 因为一个VLAN对应的子网中,子网号、子网广播地址、子网网关地址不能用作VLAN内的主机IP地址&a…...
初探机器学习与深度学习
本文以水果摊销量预测为例,揭示机器学习通过数据训练模型的核心逻辑,对比传统编程规则驱动模式。解析分类(疾病诊断)与回归(房价预测)两大任务的技术本质,类比前端开发中的类型定义与图表拟合。…...
3. 仓颉 CEF 库封装
文章目录 1. capi 使用说明2. Cangjie CEF2. 1实现目标 3. 实现示例 1. capi 使用说明 根据上一节 https://blog.csdn.net/qq_51355375/article/details/147880718?spm1011.2415.3001.5331 所述, cefcapi 是libcef 共享库导出一个 C API, 而以源代码形式分发的 li…...
Linux-TCP套接字编程简易实践:实现EchoServer与远程命令执行及自定义协议(反)序列化
一.TCP Socket常用API 1.1socket() NAMEsocket - create an endpoint for communicationSYNOPSIS#include <sys/types.h> /* See NOTES */#include <sys/socket.h>int socket(int domain, int type, int protocol); socket()打开一个网络通讯端口,如果…...
缓存(3):本地缓存作用 及 数据一致性 实现策略
概述 CAP 什么是CAP CAP理论,指的是在一个分布式系统中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可得兼。 三者关系如…...
【leetcode】《BFS扫荡术:如何用广度优搜索征服岛屿问题》
前言 🌟🌟本期讲解关于力扣的几篇题解的详细介绍~~~ 🌈感兴趣的小伙伴看一看小编主页:GGBondlctrl-CSDN博客 🔥 你的点赞就是小编不断更新的最大动力 🎆那么废话不…...
vue中理解MVVM
理解 在 Vue 中,MVVM(Model-View-ViewModel) 是其核心设计思想之一,它帮助实现了数据驱动的视图更新和良好的代码结构分离。我们来具体解析 Vue 是如何实现 MVVM 模式的。 🌐 MVVM 是什么? 角色含义Vue…...
[工具]B站缓存工具箱 (By 郭逍遥)
📌 项目简介 B站缓存工具箱是一个多功能的B站缓存工具,包含视频下载、缓存重载、文件合并及系统设置四大核心功能。基于yutto开发,采用图形化界面操作,极大简化B站资源获取与管理流程。 工具可以直接将原本缓存的视频读取&#…...