4. 通讯录实现的需求分析和架构设计

简介: 4. 通讯录实现的需求分析和架构设计

本文实现的是通讯录产品的需求分析和架构设计,重点在于结构层次的设计,方便代码阅读和维护。


一、通讯录实现的需求分析


1、通讯录的功能清单

  1. 添加一个人员
  2. 打印显示所有人员
  3. 删除一个人员
  4. 查找一个人员
  5. 保存文件
  6. 加载文件


2,数据存储信息

  1. 人员存储方式 ——> 双向链表
  2. 文件存储格式 ——> 人员数据的格式
  3. 人员信息 ——> 姓名,电话
    name: xxx,phone: xxx
    name: xxx,phone: xxx


二、通讯录实现的架构设计

1、架构的设计应该从底层往上分析。

  • 支持层:数据链表的存储,以及文件的读写。
  • 接口层:将底层的链表数据进行读取后解析出name和phone(解包),以及读取name和phone后打包写入链表数据中(打包)。另外还有统一的功能接口层,这样即使文件存储方式改变,上层设计仍可以保持不变。
  • 业务层:业务逻辑

具体举个例子:

添加一个用户(功能) —> 输入用户名和电话号码(业务逻辑) —> 通过接口层add —> 插入到链表中


2、代码和难点

2.1代码实现过程中遇到以下几个难点

  1. 二级指针
//插入
/*如果传入struct person *person,一开始person指向的是NULL,那么形参改变不会影响实参。
    参考值传递交换两个数。因此要传入的是二级指针struct person **pperson*/
int person_insert(struct person **pperson,struct person *ps)
//删除
/*如果传入struct person *person,最后person指向的是NULL,那么形参改变不会影响实参。
    参考值传递交换两个数。因此要传入的是二级指针struct person **pperson*/ 
int person_delete(struct person **pperson,struct person *ps)
//加载文件
//若使用struct person *person,刚开始空时候指向的是NULL,无法更改,因此要用二级指针
int load_file(struct person **pperson,int *count,const char *filename)

2.利用状态机读取文件信息存入到通讯录中

//解析文本内的通讯信息
int parser_token(char *buffer,int length,char *name,char *phone)
int load_file(struct person **pperson,int *count,const char *filename)
  1. 文件的操作函数
  2. 链表的插入删除

