工厂类模式家族之简单工厂模式

在面向对象编程的领域,设计模式给我们提供了适合特定场景的软件设计思想,不过大多设计模式都可以通过一般的设计进行替代,但我们为什么还有费工夫去遵循设计模式呢?这就要说到设计模式的精髓了,我们通过对软件系统进行良好的设计,不但可以提高代码的可重用性,增强系统的可扩展性,给客户提供良好的接口,还可以减少编码过程中因代码组织太乱扩展过程中需要修改旧代码而带来的一连串的错误,降低维护成本。软件设计过程我们应该尽量追求符合软件设计的开闭原则。

开闭原则:在面向对象编程领域中,开闭原则规定“软件中的对象(类,模块,函数)应该对于扩展是开放的,但是对于修改是封闭的”。该特性在产品化的环境中是特别有价值的,在这种环境中我们认为一旦类完成,我们可以对它进行扩展改变其行为,但是不允许修改类。也就是说一个类的实现只应该因错误而修改。--by wiki

工厂模式家族包括三种重要的模式,分别是简单工厂模式,工厂模式以及抽象工厂模式。他们都属于类的创建型模式。创建型模式包括两种,分别是类的创建型模式对象的创建型模式。类的创建型模式通常使用继承关系,将类的创建交由其具体的子类完成,这样就向外界隐藏了如何得到具体类的实现细节,以及这个类的实例是如何被创建或者组织在一起的。;对象创建型模式通常把一个类的创建委托给另一个对象完成,可以根据语境动态地决定生成哪个具体类的实例。

本节我们来讲解工厂模式中的简单工厂模式。主要包括以下几个部分:

  • 简单工厂模式的实质
  • 简单工厂模式的适用性
  • 简单工厂模式的结构
  • 简单工厂模式的参与者
  • 简单工厂模式各成分之间的交互
  • 实际应用
  • 简单工厂模式的优缺点
  • 简单工厂模式实例代码

简单工厂模式的实质

简单工厂模式又称为静态工厂模式。 它的实质是根据客户传递的信息,工厂类通过该信息制造出相应的产品的实例返回给客户。这样我们就做到了客户只是产品的消费者,而真正的创建者是工厂类。 在简单工厂模式中,待被创建的产品通常继承自同一个类。而这个类中包含了具体产品的所有的公共成员和方法。

简单工厂模式的适用性

简单工厂模式将对象的创建和对象本身的业务分离开来,降低了系统的耦合度,当维护期间需要对客户代码或者产品代码进行修改的时候,修改其中之一不会影响另一个。

简单工厂模式的结构

简单工厂模式的参与者

简单工厂模式中一般有以下几个部分:

  • 工厂类:简单工厂模式的核心,它的作用是根据客户提供的信息创建相应的具体产品
  • 抽象产品:所有具体产品的父类,其中主要包含所有具体产品共有的方法或对象
  • 具体产品:工厂类创建的具体实例。

简单工厂模式各成分之间的交互

  • 客户首先创建factory类(一般为单例模式),
  • factory类创建成功后,客户调用其createProduct方法,并传入相关信息,
  • 具体产品实例被创建并返回给client,开始进行使用concreteProduct

简单工厂模式的实际应用

在实际开发中使用简单工厂模式中,我们可以进行变通的使用。

在实际情况种可能会出现比较复杂的抽象产品和和具体产品之间的关系,这个时候我们依然也可以使用抽象工厂模式:

简单工厂模式的优缺点

简单工厂模式的优点: 通过在中间添加一个工厂类,降低产品类和客户代码之间的耦合度;客户在获取产品的时候无需记住所有产品的构造方法,只需要通过同意的工厂类接口进行创建产品,大大提高了效率准确率。

简单工厂模式的缺点:所有的产品实例化的逻辑都在工厂类的一个创建方法中,当需要添加新产品的时候,不得不进行修改factory类,这样就违背了设计的开闭原则(对扩展开放,对修改封闭);另外当产品类别过多的时候,会出现这个函数冗杂的问题,增加维护成本。最重要的是这个工厂类是所有产品的入口,当它不能工作的时候,所有的产品将陷入瘫痪状态。

