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.架构搭建
290 0
【畅购商城】需求分析与系统设计及3.架构搭建
|
自然语言处理 数据可视化 架构师
「需求分析」业务架构师需求分析技术权威指南
「需求分析」业务架构师需求分析技术权威指南
|
算法 Java 大数据
电商网站需求分析和架构设计(二)|学习笔记
快速学习电商网站需求分析和架构设计(二)
318 0
电商网站需求分析和架构设计(二)|学习笔记
01Spring Boot2.6实战电商网站需求分析和架构设计
01Spring Boot2.6实战电商网站需求分析和架构设计.ppt
95 0
01Spring Boot2.6实战电商网站需求分析和架构设计
|
2月前
|
Cloud Native Serverless API
微服务架构实战指南:从单体应用到云原生的蜕变之路
🌟蒋星熠Jaxonic,代码为舟的星际旅人。深耕微服务架构,擅以DDD拆分服务、构建高可用通信与治理体系。分享从单体到云原生的实战经验,探索技术演进的无限可能。
微服务架构实战指南:从单体应用到云原生的蜕变之路
|
5月前
|
缓存 Cloud Native Java
Java 面试微服务架构与云原生技术实操内容及核心考点梳理 Java 面试
本内容涵盖Java面试核心技术实操,包括微服务架构(Spring Cloud Alibaba)、响应式编程(WebFlux)、容器化(Docker+K8s)、函数式编程、多级缓存、分库分表、链路追踪(Skywalking)等大厂高频考点,助你系统提升面试能力。
260 0
|
12月前
|
弹性计算 API 持续交付
后端服务架构的微服务化转型
本文旨在探讨后端服务从单体架构向微服务架构转型的过程,分析微服务架构的优势和面临的挑战。文章首先介绍单体架构的局限性,然后详细阐述微服务架构的核心概念及其在现代软件开发中的应用。通过对比两种架构,指出微服务化转型的必要性和实施策略。最后,讨论了微服务架构实施过程中可能遇到的问题及解决方案。
|
Cloud Native Devops 云计算
云计算的未来:云原生架构与微服务的革命####
【10月更文挑战第21天】 随着企业数字化转型的加速,云原生技术正迅速成为IT行业的新宠。本文深入探讨了云原生架构的核心理念、关键技术如容器化和微服务的优势,以及如何通过这些技术实现高效、灵活且可扩展的现代应用开发。我们将揭示云原生如何重塑软件开发流程,提升业务敏捷性,并探索其对企业IT架构的深远影响。 ####
342 3
|
8月前
|
Cloud Native Serverless 流计算
云原生时代的应用架构演进:从微服务到 Serverless 的阿里云实践
云原生技术正重塑企业数字化转型路径。阿里云作为亚太领先云服务商,提供完整云原生产品矩阵:容器服务ACK优化启动速度与镜像分发效率;MSE微服务引擎保障高可用性;ASM服务网格降低资源消耗;函数计算FC突破冷启动瓶颈;SAE重新定义PaaS边界;PolarDB数据库实现存储计算分离;DataWorks简化数据湖构建;Flink实时计算助力风控系统。这些技术已在多行业落地,推动效率提升与商业模式创新,助力企业在数字化浪潮中占据先机。
453 12