【软件工程导论】——面向对象与UML(学习笔记)

📖 前言:面向对象是以问题空间中出现的物体为中心进行模型化的一种技术。建立模型是软件工程中最常使用的技术之一。无论软件分析或软件设计,都需要建立模型。UML 就是OO 软件工程使用的统一建模语言。它是一种图形化的语言,主要用图形方式来表示。

在这里插入图片描述


🕒 1. 面向对象概述

类和对象的关系:

  • 对象:代表客观世界中实际或抽象的事物
    • 客观世界是由各种对象组成的
    • 数据以及在其上的操作的封装体
  • 类:一组相似的对象的共性抽象
    • 类是一组客观对象的抽象
    • 实现抽象数据类型的工具
  • 类与对象的关系
    • 抽象与具体的关系
    • 组成类的每个对象都是该类的实例
    • 实例是类的具体事物
    • 类是各个实例的综合抽象

面向对象的基本特征:抽象、封装、继承、多态

面向对象开发的优点:可复用性、可扩展性、可维护性

🕒 2. UML(Unified Modeling Language,统一建模语言)简介

UML:通用的可视化的建模语言

  • 目前在软件工程里主要用于系统分析与系统设计
  • 软件生存周期:RUP(统一过程)
  • 软件建模方式:可视化的语言
  • 软件文档规范:文档由UML建模工具自动产生
  • 软件人员分工:岗位界面逐渐趋向模糊

🕘 2.1 UML的组成

UML由模型元素、扩展机制、图及视图等部分组成:

  • 模型元素是构成图的最基本的元素;
  • 扩展机制用于扩展一个模型元素;
  • 图由模型元素和扩展机制构成;
  • 视图由一个或多个图构成,UML包括5种不同的视图。

UML的模型元素

  • 表示模型中的某个概念
    • 类、对象、构件、用例、结点(node)、接口(interface)、包(package)和注释(note)
  • 表示模型元素之间的关系
    • 关联、泛化、依赖、实现、聚集、组合

在这里插入图片描述

UML的元模型结构:元元模型层、元模型层、模型层、用户模型层
在这里插入图片描述

图:

  • 静态图:用例图、类图、对象图、构件图和部署图
  • 动态图:状态图、时序图、协作图和活动图

视图:

  • 用例视图(用例图 [活动图] )
    • 从用户角度看到的系统应有的外部功能
  • 逻辑视图(静态:类图、对象图;动态:状态图、时序图、协作图、活动图)
    • 描述系统的静态结构和对象间的动态协作关系
  • 进程视图(状态图、时序图、协作图、活动图、构件图、部署图)
    • 展示系统的动态行为及其并发性
  • 构件视图(构件图)
    • 展示系统实现的结构和行为特征
  • 部署视图(部署图)
    • 显示系统的实现环境和构件被部署到物理结构中的映射

展开来说,标准建模语言UML可以由下列5类图(共9种图)来定义:

  • 用例图
    • 用例图:需求捕获,测试依据
  • 静态图(Static diagram):
    • 类图:类以及类之间的相互关系
    • 对象图:对象以及对象之间相互关系
  • 行为图(Behavior diagram):
    • 状态图:类所经历的各种状态
    • 活动图:对工作流建模
  • 交互图(Interactive diagram):
    • 时序图:强调时间顺序的交互图
    • 协作图:强调对象协作的交互图
  • 实现图(Implementation diagram):
    • 构件图:构件及其相互依赖关系
    • 部署图:构件在各节点上的部署

其符号体系如下:

第一类 用例图的组成符号:

可视化图符 名称 描述
在这里插入图片描述 用例 用于表示用例图中的用例,每个用例表示所建模系统的一项外部功能需求,即从用户的角度分析所得的需求。
在这里插入图片描述 参与者 用于描述与系统功能有关的外部实体,它可以是用户,也可以是外部信息系统。
在这里插入图片描述 系统 用于界定系统功能范围。描述该系统功能的用例都置于其中,而描述外部实体的参与者都置于其外,上方标注系统名称。
在这里插入图片描述 关联 连按参与者和用例,表示该参与者所代表的系统外部实体与该用例所描述的系统需求有关。这是参与者和用例的连接。
在这里插入图片描述 包含 由用例A连向用例B,表示用例A包含用例B的行为或功能。
在这里插入图片描述 泛化 由用例A连向用例B,表示用例B描述了一项基本需求。而用例A则描述了该基本需求的特殊情况。
在这里插入图片描述 扩展 由用例连向用例B,表示用例描述了一项基本需求。并声明有扩展点,而用例A则描述了该基本需求在扩展点上的扩展。
在这里插入图片描述 注释体 用于对URL实体进行文字描述。
在这里插入图片描述 注释连接 将注释体与要描述的实体相连。