简单工厂的实例

下面通过形状shape,circle以及rectangle来实现一个简单的简单工厂模式(如有问题,欢迎指正):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//factory.hpp
#ifndef FACTORY_HPP_
#define FACTORY_HPP_
#include "circle.hpp"
#include "rectangle.hpp"
#include <memory>
#include <string>
class Factory {
private:
Factory() {}
Factory(const Factory &) = delete;
Factory &operator=(const Factory &) = delete;
public:
static std::shared_ptr<Factory> m_factory;
static std::shared_ptr<Factory> getInstance();
static std::shared_ptr<Shape> getShape(const std::string &flag);
};
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//factory.cpp
#include "factory.hpp"
std::shared_ptr<Factory> Factory::m_factory = nullptr;
std::shared_ptr<Factory> Factory::getInstance() {
if (m_factory == nullptr) {
m_factory = std::unique_ptr<Factory>(new Factory());
}
return m_factory;
}
std::shared_ptr<Shape> Factory::getShape(const std::string &flag) {
if (0 == flag.compare("circle")) {
return std::shared_ptr<Shape>(new Circle());
} else if (0 == flag.compare("rectangle")) {
return std::shared_ptr<Shape>(new Rectangle());
}
}
1
2
3
4
5
6
7
8
9
10
//shape.hpp
#ifndef SHAPE_HPP_
#define SHAPE_HPP_
#include <iostream>
class Shape {
public:
virtual void draw() = 0;
virtual double getArea() = 0;
};
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//circle.hpp
#ifndef CIRCLE_HPP
#define CIRCLE_HPP
#include "shape.hpp"
class Circle : public Shape {
public:
Circle(double t_radius = 0) : m_radius(t_radius) {}
virtual void draw() {
std::cout << "this is a circle, the radius is : " << m_radius << std::endl;
};
virtual double getArea() { return m_radius * m_radius * 3.14; }
private:
double m_radius;
};
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//rectangle.hpp
#ifndef RECTANGLE_HPP_
#define RECTANGLE_HPP_
#include "shape.hpp"
class Rectangle : public Shape {
public:
Rectangle(int a = 0, int b = 0) : m_height(a), m_width(b) {}
virtual void draw() {
std::cout << "this is a rectangle\n height: " << m_height
<< "\nwidth:" << m_width << std::endl;
}
virtual double getArea() { return m_height * m_width; }
private:
int m_height;
int m_width;
};
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
//main.cpp
#include "circle.hpp"
#include "factory.hpp"
#include "rectangle.hpp"
int main() {
std::shared_ptr<Factory> t = Factory::getInstance();
std::shared_ptr<Shape> c = t->getShape("circle");
c->draw();
std::cout << "the area is: " << c->getArea() << std::endl;
std::shared_ptr<Shape> r = t->getShape("rectangle");
r->draw();
std::cout << "the area is :" << r->getArea() << std::endl;
}

Comment and share

GRASP设计原则(职责分配原则)

GRASP(General responsibility assignment software Principle)设计原则是设计模式的基础,在GOF的23中设计模式中处处可以体现其中的一个或多个设计原则,所以在掌握设计模式之前需要对GRASP原则有一定的了解,本节我在这里总结一下grasp原则。

本文共分为以下几个内容:

  • GRASP的九个原则
  • GRASP原则详解
  • GRASP原则在23中设计模式中的体现

GRASP的九个原则

  • 信息专家原则(information)
  • 创造者原则(creator)
  • 低耦合原则(low coupling)
  • 高内聚原则(high cohesion)
  • 控制器原则(controller)
  • 多态原则(polymorphism)
  • 纯虚构(pure Fabrication)
  • 中介原则(indirect)
  • 受保护变量原则(protected Variations)

Grasp原则详解

信息专家原则(information expert)

信息专家模式的本质指的是我们应该将职责委托给哪一个对象,这个职责可以是一个方法,也可以是一个算法或者其他内容。它是面向过程设计过程中最基本的原则。