2.2具体代码如下

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
//为了避免代码突然出现新定义的数字影响阅读,建议都放在宏定义中
#define NAME_LENGTH             16
#define PHONE_LENGTH            32
#define BUFFER_LENGTH           128
#define MIN_TOKEN_LENGTH        5
#define INFO    printf
//''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
//''''''''''''''''''''支持层''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
//do{...}while(0)一般用于宏定义中,只执行依次,并且条件不成立时退出
//宏不止一行,则在结尾加反斜线符号使得多行能连接上
//把item插入到list之前,(list)是因为传入的是*pperson,加括号是让指针优先,即(*pperson)->prev            
#define LIST_INSERT(item,list) do { \
    item->prev = NULL;              \
    item->next = list;              \
    if ((list) != NULL) (list)->prev=item;\
    (list) = item;                    \
}while(0);
#define LIST_REMOVE(item,list) do { \
    if (item->prev != NULL) item->prev->next=item->next;    \
    if (item->next != NULL) item->next->prev=item->prev;    \
    if (list == item) list = item->next;                    \
    item->prev =item->next=NULL;                            \
}while(0)
//person类,包含姓名、电话
struct person
{
    char name[NAME_LENGTH];
    char phone[PHONE_LENGTH];
    struct person *next;
    struct person *prev;
};
//通讯录,里面有person类,总人数
struct contact
{
    struct person *person;
    int count;  //人数
};
enum{
    OPEN_INSERT=1,
    OPEN_PRINT,
    OPEN_DELETE,
    OPEN_SEARCH,
    OPEN_SAVE,
    OPEN_LOAD
};
//''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
//''''''''''''''''''''接口层''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
//插入
int person_insert(struct person **pperson,struct person *ps){ 
    /*如果传入struct person *person,一开始person指向的是NULL,那么形参改变不会影响实参。
    参考值传递交换两个数。因此要传入的是二级指针struct person **pperson
    */
    if ( ps == NULL ) return -1;
    LIST_INSERT(ps,*pperson);
    return 0;
}
//删除
int person_delete(struct person **pperson,struct person *ps){
     /*如果传入struct person *person,最后person指向的是NULL,那么形参改变不会影响实参。
    参考值传递交换两个数。因此要传入的是二级指针struct person **pperson
    */  
    if ( ps == NULL ) return -1;
    LIST_REMOVE(ps,*pperson);
    return 0;
}
//查找
struct person* person_search(struct person *person,const char *name){
    struct person *item = NULL;
    for (item=person;item != NULL ;item=item->next){
        if (!strcmp(item->name , name)){
            break;;
        }
    }
    return item;
}
//遍历
int person_traversal(struct person *person){
    struct person *item = NULL;
    for (item=person;item != NULL ;item=item->next){
        INFO("name: %s,phone: %s\n",item->name,item->phone);
    }   
    return 0;
}
//保存文件
int save_file(struct person *person,const char *filename){
    FILE *fp=fopen(filename,"w");
    if (fp == NULL) return -1;
    struct person *item=NULL;
    for (item=person;item!=NULL;item=item->next){
        fprintf(fp,"name: %s,phone: %s\n",item->name,item->phone);
        fflush(fp);//fprintf是将数据存在缓冲区内,通过fflush刷新到磁盘中
    }
    fclose(fp);
}
//解析文本内的通讯信息
int parser_token(char *buffer,int length,char *name,char *phone){
    if (buffer == NULL ) return -1;
    if (length <MIN_TOKEN_LENGTH) return -2;  //文件结尾默认有一个文件结束标识符,大小不会超过5个字节
    //name: qiuxiang,telephone: 98765678123
    int i=0,j=0,status=0;
    //读取 name: qiuxiang
    for (i=0;buffer[i]!=',';i++){
        if (buffer[i]==' '){
            status=1;
        }
        else if(status==1){
            //将buffer[i]赋值给name[j],而后j++
            name[j++]=buffer[i];
        }
    }
    //读取 telephone: 98765678123
    status=0;
    j=0;
    for (;i<length;i++){
         if (buffer[i]==' '){
            status=1;
        }
        else if(status==1){
            //将buffer[i]赋值给name[j],而后j++
            phone[j++]=buffer[i];
        }       
    }
    INFO("file token : %s --> %s\n", name, phone);
    return 0;
}
//加载文件
//若使用struct person *person,刚开始空时候指向的是NULL,无法更改,因此要用二级指针
int load_file(struct person **pperson,int *count,const char *filename){
    FILE *fp=fopen(filename,"r");
    if (fp == NULL ) return -1;
    //feof():侦测是否读取到了文件尾,如果已到文件尾则返回非零值,其他情况返回 0
    while (!feof(fp)){
        char buffer[BUFFER_LENGTH]={0};
        //fgets(str,n,fp):从 fp 所指文件中读入 n-1 个字符放入 str 为起始地址的空间内;如果在未读满 n-1 个字符时,则遇到换行符或一个 EOF 结束本次读操作,并已 str 作为函数值返回.
        fgets(buffer,BUFFER_LENGTH,fp);
        int length=strlen(buffer);
        INFO("legth :%d\n",length);
        //name: qiuxiang,telephone: 98765678123
        char name[NAME_LENGTH]={0};
        char phone[PHONE_LENGTH]={0};
        if (0 != parser_token(buffer,length,name,phone)){
            continue;
        }
        struct person *p=(struct person*)malloc(sizeof(struct person));
        if (p == NULL) return -2;
        //void *memcpy(void *str1, const void *str2, size_t n) 从存储区 str2 复制 n 个字节到存储区 str1
        memcpy(p->name,name,NAME_LENGTH);
        memcpy(p->phone,phone,PHONE_LENGTH);
        person_insert(pperson,p);
        (*count)++;
    }
    fclose(fp);
    return 0;
}
//''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
//''''''''''''''''''''业务逻辑层''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
int insert_entry(struct contact *cts){
    if (cts ==NULL) return -1;  //输入空信息错误
    struct person *p=(struct person*)malloc(sizeof(struct person));
    if (p == NULL) return -2;  //内存分配错误
    //name
    INFO ("Please Input Name:\n");
    scanf("%s",p->name);
    //phone
    INFO ("Please Input Phone:\n");
    scanf("%s",p->phone);
    //add to person
    if (0 !=person_insert(&cts->person,p)){
        free(p);
        return -3;
    }
    cts->count++;
    INFO("Insert Success\n");
    return 0;
}
int print_entry(struct contact *cts){
    //打印通讯录中的信息
    if (cts == NULL ) return -1;
    person_traversal(cts->person);
}
int delete_entry(struct contact *cts){
    //删除通讯录中某个人的信息
    if (cts == NULL ) return -1;
    INFO("Please Input Name:\n");
    char name[NAME_LENGTH] ={0};
    scanf("%s",name);
    //判断输入的name是否存在通讯录中
    struct person *ps=person_search(cts->person,name);
    if (ps == NULL) {
        INFO("Person don't Exit\n");
        return -2;
    }
    //删除
    person_delete(&cts->person,ps);
    free(ps);
    return 0;
}
int search_entry(struct contact *cts){
    //查找通讯录中某个人的信息
    if (cts == NULL ) return -1;
    INFO("Please Input Name:\n");
    char name[NAME_LENGTH] ={0};
    scanf("%s",name);
    //判断输入的name是否存在通讯录中
    struct person *ps=person_search(cts->person,name);
    if (ps == NULL) {
        INFO("Person don't Exit\n");
        return -2;
    }
    INFO("name: %s, phone: %s",ps->name,ps->phone);
    return 0;
}
int save_entry(struct contact *cts){
    //将通讯录保存
    if (cts==NULL) return -1;
    INFO("Please Input Save Filename:\n");
    char filename[NAME_LENGTH]={0};
    scanf("%s",filename);
    save_file(cts->person,filename);
}
int load_entry(struct contact *cts){
    //加载文件中的通讯录
    if(cts==NULL)  return -1;
    INFO("Please Input Load Filename:\n");
    char filename[NAME_LENGTH]={0};
    scanf("%s",filename);
    load_file(&cts->person,&cts->count,filename);
}
void menu_info() {
  INFO("\n\n********************************************************\n");
  INFO("***** 1. Add Person\t\t2. Print People ********\n");
  INFO("***** 3. Del Person\t\t4. Search Person *******\n");
  INFO("***** 5. Save People\t\t6. Load People *********\n");
  INFO("***** Other Key for Exiting Program ********************\n");
  INFO("********************************************************\n\n");
}
int main(){
    struct contact *cts =(struct contact *)malloc(sizeof(struct contact));
    if(cts == NULL) return -1;
    memset(cts,0,sizeof(struct contact));  //初始化cts为0
    while(1){
        menu_info();
        int select=0;
        scanf("%d",&select);
        switch (select)
        {
            case OPEN_INSERT:
                insert_entry(cts);
                break;
            case OPEN_PRINT:
                print_entry(cts);
                break;
            case OPEN_DELETE:
                delete_entry(cts);
                break;
            case OPEN_SEARCH:
                search_entry(cts);
                break;
            case OPEN_SAVE:
                save_entry(cts);
                break;
            case OPEN_LOAD:
                load_entry(cts);
                break;
            default:
                goto exit; 
        }
    }
    exit:
        return 0;
        free(cts);
}