第二类 静态图——类图和对象图的组成符号:

可视化图符 名称 描述
在这里插入图片描述 第一栏是类名,第二栏是类的属性,第三在是类的操作
在这里插入图片描述 表示类图的集合,是一种分组机制
在这里插入图片描述 关联 表示类的对象之间的关系,其特殊形式是组成关联和聚合关联
在这里插入图片描述 聚合关联 表示类的对象之间是整体和部分之间的关系
在这里插入图片描述 组成关联 表示类的对象的这种关系,整体拥有部分,部分与整体共存,如果整体不存在了,则部分也随之消失
在这里插入图片描述 泛化关系 也称继承关系,定义类和包之间的一般元素和特殊元素之间的分类关系
在这里插入图片描述 依赖关系 有两个类元素A和B,如果修改则可能引起对另一个元素B的修改,则称B依赖A
在这里插入图片描述 对象 对象是类的一 个实例,第一栏表示对象名(带下划线),第二栏表示属性和行为值

第三类 行为图——状态图和活动图中常用到的图形符号:

可视化图符 名称 描述
在这里插入图片描述 初态 用于表示状态图的起始点
在这里插入图片描述 活动状态 在框中标明活动的名称,用于表示内部行动执行的情形
在这里插入图片描述 终态 用于表示状态图的终点
在这里插入图片描述 条件判断 用于表示状态之间的条件分支转移
在这里插入图片描述 并行 用于表示并发状态
在这里插入图片描述 转移 用于说明两个对象之间存在着某种关系,如果满足某个条件且当某个事件发生时,对象的状态发生转移
在这里插入图片描述 送出信号 将对象发出的信号送出到指定对象
在这里插入图片描述 收到信号 接收指定对象发出的信号

第四类 交互图——时序图和协作图中常用到的图形符号:

可视化图符 名称 描述
在这里插入图片描述 对象 用于表示物理设备和在其上运行的软件系统
在这里插入图片描述 生命线条 用于表示可执行的物理代码模块
在这里插入图片描述 同步消息 用于表示嵌套的控制流,发出消息后要等待操作处理完毕,调用者才能继续执行自 己的操作
在这里插入图片描述 异步消息 用于表示异步控制流,当消息发出后不用等待返回即可继续执行自己的操作
在这里插入图片描述 简单消息 用于表示控制如何在对象间进行传递
在这里插入图片描述 关联 表示对象之间的关系

第五类 实现图——构件图和配置图中常用到的图形符号:

可视化图符 名称 描述
在这里插入图片描述 节点 用于表标物理设备和在其上运行的软件系统
在这里插入图片描述 构件 用于表示可执行的物理代码模块
在这里插入图片描述 接口 界面 用于表示对外提供的可视操作和属性
在这里插入图片描述 连接 用于表示节点之间的连接,表示系统之间进行的通信路径

🕘 2.2 UML的特点与应用

特点

  • 统一标准
  • 面向对象
  • 表达能力强大、可视化

应用:用于描述系统开发的不同类型于不同阶段

  • 从需求分析到软件设计到软件测试及维护
  • 可视化问题描述,帮助理解问题
  • 帮助建立各阶段的文档
  • 获取和交流有关应用问题求解的知识
  • 辅助构建系统

🕘 2.3 静态建模

静态建模包括:用例图、类图、对象图

🕤 2.3.1 用例图与用例模型

下图是某保险商务系统的用例图。在这一简化的保险系统中有3个用例:签订保险单、销售统计和客户统计。客户只与用例“签订保险单”有交互,保险销售员则与3个用例都有交互。

在这里插入图片描述

用例之间主要存在两种关系:扩展关系和包含关系

扩展关系:根据指定的条件,一个用例中有可能加入另一个用例的动作

在这里插入图片描述