委托原则:我们在设计对象的时候,如果某个对象拥有完成某个职责所需要的所有信息,那么这个职责就分配给这个对象实现。这个时候,这个类就是相对于这个职责的信息专家。

示例:我们在设计购物网站的时候,为避免重复,一种商品只能在购物车中出现一次,如果多次出现,则需要将其数量增加。这个时候我们在将物品放入购物车的时候,要首先判断当前物品是否在购物车中,判断两个物品是否为同一个物品的方法这个职责应该委托给谁呢?显而易见,商品类中有唯一标识,所以这个职责由商品类实现,而不是购物车。

创造者原则(creator)

creator原则的本质是创建类对象职责应该委托给那个对象,也就是谁应该负责产生某个类的实例。

解决方案: 如果符合下面的一个或者多个条件,则可以将创建A的实例的职责分配给B;

  • B包含A
  • B聚合A
  • B拥有初始化A的数据并在创建A的实例时将数据传递给A
  • B记录A的实例
  • B频发使用A

满足上述一种或者多种情况的时候,我们应该奖创建A的实例的职责分配给B。

合理的creator原则带来的优点:如果职责分配合理,设计就能降低耦合,提高设计的清晰度,封装性和重用性。

示例:例如订单和商品的关系是聚合关系,这个时候我们将在订单中创建商品。

低耦合(Low coupling)

耦合是评价一个系统中各个元素之间连接或者依赖关系强弱的尺度。低耦合的原则是我们在设计系统的时候尽量降低系统中各个元素之间的耦合度,这样对于系统的理解和维护都有很大的益处。

耦合性高的系统会带来的坏处:

  • 一个类的修改导致其他类产生较大的影响;
  • 系统难以维护和理解;
  • 系统的重用性差,在重用一个高耦合类的时候,不得不重用它所依赖的所有类。

两个类具有以下特性中的其中一个,我们就说这两个类是耦合的:

  • A具有一个类型为B的属性;
  • A调用B的方法
  • A的方法包含对B的引用(参数或者返回值的方式)
  • A是B的直接或者间接的子类
  • A是接口B的一种实现

低耦合系统的设计方法

  • 在类的划分上,尽量创建松耦合的类,类之间的耦合性越低,越有利于复用,修改一个类不会影响其他类。
  • 在类的设计上,尽量降低类中成员和方法的访问权限。
  • 在类的设计上,尽量将类设计为不变类
  • 在类的引用上,将一个对象对另一个对象的引用降低到最小

高内聚(high cohesion)

内聚是评价一个对象的职责被关联的尺度或者强弱,也可以说是功能性内聚的职责。也就是功能性紧密的相关职责应该放在同一个类中,并共同完成有限的功能。这样做更加有利于对类的理解和重用,也可以降低类的维护成本。

往往低内聚的系统设计会导致类的混乱,当对功能进行扩展或者改进的时候带来不必要的麻烦,低内聚的类也不利于重用,因为他们的职责如此之混乱。

为了达到高内聚,我们需要对类的职责进行分解,使分解出来的类具有独立的职责,满足单一职责原则。将一些需要在多个类中使用到的方法封装到一个类中,其他的类只负责他们需要负责的相关功能,这样我们可以提高类的内聚程度。

控制器原则(controller)

控制器模式的实质是将一些系统事件的接受和处理委托给一个的对象controller,这个对象可以是一个类,系统或者子系统,它不与UI进行交互,它只负责系统信息的接收和处理。

一般情况下,控制器是一个系统,这个系统中包括多个处理器,分别对应处理不同的事务。通常情况下,一个控制器应当把要完成的功能委托给其他对象,而它只负责任务的协调控制和分配。

控制器原则与MVC模式相对应,MVC模式是一种比设计模式更高的架构模式。

多态原则(polymorphism)

多态原则与面向对象设计原则中的多态概念类似,这里不再详细赘述。

纯虚构(pure Fabrication)