目录
相关文章
|
自然语言处理 数据可视化 架构师
「需求分析」业务架构师需求分析技术权威指南
「需求分析」业务架构师需求分析技术权威指南
|
供应链 搜索推荐 双11
【畅购商城】需求分析与系统设计及3.架构搭建
【畅购商城】需求分析与系统设计及3.架构搭建
175 0
【畅购商城】需求分析与系统设计及3.架构搭建
|
算法 Java 大数据
电商网站需求分析和架构设计(二)|学习笔记
快速学习电商网站需求分析和架构设计(二)
191 0
电商网站需求分析和架构设计(二)|学习笔记
01Spring Boot2.6实战电商网站需求分析和架构设计
01Spring Boot2.6实战电商网站需求分析和架构设计.ppt
59 0
01Spring Boot2.6实战电商网站需求分析和架构设计
|
7天前
|
缓存 负载均衡 JavaScript
探索微服务架构下的API网关模式
【10月更文挑战第37天】在微服务架构的海洋中,API网关犹如一座灯塔,指引着服务的航向。它不仅是客户端请求的集散地,更是后端微服务的守门人。本文将深入探讨API网关的设计哲学、核心功能以及它在微服务生态中扮演的角色,同时通过实际代码示例,揭示如何实现一个高效、可靠的API网关。
|
5天前
|
Cloud Native 安全 数据安全/隐私保护
云原生架构下的微服务治理与挑战####
随着云计算技术的飞速发展,云原生架构以其高效、灵活、可扩展的特性成为现代企业IT架构的首选。本文聚焦于云原生环境下的微服务治理问题,探讨其在促进业务敏捷性的同时所面临的挑战及应对策略。通过分析微服务拆分、服务间通信、故障隔离与恢复等关键环节,本文旨在为读者提供一个关于如何在云原生环境中有效实施微服务治理的全面视角,助力企业在数字化转型的道路上稳健前行。 ####
|
6天前
|
Dubbo Java 应用服务中间件
服务架构的演进:从单体到微服务的探索之旅
随着企业业务的不断拓展和复杂度的提升,对软件系统架构的要求也日益严苛。传统的架构模式在应对现代业务场景时逐渐暴露出诸多局限性,于是服务架构开启了持续演变之路。从单体架构的简易便捷,到分布式架构的模块化解耦,再到微服务架构的精细化管理,企业对技术的选择变得至关重要,尤其是 Spring Cloud 和 Dubbo 等微服务技术的对比和应用,直接影响着项目的成败。 本篇文章会从服务架构的演进开始分析,探索从单体项目到微服务项目的演变过程。然后也会对目前常见的微服务技术进行对比,找到目前市面上所常用的技术给大家进行讲解。
17 1
服务架构的演进:从单体到微服务的探索之旅
|
4天前
|
Cloud Native 安全 API
云原生架构下的微服务治理策略与实践####
—透过云原生的棱镜,探索微服务架构下的挑战与应对之道 本文旨在探讨云原生环境下,微服务架构所面临的关键挑战及有效的治理策略。随着云计算技术的深入发展,越来越多的企业选择采用云原生架构来构建和部署其应用程序,以期获得更高的灵活性、可扩展性和效率。然而,微服务架构的复杂性也带来了服务发现、负载均衡、故障恢复等一系列治理难题。本文将深入分析这些问题,并提出一套基于云原生技术栈的微服务治理框架,包括服务网格的应用、API网关的集成、以及动态配置管理等关键方面,旨在为企业实现高效、稳定的微服务架构提供参考路径。 ####
24 5
|
7天前
|
监控 API 微服务
后端技术演进:从单体架构到微服务的转变
随着互联网应用的快速增长和用户需求的不断演化,传统单体架构已难以满足现代软件开发的需求。本文深入探讨了后端技术在面对复杂系统挑战时的演进路径,重点分析了从单体架构向微服务架构转变的过程、原因及优势。通过对比分析,揭示了微服务架构如何提高系统的可扩展性、灵活性和维护效率,同时指出了实施微服务时面临的挑战和最佳实践。
25 7
|
6天前
|
Kubernetes 负载均衡 Cloud Native
云原生架构下的微服务治理策略
随着云原生技术的不断成熟,微服务架构已成为现代应用开发的主流选择。本文探讨了在云原生环境下实施微服务治理的策略和方法,重点分析了服务发现、负载均衡、故障恢复和配置管理等关键技术点,以及如何利用Kubernetes等容器编排工具来优化微服务的部署和管理。文章旨在为开发者提供一套实用的微服务治理框架,帮助其在复杂的云环境中构建高效、可靠的分布式系统。
19 5