包含关系:一个用例的行为包含另一个用例的行为
在这里插入图片描述

包含关系和扩展关系的联系和区别:

  • 联系:都是从现有的用例中抽取出公共的那部分信息,作为一个单独的用例,然后通过不同的方法来重用这个公共的用例,以减少模型维护的工作量。
  • 区别:扩展关系中基本用例的基本流执行时,扩展用例不一定执行,即扩展用例只有在基本用例满足某种条件的时候才会执行。

包含关系中基本用例的基本流执行时,包含用例一定会执行。

在这里插入图片描述

🕤 2.3.2 类图和对象图

类图:
在这里插入图片描述
上图表示一个学生可以购买0或多本书,而一本书只能属于一个学生。

类图表示类间关系:

  • 关联关系 (Association)
    • 类之间存在的语义上的关系
    • 普通关联、递归关联、多重关联等
  • 聚集关系(Aggregation)
    • 特殊的关联:整体-部分
  • 组合关系(Composition)
    • 特殊的聚集:整体强烈拥有部分
  • 泛化关系(Generalization)
    • 继承
  • 依赖关系(Dependency)
    • 对一个类/对象的修改会影响另一个类/对象

普通关联:
在这里插入图片描述

上图也是对象图,是类图的实例。

在关联的两端可写上一个被称为重数(multiplicity)的数值范围,表示该类有多少个对象可与对方的一个对象连接。重数的符号表示有:

  • 1 表示1个对象,重数的默认值为1。
  • 0 …1表示0或1。
  • 1 …*表示1或多。
  • 0…*表示0或多,可以简化表示为*。
  • 2…4 表示2~4。

在这里插入图片描述

聚集关系(Aggregation)

特殊的关联:表示类之间具有整体与部分的关系
特征是“部分”对象可以是多个任意“整体”对象的一部分,“部分”可以参与到多个“整体”中,部分可以脱离整体。
在这里插入图片描述

组合关系(Composition)

特殊的聚集:整体强烈拥有部分
在组合中,“整体”强烈拥有“部分”,“部分”与“整体”共存。如果“整体”不存在了,“部分”也会随之消失。“整体”的重数必须是0或1,“部分”的重数可以是任意的。

泛化关系(Generalization)

  • 又称继承
  • 普通泛化,限制泛化
  • 此处的一般元素可视作父类,特殊元素视作子类。
  • 一般元素所具有的关联、属性和操作,特殊元素也都隐含性地拥有;
  • 特殊元素应包含额外信息;
  • 允许使用特殊元素实例的地方,也应能使用一般元素。

在这里插入图片描述

依赖关系(Dependency)

对一个类/对象的修改会影响另一个类/对象
例如,某个类中使用另一个类的对象作为操作中的参数,一个类存取作为全局对象的另一个类的对象,或一个类的对象是另一个类的类操作中的局部变量等,都表示这两个类之间有依赖关系。
在这里插入图片描述

包图:
在这里插入图片描述

🕘 2.4 动态建模

动态建模包括:状态图、时序图、协作图和活动图等

🕤 2.4.1 消息

在这里插入图片描述

🕤 2.4.2 状态图

状态图描述对象的所有可能状态及事件发生时状态的转移条件

在这里插入图片描述
状态图之间发送消息:
在这里插入图片描述

🕤 2.4.3 时序图(顺序图)和协作图(通信图)

时序图(元素:对象、对象生命线、消息)
时序图用来描述对象之间的动态交互,着重体现对象间消息传递的时间顺序。它以垂直轴表示时间,水平轴表示不同的对象。对象用一个带有垂直虚线的矩形框表示,并标有对象名和类名。垂直虚线是对象的生命线,用于表示在某段时间内对象是存在的。对象间的通信在对象的生命线间通过消息符号来表示,消息的箭头指明消息的类型。
在这里插入图片描述

协作图(元素有对象、链接和消息流)
协作图描述了对象间的动态协作关系,但它强调消息发生和接收的对象的结构组织(及连接关系)(协作对象之间的交互和链接)
在这里插入图片描述

🕤 2.4.4 活动图

活动图描述了用例的活动行为以及活动时的约束关系(泳道表明执行的对象)

在这里插入图片描述

🕘 2.5 物理架构建模

逻辑架构和物理架构

