面向对象设计 主要就是使用UML的类图,类图用于描述系统中所包含的类以及它们之间的相互关系,帮助人们简化对系统的理解,它是系统分析和设计阶段的重要产物,也是系统编码和测试的重要模型依据。
统一建模语言 UML (Unified Modeling Language) 类图是一种用于描述系统结构的图形化工具。它以类和对象为基础,主要用于表示系统中的类、接口、继承关系、关联关系等元素,以及它们之间的静态结构和关系。在本文中,将深入介绍UML类图的基本元素、关系类型以及如何创建一个简单而有效的类图。
类图以反映类的结构(属性、操作)以及类之间的关系为主要目的,描述了软件系统的结构,是一种静态建模方法。类图用来描述系统中有意义的概念,包括具体的概念、抽象的概念、实现方面的概念等,是对现实世界中事物的抽象。
类图的主要作用是对系统的词汇进行建模、对简单的协作进行建模和对逻辑数据库模式进行建模。类图显示集合的类,接口,关联,协作和约束,它也被称为作为结构图。
UML类图是在 设计程序之前 画,而不是等写完程序再画!!!
- Java 中有接口。
- C++ 中没有接口,只有抽象类。
在类图中一共包含了以下几种模型元素,分别是:类(
Class
)、接口(Interface
)以及类之间的关系。
类(class
/ struct
)封装了数据和行为,是面向对象的重要组成部分,它是具有相同 属性
、操作
、关系
的 对象集合的总称,是 对具有相同属性和行为的对象的一种抽象表示。在系统中,每个类都具有一定的职责,职责指的是类要完成什么样子的功能,要承担什么样子的义务。一个类可以有多种职责,但是设计得好的类一般只有一种职责。
接口(Interface
)是一种特殊的类,具有类的结构但 不可被实例化,只可以被实现(继承)。在某种程度上,一个 包含纯虚函数的抽象类 可以被认为是一个接口。然而,严格来说,C++中没有显式的“接口”关键字,而是通过抽象类和纯虚函数的组合 来实现 接口的概念。
比如,我现在定义了猎人类:
class Hunter
{
public:
int m_age = 32; //普通的成员变量,属于对象,只有创建对象才会存在
static int m_times; //静态成员变量,属于类,并不属于对象,在不创建对象的情况下仍然存在。(所有对象共享唯一的静态成员变量)
string getName()
{
return m_name;
}
void setName(string name)
{
m_name = name;
}
void goHunting()
{
aiming();
shoot();
}
static void saySorry() //静态成员函数,属于类,并不属于对象;可以直接通过类名调用
{
//静态成员函数只能使用静态成员变量,非静态成员变量是不能直接使用的(但可以间接使用)
string count = to_string(m_times);
cout << "Say sorry to every animal " + count + " times!" << endl;
}
protected:
string m_name = "Jack";
void aiming()
{
cout << "使用" + m_gunName + "瞄准猎物..." << endl;
}
private:
string m_gunName = "AK-47";
void shoot()
{
cout << "使用" + m_gunName + "射击猎物..." << endl;
}
};
int Hunter::m_times = 3;
[可见性] [属性名称] : [类型] = { 默认值,可选 }
[可见性] [方法名称] ([参数名 : 参数类型, …]) : [返回值类型]
冒号
:
前是 方法名/变量名(根据有无括号区分)
冒号后:
是 返回参数/变量类型(根据有无括号区分)
如果没有冒号的话表示 方法返回空(也有人通过:void
表示返空)
符号解释
+
” “-
” 和 “#
” 表示 可见性:
+
:public
,公用的,对 所有类 可见-
:private
,私有的,只对 该类本身 可用#
:protected
,受保护的,对该类的 子孙 可见default
可见性 | 符号 | 当前类 | 当前包 | 子孙类 | 其他包 |
---|---|---|---|---|---|
public | + | ✔ | ✔ | ✔ | ✔ |
protected | # | ✔ | ✔ | ✔ | ✖ |
default | 无 | ✔ | ✔ | ✖ | ✖ |
private | - | ✔ | ✖ | ✖ | ✖ |
~
:package
,包的,只对同一包声明的其他类可见=
:表示默认值__(下划线)
:表示static
斜体
:抽象 (注意也可以用两个尖括号包裹来表示抽象,比如 —— <<我是 抽象类 or 接口 >>)如果我们定义的类是一个 抽象类(类中有纯虚函数),在画UML类图的时候,类名需要使用斜体显示。
在使用UML画类图的时候,虚函数 的表示方式跟随类名,也就是使用斜体,如果是 纯虚函数 则需要在最后给 函数指定 = 0。
UML中的关系是面向对象关系。如果不以面向对象的思维去考虑会感觉到有很多关系认为是一样的。
继承 也叫作 泛化(Generalization
),用于描述父子类之间的关系,父类又称为 基类 或者 超类,子类又称作 派生类。在UML中,泛化关系用 带空心三角形的实线 来表示。
关于继承关系一共有两种:普通继承关系 和 抽象继承关系,但是不论哪一种表示继承关系的线的样式是不变的。
假如现在我定义了一个父类(Bird
)和两个子类(Parrot
、Eagle
):
class Bird
{
public:
string getName()
{
return m_name;
}
void setName(string name)
{
m_name = name;
}
virtual void fly() {}
virtual void eat() {}
protected: //可以被子类继承
string m_name;
};
class Parrot : public Bird
{
public:
void fly() override
{
cout << "我拍打翅膀飞行..." << endl;
}
void eat() override
{
cout << "我喜欢吃肉肉的小虫子..." << endl;
}
};
class Eagle : public Bird
{
public:
void fly() override
{
cout << "我展翅翱翔..." << endl;
}
void eat() override
{
cout << "我喜欢吃小动物..." << endl;
}
};
Bird
中的 fly()
和 eat()
是虚函数,它有两个子类Porrot
和Eagle
在这两个子类中重写了父类的虚函数,在使用 带空心三角形的实线 表示继承关系的时候,有空心三角的一端指向父类,另一端连接子类。 组合(Composition
)关系也表示的是一种整体和部分的关系,但是在组合关系中 整体对象 可以 控制 成员对象的生命周期,一旦整体对象不存在,成员对象也不存在,整体对象和成员对象之间具有 同生共死 的关系。
在UML中组合关系用带 实心菱形的直线 表示,下面举两个组合关系的例子:
Head
)和 嘴巴(Mouth
)、鼻子(Nose
)、耳朵(Ear
)、眼睛(Eye
)Tree
)和 树根(Root
)、树干(Trunk
)、树枝(Branch
)、树叶(Leaf
)以树为例,对应的C++类的定义如下:
class Root
{
};
class Trunk
{
};
class Branch
{
};
class Leaf
{
};
class Tree
{
public:
Tree()
{
m_root = new Root;
m_trunk = new Trunk;
m_branch = new Branch;
m_leaf = new Leaf;
}
~Tree()
{
delete m_root;
delete m_trunk;
delete m_branch;
delete m_leaf;
}
private:
Root* m_root;
Trunk* m_trunk;
Branch* m_branch;
Leaf* m_leaf;
};
聚合(Aggregation
)关系表示 整体 与 部分 的关系。在聚合关系中,成员对象 是 整体的一部分,但是成员对象可以脱离整体对象独立存在。在UML中,聚合关系用 带空心菱形的直线 表示,下面举两个聚合关系的例子:
Car
)与 引擎(Engine
)、轮胎(Wheel
)、车灯(Light
)Forest
)与 植物(Plant
)、动物(Animal
)、水(Water
)、阳光(Sunshine
)以森林为例,对应的C++类的定义如下:
class Plant
{
// 植物
};
class Animal
{
// 动物
};
class Water
{
// 水
};
class Sunshine
{
// 阳光
};
//森林
class Forest
{
public:
Forest(Plant p, Animal a, Water w, Sunshine s) : m_plant(p),m_animal(a),m_water(w),m_sun(s)
{
}
private:
Plant m_plant;
Animal m_animal;
Water m_water;
Sunshine m_sun;
};
表示聚合关系的线,由 空心菱形 的一端指向整体对象,另一端连接 局部对象(有些UML绘图软件在这一端还带一个箭头)。
关联(Assocition
)关系是类与类之间最常见的一种关系,它是一种结构化的关系,表示一个对象与另一个对象之间有联系,如汽车和轮胎、师傅和徒弟、班级和学生等。在UML类图中,用 ( 带接头 或 不带箭头 的)实线 连接有关联关系的类。在C++中这种关联关系在类中是这样体现的,通常 将一个类的对象 作为 另一个类的成员变量。
类之间的关联关系有三种,分别是:单向关联
、双向关联
、自关联
。下面逐一给大家进行介绍。
单向关联指的是关联只有一个方向,比如每个孩子(Child
)都拥有一个父亲(Parent
),其代码实现为:
class Parent
{
};
class Child
{
private:
Parent m_father;
};
Father 类
作为了Child 类
的 成员变量,因此箭头端应该指向 Father 类
,另一端连接Child 类
。现实生活中每个孩子都有父母,每个父母同样有自己的孩子,如果想要通过类来描述这样的亲情关系,代码如下:
class Parent
{
private:
Child m_son;
};
class Child
{
private:
Parent m_father;
};
有些UML绘图软件使用的是 带双向箭头的实线 来表示双向关联关系。
自关联指的就是当前类中 包含一个自身类型的对象成员,这在 链表 中非常常见,单向链表中都会有一个指向自身节点类型的后继指针成员,而双向链表中会包含一个指向自身节点类型的前驱指针和一个指向自身节点类型的后继指针。就以双向链表节点类为例,它的C++写法为:
class Node
{
private:
int m_data = 0;
Node* m_prev;
Node* m_next;
};
有些UML绘图软件表示类与类的关联关系,使用的就是一条实线,没有箭头。
依赖(Dependency
)关系是一种 使用关系,特定事物的改变有可能会影响到使用该事物的其他事物,在需要表示一个事物使用另一个事物时使用依赖关系,大多数情况下依赖关系体现在某个类的方法 使用 另一个类的对象 作为参数。(也可以使用排除法判断,不是组合和聚合关系,就是依赖联系啦!)
在UML中,依赖关系用 带箭头的虚线 表示,由 依赖 的一方 指向 被依赖的一方,下面举两个依赖关系的例子:
驾驶员(Driver
)开车,需要将车(Car
)对象作为参数传递给 Driver 类
的 drive()
方法。
class Car
{
public:
void move() {}
};
class Driver
{
public:
void drive(Car car)
{
car.move();
}
};
树木(Tree
)的生长,需要将空气(Air
)、水(Water
)、土壤(Soil
)对象作为参数传递给 Tree 类
的 grow()
方法。
class Water
{
};
class Air
{
};
class Soil
{
};
class Tree
{
public:
void grow(Water w, Air a, Soil s)
{
cout << "借助 w 中的水分, s 中的养分和 a 中的二氧化碳, 我就可以茁壮成长了";
}
};
关于树木这个类,它对应的UML类图为:
依赖关系通常通过三种方式来实现:
类之间的关系强弱顺序是这样的:继承(泛化) > 组合 > 聚合 > 关联 > 依赖。
组合、聚合、关联关系之间的区别
组合 和 聚合 的区别则在 语义 和 实现 上都有差别:
- 组合的两个对象之间生命周期有很大的关联,被组合的对象 在 组合对象创建的 同时或者创建之后 创建,在组合对象销毁之前销毁,聚合则无需考虑这些事情。
- 一般来说 被组合对象 不能脱离 组合对象独立存在,而且也只能属于 一个 组合对象,聚合则不一样,被聚合的对象可以属于 多个 聚合对象。
关联 和 聚合 的区别主要在于 语义 上:关联的两个对象之间一般是平等的,聚合则一般是不平等的。
(实际应用中,这三种关系的界限划分其实没有那么清楚,有些时候我们会感觉组合和聚合没什么区别,所以,在设计的时候没必要死抠细节,只要能够利用对象之间的关系设计出可行的解决方案即可。 如果同时有多个关系,只需画出最强的关系即可。)
最后,再举例子来描述一下这三种关系:
- 人和自己的心脏属于组合关系,因为心脏不能脱离人体而独自存在。
- 图书馆看书的时候,人和书属于聚合关系。书是可以独立存在的,而且书不仅可以属于自己,也可以属于别人。
- 朋友之间属于关联关系,因为这种关系是平等的,关联关系只是用于表示两个对象之间的一种简单的联系而已。
参考:https://subingwen.cn/design-patterns/UML-class-diagrams/
注:仅供学习参考,如有不足,欢迎指正!