纯虚构原则与我们所说的纯虚函数类似。

  纯虚构的作用是用来解决高内聚和低耦合之间的矛盾的。高内聚低耦合是我们系统设计的终极目标,高内聚意味着我们要将类拆分成多个功能集中的类,但是拆分的多个类之间需要进行协作才能正常工作,这样又增加了类之间的耦合性。

  纯虚构原则是用来解决上述问题的方案。它要求将一部分类的职责转移到纯虚构类中,在理想的情况下,分配给这种虚构类的职责是为了达到高内聚低耦合的效果。在实际的操作过程中,纯虚构类的实现又很多种方式,例如将数据库中操作的方法从数据库实体中分离出来,形成专门的数据访问类;通过对类的分解来实现类的重用,新增加的数据访问类对应于数据的持久化存储,这是软件开发过程中为了方便虚构出来的一个概念。一般情况下,纯虚构模式通常基于功能进行划分的。

中介模式(indirect)

中介模式的目的是为了避免两个对象之间产生直接耦合,降低对象之间的耦合度。

解决方案是建立中间对象来协调两个对象之间的交互,避免耦合性过高。

受保护模式(protected variations)

受保护模式的实质与OCP(开放闭合原则)类似,我们首先找到系统中不稳定的变化点,使用统一的接口封装起来,如果未来发生变化的时候,可以通过扩展接口来扩展新的功能,而不需要改变旧的代码。这样达到易于扩展的目的。

Comment and share

创建型设计模式之build模式

最近在读《设计模式-可复用面向对象软件设计的基础》一书,在阅读的过程中我会结合书中的相关知识和实例以及在网络上的博客对相关的模式的理解进行总结,并在此基础上加入自己的一些理解,总结模式中需要注意的一些点,记录在此博客,以供大家交流分享,同时防止自己对内容遗忘,如有不正确指出,欢迎批评指正。

本节的主要内容是设计模式中的创建型模式之一:builder模式

要用好builder模式,必须对其机制了解透彻,将该模式用在合适的软件中才能显出它真正的威力,第五部分中实例将为您展示它的真正威力。

本文内容分为以下几个方面:

  • builder模式的意图
  • builder模式的适用性
  • builder模式的结构(通用UML类图)及详解
  • builder模式中的参与者
  • builder模式中各个成分之间的交互
  • builder模式比较好的一个实例
  • builder模式使用过程中注意的点
  • KFC套餐实例代码

builder模式的精髓都隐藏于builder模式的意图和实用性中,让你真正理解这两个方面,可以说你已经掌握了builder模式(以下意图和模式都摘自《设计模式》书中)

builder模式的意图

builder模式的意图是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

builder模式的适用性

  • 创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
  • 当构造过程必须允许被构造的对象有不同的表示时。

提前记录一下:装配的工作是由导向器(director)完成的,复杂对象的创建是由具体的建造器完成(concreteBuilder),这里不明白没问题,等你看完全文再过来看这里你就理解了。

当且仅当上面两个条件均满足的情况下我们要使用builder模式(完全理解这两条不一件重要的事情,但是不要着急,当你读完这篇文章的时候就会拨开它的庐山真面目了)

builder模式的结构(UML类图)及详解

builder模式的UML类图关系如下:

注意的问题:

  • 在builder中一般不声明纯虚函数,而是把它们定义为空方法,这使客户只重定义他们感兴趣的操作。
  • 在director中,注意我们不是把所有的buildPart都去执行一次,而是根据需要的客户的需求,进行定制的去buildPart部分构建,可以构建一次,也可以构建多次。
  • 一般将m_product声明为protected成员,因为这样既保证了封装性,又能使得concreteBuilder能够正常操作product;

builder模式种的参与者

从上面的类图中我们也可以看出,在builder模式中的参与者有以下四种,以及他们的职责分别是:(此处为个人理解,与书中内容可能不一致,如果有问题欢迎指正)

  • Director: 负责装配product的各个部件,使用Builder的类方法进行实现。它的作用是隔离了客户与product的具体生产过程;并负责控制product的生产过程。
  • Builder: 为创建一个product对象的各个部件指定抽象接口。一般情况下默认builderPart的操作默认为空。
  • concreteBuilder: 具体实现每一个部件的具体的复杂生产过程,如buildPartA()的具体实现,并提供一个检索产品的接口。
  • Product: 这个争议不大,即为被构造的复杂对象。此类对象会有不同的表示。