构件图:显示软件构件直接的依赖关系。一般来说,软件构件就是一个实际文件,可以是源代码文件、二进制代码文件和可执行文件。

构件图可以用来表现编译、链接或执行时构件之间的依赖关系。
在这里插入图片描述

部署图:描述系统硬件的物理拓扑结构以及在此结构上执行的软件。部署图可以显示计算节点的拓扑结构和通信路径、结点上运行的软件构件、软件构件包含对的逻辑单元(对象、类)等。
在这里插入图片描述

🕒 3. 课后习题

用例(use case):表示参与者与系统的一次交互过程,描述系统的一个功能。用例图被用在需求分析阶段,通过系统外部的参与者与系统之间交互过程的描述,来展现系统的功能。用例之间存在着泛化、包含、扩展关系。

1、根据你的理解,把下面的用例图补充完整。
在这里插入图片描述
解答:
在这里插入图片描述

2、某学校要开发一个网上选课系统。该系统提供以下基本功能:
建立课程:教务人员通过本系统建立课程信息
课程维护:教务人员修改和删除课程信息
安排课程:教务人员安排课程,课程的安排信息包括:
周学时、授课时间、授课老师、教室等信息
调整课程:教务人员对已经安排的课程信息进行调整。
课程浏览:用户可以浏览和查询课程信息
学生选课:学生登陆本系统,选择自己要修的课程。
选课浏览:学生浏览自己选修的课程。
试分析以上问题,并通过用例图描述该系统的功能。

在这里插入图片描述在这里插入图片描述

3、某绘图系统存在Point、Line、Square三种图元,它们具有Shape接口,图元的类图关系如图所示。现要将Circle图元加入此绘图系统以实现功能扩充。已知某第三方库已经提供了XCircle类,且完全满足系统新增的Circle图元所需的功能,但XCircle不是由Shape派生而来,它提供的接口不能被系统直接使用。代码3-1既使用了XCircle又遵循了Shape规定的接口,既避免了从头开发一个新的Circle类,又可以不修改绘图系统中已经定义的接口。代码3-2根据用户指定的参数生成特定的图元实例,并对之进行显示操作。请在代码补全代码空缺处。
绘图系统定义的接口与XCircle提供的显示接口及其功能如下表所示:
在这里插入图片描述

// 代码3-1
class Circle  (1)  {
    private (2) pxc;
    
    public Circle() {
        pxc = new (3);
    }
    
    public void display() {
        pxc.(4);
    }
}
// 代码3-2
public class Factory {
    public (5) getShapeInstance(int type) { // 生成特定类实例
        switch(type) {
            case 0: return new Point();
            case 1: return new Rectangle();
            case 2: return new Line();
            case 3: return new Circle();
            default: return null;
        }
    }
}

public class App {
    public static void main(String argv[]) {
        if (argv.length != 1) {
            System.out.println("error parameters!");
            return;
        }
        
        int type = Integer.parseInt(argv[0]);
        Factory factory = new Factory();
        Shape s;
        s = factory.(6);
        
        if (s == null) {
            System.out.println("Error get instance!");
            return;
        }
        
        s.display();
        return;
    }
}

解答:

// 代码3-1
class Circle implements Shape  {
    private Xcircle pxc;
    
    public Circle() {
        pxc = new Xcircle;
    }
    
    public void display() {
        pxc.displayIt();
    }
}
// 代码3-2
public class Factory {
    public Shape getShapeInstance(int type) { // 生成特定类实例
        switch(type) {
            case 0: return new Point();
            case 1: return new Rectangle();
            case 2: return new Line();
            case 3: return new Circle();
            default: return null;
        }
    }
}

public class App {
    public static void main(String argv[]) {
        if (argv.length != 1) {
            System.out.println("error parameters!");
            return;
        }
        
        int type = Integer.parseInt(argv[0]);
        Factory factory = new Factory();
        Shape s;
        s = factory.getShapeInstance(type);
        
        if (s == null) {
            System.out.println("Error get instance!");
            return;
        }
        
        s.display();
        return;
    }
}

4、在一个订货系统中,采购员从供货商处订货,双方需要签订订单,一个采购员可以订多个供货商的货品,一个供货商也可以给多个采购员供货。分析这个问题,并用类图对这个问题进行建模。

