drawio和EA是架构设计时经常使用的画图工具。
drawio学习门槛低,使用灵活,但是功能仅仅限于画图。
EA学习门槛高,但是功能更加的丰富:
①在画图方面,EA严格满足UML标准,EA中的图和类是关联的,如果修改了一个类的名字或者函数等,在引用这个类的图中也会自动修改
②EA还可以将架构设计时定义的类、接口等导出为代码
③EA可以导入已有的代码,生成类图
类似于表写代码,drawio类似于一种纯编辑器的代码,没有其它辅助的功能,EA类似于一个有丰富功能的IDE,可以有各种各样的提示,提供一种沉浸式的使用环境。
当讨论uml,最常见的使用场景就是类图。常用的类之间的关系包括实现、继承、聚合、组合、关联、依赖,而这六种关系中实现和继承关系类似,聚合和组合类似,依赖和关联类似。
本文中的类图使用EA来画。
1实现和继承
实现:
实现说的是类实现接口。在c++中,没有接口这个概念,抽象类可以看做接口。c++中的抽象类,是至少有一个函数是纯虚函数,这样的类不能实例化对象。如果以java语言的标准来定义c++中的接口和抽象类,那么接口中的函数都是纯虚函数,都没有自己的实现,抽象类中是至少有一个函数是纯虚函数,也可以有不是纯虚函数的函数。但是在很多时候,接口和抽象类的边界也没有这么清晰。接口可以看做是对类的行为的约束,值提供规则,不提供实现,就像很多行业中的标准制定者,只定义标准,不提供实现,比如网络通信协议标准、autosar标准等。
接口只定义标准,不提供实现,所以一个类对接口进行实现,就是实现的关系。当然,一个接口也可以继承一个接口。
如下代码,有一个接口类Shape,有两个实现类Rectangle和Circle。Rectancle和Circle是对Shape的实现。
#include <iostream>
#include <cmath>
#include <stdexcept>// Shape 接口类
class Shape {
public:virtual ~Shape() {} // 虚析构函数// 获取面积virtual double getArea() const = 0;// 获取周长virtual double getPerimeter() const = 0;// 打印形状信息virtual void printInfo() const {std::cout << "Area: " << getArea()<< ", Perimeter: " << getPerimeter() << std::endl;}
};// 矩形类
class Rectangle : public Shape {
private:double width, height;public:Rectangle(double w, double h) : width(w), height(h) {if (width <= 0 || height <= 0) {throw std::invalid_argument("Width and height must be positive");}}double getArea() const override {return width * height;}double getPerimeter() const override {return 2 * (width + height);}void printInfo() const override {std::cout << "Rectangle with width: " << width<< ", height: " << height << std::endl;Shape::printInfo();}
};// 圆形类
class Circle : public Shape {
private:double radius;static constexpr double PI = 3.14159265358979323846;public:Circle(double r) : radius(r) {if (radius <= 0) {throw std::invalid_argument("Radius must be positive");}}double getArea() const override {return PI * radius * radius;}double getPerimeter() const override {return 2 * PI * radius;}void printInfo() const override {std::cout << "Circle with radius: " << radius << std::endl;Shape::printInfo();}
};int main() {try {// 创建各种形状Shape* shapes[] = {new Rectangle(4, 5),new Circle(3)};// 测试每个形状for (Shape* shape : shapes) {shape->printInfo();std::cout << std::endl;delete shape;}} catch (const std::exception& e) {std::cerr << "Error: " << e.what() << std::endl;}return 0;
}
Rectangle和Circle堆Shape的实现关系,就可以表示为下图。实现关系用 三角形加虚线来表示。
继承:
实现是对一个没有实现的接口类进行实例化,进行实现,是一个从抽象到具体的过程。而继承是为了复用,继承说的是派生类和基类之间的关系。
还是以上边的图形类代码为例子,如果我们新增一个正方形的类,正方形是一个长和宽相等的特殊的矩形,所以正方形可以继承矩形。那么正方形类的代码如下:
// 正方形类
class Square : public Rectangle {
public:Square(double length) : Rectangle{length,length} {}
};
实现是为了多态,继承是为了复用,可以看到,正方形类可以服用矩形类的函数和属性,正方形类只需要实现自己的构造函数即可。
继承关系用三角形加实现来表示。实现和继承的区别是,一个是虚线,一个是实线。
2聚合和组合
聚合和组合,两者均是表示整体与部分的关系,但是也是有区别的:
聚合:整体不存在了,部分还可以单独存在
比如汽车和轮胎、发送机、变速箱的关系,汽车是由轮胎、发动机、变速箱等聚合而成,但是轮胎、发动机、变速箱可以单独生产,在不有装车之前就可以存在,如果汽车要处理掉,这辆车可以拆卸,拆卸之后,汽车不存在了,汽车的零件还可以单独存在。
组合:整体不存在了,部分也就不存在了
比如公司和部门的关系,我们没有见过不依赖于公司而独立存在的部门,部门都是存在于公司内的,当公司倒闭,那么部门也就不存在了。
可以看到部分和整体之间的关系,组合的依赖更强。
下图是聚合的关系,聚合用空心菱形加实线表示。
下图是组合关系,组合用实心菱形加实现表示。
3依赖和关联
依赖关系也可以叫引用关系,也可以叫使用关系,说的是在一个对象中使用了另一个对象,那么就可以说前者依赖后者。比如程序员在工作中会用到电脑,那么就可以说两者的关系是依赖关系。
依赖关系如下图所示,依赖用箭头和虚线表示。
个人感觉关联和依赖之间的概念,并不是很清晰,没有很清晰的边界,有时候关联可以看做依赖,依赖也可以看做关联。 关联关系分单向关联、双向关联、自关联、多维关联。
单向关联,箭头加实线表示。
双向关联,实线两端均有箭头,也可以两端均没有箭头。
自关联,指向自身的实线表示,可以带箭头,也可以不带。
多维关联,表示多个对象之间的关联关系,可以用一个菱形来表示,当然也可以在多个对象之间两两关联来表示。