builder模式各个成分之间的交互方式

先上一张时序图:

从上面的时序图可以分析出,各个成分之间的交互方式如下:

  • 客户首先创建一个concreteBuilder对象,然后创建一个Director,
  • 创建结束之后,使用concreteBuilder修饰对象Director,
  • 然后调用Director的construct()方法,进行构造Product,
  • 最后客户通过concreteBuilder的getResult()方法取回生成的Product

builder模式中一个较好的实例

两个实例来自网络对两本书籍实例的纠正,改编

上面这篇文章深刻的分析了builder模式的本质以及诸多误区的分析,收益颇多,在此感谢博主分享。

在本例中,设备(Equipment)是一个复杂对象,由一个machine和一个或多个输入端口(InputPort)或者输出端口(outputPort)组成;其中输入或输出端口可能有不同的类型(ordinary和super)。现在要你设计一个生成不同型号的产品,要求产品可能包含一进一出(普通或super),一进两出(普通或super)。

  在设计中,我们首先定义一个LCDFactory对象充当director,一个设备生成器(EQPBuilder),相当于Builder。

​ 首先ordinary和super是port的内部实现方式不同,所以我们需要定义两个具体类,即 ordinaryEQPBuilder和superEQPBuilder。

​ 在EQPbuilder中,我们将定义四个函数,分别是:buildMachine(), addInputPort(), addOutputPort()和getEQP()。两个concrete类继承EQPBuilder。

​ 当我们需要获取不同数目的port的设备,这属于组装方面的范畴,所以我们将在LCDFactory中的createEQP中做。其UML类图如下:

builder模式在使用的过程中需要注意的问题

这里所说的注意的问题可能上面已经提到了,但是这里还是要着重强调一下,因为我们稍不注意,就可能将模式滥用,导致系统设计的失败。

  • 在builder模式中Builder一定不要定义纯虚函数成员函数,而是定义为空方法的虚函数,这样就可以使得客户可以只定义他们感兴趣的操作。
  • Director负责装配产品,concreteBuilder负责实现复杂产品部件的具体实现
  • 在Director中调用Builder的buildPart函数的时候,并不是每个函数都被调用,而是根据要生成的product对其选择性调用,可能调用零次,也可能调用多次。
  • Builder中的m_product声明为protected,即保证对象的封装性,又能让具体类方便的使用。(这是我在写代码的时候的解决方法,如果有更好的将m_product声明为private的解决方法欢迎交流2824759538@qq.com)

KFC套餐实例代码