解答:
在这里插入图片描述

5、根据自己的理解,对下图中不合理之处进行修改。
在这里插入图片描述
解答:
在这里插入图片描述

5、传输门是传输系统中的重要装置。传输门具有 Open(打开)、Closed(关闭)、Opening(正在打开)、StayOpen(保持打开)、Closing(正在关闭)五种状态。触发状态的转换事件有 click、complete 和 timeout 三种。事件与其相应的状态转换如图所示。

在这里插入图片描述

下面的【Java 代码 1】与【Java 代码 2】分别用两种不同的设计思路对传输门进行状态模拟,请填补代码中的空缺。

// 代码1
public class Door {
    // 定义状态变量,用不同的整数表示不同状态
    public static final int CLOSED = 1;
    public static final int OPENING = 2;
    public static final int OPEN = 3;
    public static final int CLOSING = 4;
    public static final int STAYOPEN = 5;

    private int state = CLOSED;

    private void setState(int state) {
        this.state = state; // 设置传输门当前状态
    }

    public void getState() {
        // 此处代码省略,本方法输出状态字符串,
        // 例如,当前状态为 CLOSED 时,输出字符串为”CLOSED”
    }

	// 发生 click 事件时进行状态转换
    public void click() { 
        if (1) {
            setState(OPENING);
        } else if (2) {
            setState(CLOSING);
        } else if (3) {
            setState(STAYOPEN);
        }
    }

    // 发生 timeout 事件时进行状态转换
    public void timeout() {
        if (state == OPEN) {
            setState(CLOSING);
        }
    }
	
	// 发生 complete 事件时进行状态转换
    public void complete() { 
        if (state == OPENING) {
            setState(OPEN);
        } else if (state == CLOSING) {
            setState(CLOSED);
        }
    }

    public static void main(String[] args) {
        Door aDoor = new Door();
        aDoor.getState();
        aDoor.click();
        aDoor.getState();
        aDoor.complete();
        aDoor.getState();
        aDoor.click();
        aDoor.getState();
        aDoor.click();
        aDoor.getState();
        return;
    }
}
// 代码2
public class Door {
    public final DoorState CLOSED = new DoorClosed(this);
    public final DoorState OPENING = new DoorOpening(this);
    public final DoorState OPEN = new DoorOpen(this);
    public final DoorState CLOSING = new DoorClosing(this);
    public final DoorState STAYOPEN = new DoorStayOpen(this);

    private DoorState state = CLOSED;

    // 设置传输门当前状态
    public void setState(DoorState state) {
        this.state = state;
    }

    public void getState() { // 根据当前状态输出对应的状态字符串
        System.out.println(state.getClass().getName());
    }

    public void click() { // 发生 click 事件时进行状态转换4;
    }

    public void timeout() { // 发生 timeout 事件时进行状态转换5;
    }

    public void complete() { // 发生 complete 事件时进行状态转换6;
    }

    public static void main(String[] args) {
        Door aDoor = new Door();
        aDoor.getState();
        aDoor.click();
        aDoor.getState();
        aDoor.complete();
        aDoor.getState();
        aDoor.timeout();
        aDoor.getState();
        return;
    }
}

public abstract class DoorState { // 定义所有状态类的基类
    protected Door door;

    public DoorState(Door door) {
        this.door = door;
    }

    public void click() {}
    public void complete() {}
    public void timeout() {}
}

class DoorClosed extends DoorState { // 定义一个基本的 Closed 状态
    public DoorClosed(Door door) {
        super(door);
    }

    public void click() {7;
    }

    // 该类定义的其余代码省略
}

// 其余代码省略

解答:
(1) state == CLOSED || state == CLOSING
(2) state == OPENING || state == STAYOPEN
(3) state == OPEN
(4) state.click()
(5) state.timeout()
(6) state.complete()
(7) door.setState(door.OPENING)

【代码一】没有用状态模式,将所有的状态转换代码都写到Door类中,每一个状态对应一个整型常量(状态变量),当Door的click()等方法被调用时,可以实现相应状态的转换,根据状态图,可以完成对(1)、(2)和(3)空的解答,例如当状态变量state为CLOSED或者CLOSING时,调用click()方法,状态将转换为OPENING。同理,当timeout()方法和complete()方法被调用时,某些状态之间也可以发生转换。

