1. 概述在统一建模语言UML中依赖关系是一种重要的模型元素用来表示一个事物比如类、组件或包依赖于另一个事物的情况。依赖关系通常表示一个事物的定义或实现部分地或完全依赖于另一个事物。2. 特点方向性依赖关系是有方向的表示一个元素使用或依赖另一个元素。动态性依赖通常表示在运行时或在编译时的关系不像关联那样通常表示更长久的关系。弱关系相比于类的关联、聚合或组合依赖是一种相对较弱的关系通常用于表示非永久性的使用或交互。3.依赖关系的类型UML 依赖关系可以细分为以下几种类型3.1 使用依赖一个类客户端在其方法中使用另一个类供应商的实例。例如一个Car类使用Engine类来提供动力。#include iostream class Engine { public: void start() { std::cout Engine started std::endl; } void stop() { std::cout Engine stopped std::endl; } }; class Car { public: Car() { engine new Engine(); } void start() { engine-start(); } void stop() { engine-stop(); } private: Engine* engine; }; int main() { Car car; car.start(); car.stop(); return 0; }一个ReportGenerator类使用DataSource类来获取数据。#include iostream #include vector class DataSource { public: virtual ~DataSource() {} virtual std::vectorint getData() 0; }; class JdbcDataSource : public DataSource { public: std::vectorint getData() override { std::cout Getting data from JDBC data source std::endl; return {1, 2, 3}; } }; class CsvDataSource : public DataSource { public: std::vectorint getData() override { std::cout Getting data from CSV data source std::endl; return {4, 5, 6}; } }; class ReportGenerator { public: ReportGenerator(DataSource* dataSource) { this-dataSource dataSource; } void generateReport() { std::vectorint data dataSource-getData(); // ... 生成报表 ... } private: DataSource* dataSource; }; int main() { // 使用 JDBC 数据源 JdbcDataSource* jdbcDataSource new JdbcDataSource(); ReportGenerator reportGenerator1(jdbcDataSource); reportGenerator1.generateReport(); // 使用 CSV 数据源 CsvDataSource* csvDataSource new CsvDataSource(); ReportGenerator reportGenerator2(csvDataSource); reportGenerator2.generateReport(); return 0; }3.2 创建依赖一个类负责创建另一个类的实例。例如一个Factory类负责创建Product类的实例。#include iostream class Product { public: virtual ~Product() {} virtual void doSomething() 0; }; class ConcreteProductA : public Product { public: void doSomething() override { std::cout ConcreteProductA do something std::endl; } }; class ConcreteProductB : public Product { public: void doSomething() override { std::cout ConcreteProductB do something std::endl; } }; class Factory { public: virtual ~Factory() {} virtual Product* createProduct() 0; }; class ConcreteFactoryA : public Factory { public: Product* createProduct() override { return new ConcreteProductA(); } }; class ConcreteFactoryB : public Factory { public: Product* createProduct() override { return new ConcreteProductB(); } }; int main() { Factory* factory new ConcreteFactoryA(); Product* productA factory-createProduct(); productA-doSomething(); factory new ConcreteFactoryB(); Product* productB factory-createProduct(); productB-doSomething(); return 0; }一个Document类负责创建Paragraph类的实例。#include iostream #include vector class Section { public: Section(const std::string text) { this-text text; } void print() { std::cout text std::endl; } private: std::string text; }; class Document { public: void addSection(const std::string text) { sections.push_back(new Section(text)); } void print() { for (Section* section : sections) { section-print(); } } private: std::vectorSection* sections; }; int main() { Document document; document.addSection(This is the first section.); document.addSection(This is the second section.); document.print(); return 0; }3.3 参数依赖一个类的方法接受另一个类的实例作为参数。例如一个FileReader类的方法接受一个File类的实例作为参数。#include iostream #include fstream class File { public: File(const std::string path) { this-path path; } const std::string getPath() const { return path; } private: std::string path; }; class FileReader { public: void readFile(const File file) { std::ifstream ifs(file.getPath()); if (ifs.is_open()) { std::string line; while (getline(ifs, line)) { std::cout line std::endl; } ifs.close(); } else { std::cout Error opening file std::endl; } } }; int main() { File file(C:/test.txt); FileReader fileReader; fileReader.readFile(file); return 0; }一个List类的Add方法接受一个要添加的元素作为参数。#include iostream #include vector template typename T class List { public: void add(const T element) { elements.push_back(element); } void print() { for (const T element : elements) { std::cout element ; } std::cout std::endl; } private: std::vectorT elements; }; int main() { Listint list; list.add(1); list.add(2); list.add(3); list.print(); return 0; }3.4 返回值依赖一个类的方法返回另一个类的实例。例如一个Database类的方法返回一个Connection类的实例。#include iostream class Connection { public: Connection() { std::cout Creating connection std::endl; } ~Connection() { std::cout Closing connection std::endl; } }; class Database { public: Connection* getConnection() { return new Connection(); } }; int main() { Database database; Connection* connection database.getConnection(); // 使用连接... delete connection; return 0; }一个Parser类的方法返回一个Document类的实例。#include iostream #include fstream class Document { public: Document(const std::string text) { this-text text; } void print() { std::cout text std::endl; } private: std::string text; }; class Parser { public: Document* parse(const std::string filePath) { std::ifstream ifs(filePath); if (ifs.is_open()) { std::string text; while (getline(ifs, text)) { // ... 解析文本 ... } ifs.close(); return new Document(text); } else { std::cout Error opening file std::endl; return nullptr; } } }; int main() { Parser parser; Document* document parser.parse(C:/test.txt); if (document ! nullptr) { document-print(); delete document; } return 0; }3.6 绑定依赖指模板实例化时将模板类绑定到具体的参数上。类模板是指可以生成不同类型对象的类。类模板的实例化需要指定具体的类型参数。如果一个类的模板实例化依赖于另一个类那么就表示该类模板依赖于另一个类。例如一个List类模板可以生成不同类型元素的列表。#include iostream #include vector template typename T class List { public: void add(const T element) { elements.push_back(element); } void print() { for (const T element : elements) { std::cout element ; } std::cout std::endl; } private: std::vectorT elements; }; int main() { // 生成 int 型元素的列表 Listint list1; list1.add(1); list1.add(2); list1.add(3); list1.print(); // 生成 string 型元素的列表 Liststd::string list2; list2.add(Hello); list2.add(World); list2.add(!); list2.print(); return 0; }一个Map类模板可以生成不同类型键值对的映射。#include iostream #include map template typename K, typename V class Map { public: void add(const K key, const V value) { elements[key] value; } void print() { for (const auto pair : elements) { std::cout pair.first - pair.second std::endl; } } private: std::mapK, V elements; }; int main() { // 生成 int 型键值对的映射 Mapint, int map1; map1.add(1, 10); map1.add(2, 20); map1.add(3, 30); map1.print(); // 生成 string 型键值对的映射 Mapstd::string, std::string map2; map2.add(Hello, World); map2.add(Goodbye, Cruel World); map2.print(); return 0; }3.7 实现依赖实现关系是指一个类实现类承诺实现另一个类接口定义的契约。接口定义了一组方法和属性但并不提供具体的实现而是描述了一种行为规范。实现类则提供了这些方法和属性的具体实现。例如一个Animal类定义一个Speak接口。一个Dog类实现Animal接口并提供具体的Speak方法实现。#include iostream class Animal { public: virtual void speak() 0; // 纯虚函数没有具体实现 }; class Dog : public Animal { public: void speak() override { std::cout Woof! std::endl; } }; int main() { Dog dog; dog.speak(); return 0; }实现关系的特点契约:接口定义了一种行为规范实现类必须遵守该规范。实现:实现类提供接口定义的方法和属性的具体实现。依赖:实现类依赖于接口因为需要根据接口定义来实现其功能。多态:接口可以被多个类实现从而提供不同的实现方式。3.8 扩展依赖扩展关系是一种 UML 关系类型表示一个用例扩展用例在某些条件下扩展另一个用例基本用例的功能。扩展关系通常用于表示可选的、非必须的功能。扩展关系的关键点扩展点:基本用例中定义的点扩展用例可以在该点插入其行为。可选性:扩展用例不是基本用例必需的只有在满足特定条件时才会执行。行为插入:扩展用例的行为插入到基本用例的流程中修改或补充基本用例的行为。类型:扩展关系可以是包含或扩展两种类型。扩展关系的应用场景可选功能:为基本用例添加可选功能例如日志记录、错误处理等。条件性功能:仅在满足特定条件时才执行的功能例如权限控制、数据验证等。行为变异:在不同情况下对基本用例行为进行不同的变异例如不同的用户界面、不同的处理流程等。扩展关系的优点灵活性:扩展关系可以使代码更加灵活可以根据需要动态添加或删除功能。可扩展性:扩展关系可以使代码更加可扩展可以方便地添加新的功能。可维护性:扩展关系可以使代码更加模块化和可维护性可以将可选功能或条件性功能从基本用例中分离出来。扩展关系与其他关系的比较包含关系:包含关系表示一个用例包含另一个用例的所有功能扩展关系则表示扩展用例只在某些情况下扩展基本用例的部分功能。继承关系:继承关系表示一个类继承另一个类的所有属性和方法扩展关系则表示两个用例之间是一种依赖关系。扩展关系示例一个登录用例可以扩展一个安全检查用例在需要进行安全检查时才执行安全检查功能。一个下单用例可以扩展一个优惠计算用例在满足优惠条件时才计算优惠价格。一个文件上传用例可以扩展一个病毒扫描用例在上传文件之前进行病毒扫描。#include iostream class IControl { public: virtual void Click() 0; protected: virtual void DoClick() 0; }; class Button : public IControl { public: void Click() { std::cout 按钮被点击了! std::endl; DoClick(); } protected: void DoClick() override { std::cout 按钮执行点击操作! std::endl; } }; class Security { public: virtual bool VerifyUser() 0; protected: virtual void DoVerifyUser() 0; }; class LoginUseCase { public: void Execute() { std::cout 登录用例正在执行... std::endl; // ... // 检查是否需要安全检查。 if (isSecurityCheckNeeded) { Security security; if (!security.VerifyUser()) { std::cout 登录失败身份验证失败! std::endl; return; } } // ... } private: bool isSecurityCheckNeeded true; }; int main() { LoginUseCase loginUseCase; loginUseCase.Execute(); return 0; }Control 类Control 类是一个抽象类代表一种控件。Control 类定义了一个虚方法 Click()用于触发控件的点击操作。Button 类Button 类继承自 Control 类代表一种按钮控件。Button 类实现了 Click() 方法用于执行按钮的点击操作。当用户点击按钮时Button 类会执行 Click() 方法并触发相应的业务逻辑。Security 类Security 类继承自 Control 类代表一种安全控制。Security 类没有实现 Click() 方法因为它不是用于点击操作的控件。Security 类可以提供其他方法用于执行安全检查操作。LoginUseCase 类LoginUseCase 类是一个用例代表登录用例。LoginUseCase 类使用 Button 类和 Security 类来实现其功能。LoginUseCase 类根据需要动态添加或删除 Button 类和 Security 类。#include iostream class Order { public: virtual double calculatePrice() 0; // 纯虚函数没有具体实现 protected: double originalPrice; public: Order(double originalPrice) { this-originalPrice originalPrice; } }; class DiscountCalculator { public: virtual bool isEligibleForDiscount(const Order order) 0; // 纯虚函数没有具体实现 virtual double calculateDiscount(const Order order) 0; // 纯虚函数没有具体实现 }; class OrderWithDiscount : public Order { public: OrderWithDiscount(double originalPrice) : Order(originalPrice) {} double calculatePrice() override { DiscountCalculator calculator; if (calculator.isEligibleForDiscount(*this)) { return originalPrice - calculator.calculateDiscount(*this); } else { return originalPrice; } } }; class SimpleDiscountCalculator : public DiscountCalculator { public: bool isEligibleForDiscount(const Order order) override { return order.originalPrice 100; } double calculateDiscount(const Order order) override { return order.originalPrice * 0.1; } }; int main() { Order* order new OrderWithDiscount(120); std::cout 原价 order-originalPrice std::endl; std::cout 优惠价 order-calculatePrice() std::endl; return 0; }在这个例子中OrderWithDiscount类扩展了Order类并使用了DiscountCalculator类来计算优惠价格。#include iostream #include fstream class File { public: virtual bool isInfected() 0; // 纯虚函数没有具体实现 protected: std::string path; public: File(const std::string path) { this-path path; } }; class VirusScanner { public: virtual bool scan(const File file) 0; // 纯虚函数没有具体实现 }; class FileUpload { public: virtual void upload(const File file) 0; // 纯虚函数没有具体实现 }; class FileUploadWithVirusScan : public FileUpload { public: FileUploadWithVirusScan() {} void upload(const File file) override { VirusScanner scanner; if (!scanner.scan(file)) { std::cout 文件 file.path 含有病毒无法上传 std::endl; return; } // 上传文件... std::cout 文件 file.path 上传成功 std::endl; } }; class SimpleVirusScanner : public VirusScanner { public: bool scan(const File file) override { std::ifstream ifs(file.path); if (ifs.is_open()) { // 扫描文件内容... return true; } else { return false; } } }; int main() { File* file new File(C:/test.txt); FileUploadWithVirusScan uploader; uploader.upload(*file); return 0; }在这个例子中FileUploadWithVirusScan类扩展了FileUpload类并使用了VirusScanner类来扫描文件是否含有病毒。4. 依赖关系的表示假设有一个ReportGenerator类它负责生成报告并使用DataSource类来获取所需的数据。在这种情况下ReportGenerator依赖于DataSource因为它需要DataSource提供的数据来完成其功能。在UML图中这种依赖关系会用一条从ReportGenerator指向DataSource的带有开放箭头的虚线来表示。5. 注意事项依赖关系应该尽可能地弱以便提高代码的模块性和可维护性。循环依赖关系应该避免因为它会导致代码难以理解和维护。依赖关系应该在设计阶段仔细考虑并进行必要的调整和优化。