这是本人使用KFC服务员生成套餐时的一种模拟,其实实际情况中并不需要使用builder模式,但是这里为了练手强行写成了builder模式,希望大家不要吐槽。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//KFCWaiter.hpp 注意此处water相当于Director的作用
#ifndef KFCWAITER_HPP_
#define KFCWAITER_HPP_
#include "MealBuilder.hpp"
class KFCWaiter {
public:
void setMealBuilder(std::shared_ptr<MealBuilder> t_builder);
void construct();
private:
std::shared_ptr<MealBuilder> m_builder;
};
#endif
1
2
3
4
5
6
7
8
9
//KFCWaiter.cpp
#include "KFCWaiter.hpp"
void KFCWaiter::setMealBuilder(std::shared_ptr<MealBuilder> t_builder) {
m_builder = t_builder;
}
void KFCWaiter::construct() {
m_builder->buildFood();
m_builder->buildDrink();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Meal.hpp Meal相当于Product
#ifndef MEAL_HPP
#define MEAL_HPP
#include <iostream>
#include <string>
class Meal {
public:
Meal(std::string t_food = "hanbao", std::string t_drink = "kele");
~Meal();
std::string getFood() const;
std::string getDrink() const;
void getMeal() const;
void buildFood(const std::string &t_food);
void buildDrink(const std::string &t_drink);
private:
std::string m_food;
std::string m_drink;
};
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//Meal.cpp
#include "Meal.hpp"
Meal::Meal(std::string t_food, std::string t_drink)
: m_food(t_food), m_drink(t_drink) {}
Meal::~Meal() {}
std::string Meal::getFood() const { return m_food; }
std::string Meal::getDrink() const { return m_drink; }
void Meal::getMeal() const {
std::cout << "套餐为:" << std::endl
<< "食物:" << m_food << std::endl
<< "饮料:" << m_drink << std::endl;
}
void Meal::buildFood(const std::string &t_food) { m_food = t_food; }
void Meal::buildDrink(const std::string &t_drink) { m_drink = t_drink; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//MealBuilder.hpp
#ifndef BUILDER_HPP
#define BUILDER_HPP
#include "Meal.hpp"
#include <memory>
#include <string>
class MealBuilder {
public:
MealBuilder();
virtual void buildFood() = 0;
virtual void buildDrink() = 0;
std::shared_ptr<Meal> getResult();
virtual ~MealBuilder(){};
protected:
std::shared_ptr<Meal> m_meal;
};
#endif
1
2
3
4
//MealBuilder.cpp
#include "MealBuilder.hpp"
MealBuilder::MealBuilder() : m_meal(std::shared_ptr<Meal>(new Meal())) {}
std::shared_ptr<Meal> MealBuilder::getResult() { return m_meal; }
1
2
3
4
5
6
7
8
9
10
//MealBuilderA.hpp
#ifndef MEALBUILDERA_HPP_
#define MEALBUILDERA_HPP_
#include "MealBuilder.hpp"
class MealBuilderA : public MealBuilder {
public:
virtual void buildFood();
virtual void buildDrink();
};
#endif
1
2
3
4
5
//MealBuilderA.cpp
#include "MealBuilderA.hpp"
void MealBuilderA::buildFood() { m_meal->buildFood("套餐A食物"); }
void MealBuilderA::buildDrink() { m_meal->buildDrink("套餐A饮料"); }
1
2
3
4
5
6
7
8
9
10
//MealBuilderB.hpp
#ifndef MEALBUILDERB_HPP_
#define MEALBUILDERB_HPP_
#include "MealBuilder.hpp"
class MealBuilderB : public MealBuilder {
public:
virtual void buildFood();
virtual void buildDrink();
};
#endif
1
2
3
4
5
//MealBuilderB.cpp
#include "MealBuilderB.hpp"
void MealBuilderB::buildFood() { m_meal->buildFood("套餐B食物"); }
void MealBuilderB::buildDrink() { m_meal->buildDrink("套餐B饮料"); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//main.cpp
#include "KFCWaiter.hpp"
#include "MealBuilderA.hpp"
#include "MealBuilderB.hpp"
int main() {
std::shared_ptr<MealBuilder> Aptr =
std::shared_ptr<MealBuilder>(new MealBuilderA());
KFCWaiter k;
k.setMealBuilder(Aptr);
k.construct();
std::shared_ptr<Meal> current_meal = Aptr->getResult();
current_meal->getMeal();
}

Comment and share

UML时序图详解

  这几天在阅读《设计模式》一书,书中总结了前人在面向对象软件设计过程中针对特定的问题提出的简洁而优雅的解决方案。在阅读的过程中,我一边理解书中给出的实例,一边结合自己写过的项目或者用过的框架与当前模式进行对比,发现之前的项目有一些还是有很大的改进空间。在阅读本书的过程中,书中很多例子都是通过UML类图和UML时序图进行说明,所以,理解这些例子的前提是对UML类图以及UML时序图有一定的了解,UML类图在前面的博客(https://langzi989.github.io/2017/01/05/UML%E5%9B%BE%E8%A1%A8%E7%A4%BA%E5%B8%B8%E8%A7%81%E7%9A%84%E7%B1%BB%E4%B9%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB/)中已经讲解,现在我们主要来看一下UML时序图中的重要概念。

  UML类关系以及时序图在大三上《系统分析与设计》课的时候学过,但是那个时候不知道这东西有什么用,所以没有认真学和理解。随着实战经验的增多,越来越能体会到这些在软件设计的过程中重中之重,不管是描述你的设计思想还是与团队中其他人进行交流。所以还是建议大家在学到这一部分的时候对这门课认真对待。

定义

时序图是一种按照时间顺序显示对象之间的交互的图。时序图中显示的是参与交互的对象和对象之间消息传递的顺序。

基本元素

时序图中包含的主要的元素有:

  • 角色(Actor)
  • 对象(Object)
  • 生命线(life line)
  • 控制焦点(focus of control)
  • 消息(message)
  • 自关联消息(self-associated message)
  • 组合片段(combined fragment)

基本元素详解

角色(actor)

角色指的是系统角色,这个角色可以是人,其他系统或者子系统

图形表示:

对象(object)

对象包括三种:

  • 指定类名和对象名的对象objectName: className
  • 指定类名但没有对象名的对象(匿名对象) :className
  • 指定对象名但不指定类名的对象 objectName

图形表示:
第一类:

第二类:

第三类:

生命线(life line)

生命线在时序图中使用向下的虚线表示,它指的是其所对应的对象的存活时间。从上到下表示时间的推后。

图像表示:

控制焦点(focus of control)

控制焦点在时序图中以方块的形式出现,它是一种表示时间段的方式,对象在这一时间段中执行相应的操作。

图像表示:

消息(message)

消息有三种类型

  • 同步消息:发送者发送请求后被阻塞,直到接受者返回消息或者做完其他处理之后发送者才能继续执行之后的操作
  • 异步消息:发送者发送消息给接受者之后不等待接受者返回响应而继续执行下面的操作。
  • 返回消息:接受者返回消息给发送者

消息的图形表示:
同步消息:

异步消息:

返回消息:

自关联消息(self-associated message)

自关联消息一般指的是当前类调用自己的函数的情况

组合片段(combined fragment)

组合片段中包括四个部分:

  • Alternative fragment(alt表示):与if…then…else…对应

  • option fragment(opt表示):与switch对应

  • parallel fragment(par表示):表示同时发生

  • Loop Fragment(loop表示):与循环对应

    图形表示:

    alt:

Comment and share

UML图表示常见的类之间的关系

类之间的关系常见的主要包括以下几种:

  • 继承
  • 实现
  • 依赖
  • 关联
  • 聚合
  • 组合

继承(inheritance)

继承的概念大家都比较熟悉,他主要指的是派生类继承父类或者接口之间的继承,派生类继承了父类中原有的功能,并在此基础上添加了新的功能。例如public继承实质上是一种is-a的关系,(子类is a 基类)

图形表示:

实现(implement)

实现指的是一个类实现一个接口,一个类可以实现一个或者多个接口。实现是类和接口之间常见的一种关系。在java中通常通过implements实现。

图形表示:

依赖

依赖指的是一个类A使用另外一个类B,这种该使用关系是偶然性的,临时的,非常弱的一种关系。但是类B的变化会影响到类A;比如人要使用船过河中,这个时候人和船就是一种依赖关系。具体表现在代码中就是B是类A中的某个方法的参数。

图形表示:

关联

关联是指俩个类之间的或者类和接口之间的一种强依赖关系。而这种强依赖关系不是偶然性的,而是长期的,而且双方的关系是平等的,关联可以是单向的,也可以是双向的。具体表现在代码中是(1):B以类属性的方式出现在类A中(2)A引用类型为B的全局变量

图形表示:

聚合

聚合是关联关系的一种特例,它表现的是一种has-a的关系,即整体与部分的关系,此时整体与部分可以是分离的,他们可以具有各自的声明周期,部分可以属于多个整体对象,也可以被多个整体对象共享;如计算机与CPU的关系聚合中部分和整体都有各自的生命周期,并且互相影响

图形表示:

组合

组合也是关联的一种特例,它体现的是一种contain-a的关系,这种关系比聚合强;这个时候整体和部分是不可分离的,整体的声明周期与部分的声明周期相同,当整体的周期结束时,部分的周期也随之结束。如人和大脑的关系

图形表示:

依赖性强弱:

组合>聚合>关联>依赖

Comment and share

  • page 1 of 1

魏传柳(2824759538@qq.com)

author.bio


Tencent


ShenZhen,China