【代码二】使用了状态模式,Door类充当环境类,提供了抽象类DoorState充当抽象状态类,其子类,如DoorClosed、DoorOpening等,充当具体状态类。在环境类Door枚举了所有的状态,提供了一个setState()方法用于设置当前状态,此外,在Door的click()方法中调用状态类的click()方法,在Door的timeout()方法中调用状态类的timeout()方法,在Door的complete()方法中调用状态类的complete()方法。因此,(4)、(5)、(6)空用于实现间接调用。第(7)空用于实现状态的切换,当状态为DoorClosed时,如果调用click()方法,那么Door的当前状态将切换到OPENING,因此在第(7)空,将调用door对象的setState()方法,注入一个DoorOpening类型的对象,由于该对象已存在于Door中,无须再重新创建。在C++中,可以通过door->OPENING来获取,而在Java中则通过door.OPENING来获取。在本试题中,状态的切换由具体状态类来完成,由环境类来维护各个具体状态类的实例。

6、用状态图描述一个请假流程:
少于等于 3 天的请假申请,辅导员审批就可以了,超过 3 天的请假还需系主任审批。

解答:
在这里插入图片描述

7、下图是一个通信图,试把该图转换为顺序图

在这里插入图片描述
解答:
在这里插入图片描述

8、在顺序图中:Kjosk表示信息亭,BoxOffice表示售票中心, CreditCardService表示信用卡服务。
(1)Kjosk类中的操作有哪些?BoxOffice的操作有哪些 ? CreditCardService类的操作有哪些?
(2)根据对象间的消息顺序,描述该顺序图的含义。

在这里插入图片描述

解答:
(1)Kjosk类的操作有showAvailable(seat-list)、DemandPayment(cost)、printtickets(performance,seats)、ejectcard
BoxOffice的操作有Request(count,performance)、SelectSeats、InsertCard(CardNumber)、authorized
CreditCardService的操作有change(cardnumber,cost)
(2)信息亭查询演出的信息
售票中心给出可用的座位表
信息亭选择座位,售票中心给出所需要支付的费用
信息亭插入卡,提供信用卡卡号,进行费用支付
售票中心将信用卡号和费用发送给信用卡服务中心
信用卡服务中心验证有效性,并进行扣款
信息亭确认支付成功后,打印包含演出和座位信息的票,并弹出信用卡。


OK,以上就是本期知识点“面向对象与UML”的知识啦~~ ,感谢友友们的阅读。后续还会继续更新,欢迎持续关注哟📌~
💫如果有错误❌,欢迎批评指正呀👀~让我们一起相互进步🚀
🎉如果觉得收获满满,可以点点赞👍支持一下哟~

❗ 转载请注明出处
作者:HinsCoder
博客链接:🔎 作者博客主页

相关推荐

  1. 【设计模式】面向对象UML

    2024-04-02 11:18:03       12 阅读
  2. 二、UML 类图面向对象设计原则 之 UML概述

    2024-04-02 11:18:03       24 阅读
  3. 学习笔记软件工程概述

    2024-04-02 11:18:03       10 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-04-02 11:18:03       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-02 11:18:03       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-02 11:18:03       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-02 11:18:03       18 阅读

热门阅读

  1. html怎么实现axios发请求,并且实现跨域

    2024-04-02 11:18:03       13 阅读
  2. Python学习之-继承和多态

    2024-04-02 11:18:03       15 阅读
  3. 入门编程第一步,从记住这些单词开始

    2024-04-02 11:18:03       15 阅读
  4. leetcode热题100.数据流的中位数

    2024-04-02 11:18:03       15 阅读
  5. python如何处理文本错误

    2024-04-02 11:18:03       13 阅读
  6. 什么是站群服务器?

    2024-04-02 11:18:03       13 阅读
  7. OMP压缩感知仿真(MATLAB)

    2024-04-02 11:18:03       16 阅读
  8. 导航守卫有哪三种?分别有什么作用

    2024-04-02 11:18:03       15 阅读
  9. 【漏洞复现】金和OA XmlDeal.aspx XXE漏洞

    2024-04-02 11:18:03       13 阅读
  10. 探索Django:打造高效、可扩展的Web应用(上)

    2024-04-02 11:18:03       15 阅读