C模版方法模式

管理资源(文件、内存)是一件很麻烦的事,因为在分配资源后必须释放资源。例如用fopen打开了文件后必须用fclose关闭文件;使用malloc分配了内存后必须使用free释放掉内存。因此,处理资源的代码总是显得非常繁杂。

一、返回非int 值

   1.file_reader.h

//1
typedef struct FileReaderContext{
    const char * const pFname;
    void(* const processor)(struct FileReaderContext * pThis,FILE * fp);
} FileReaderContext;

//继承 FileReaderContext
typedef struct{
    FileReaderContext base;
    int  result;
} MyFileReaderContext;


int  read_file(FileReaderContext *pCtx);
int  range(const char * pFname);

1.file_reader.c

//
//  file_reader.c
//  TestXianDaiC
//
//  Created by kangxg on 2018/2/7.
//  Copyright © 2018年 zy. All rights reserved.
//

#include "file_reader.h"
#include <limits.h>
static void calc_range(FileReaderContext * p,FILE * fp);

int  read_file(FileReaderContext *pCtx)
{
    fprintf(stderr, "filename'%s\n",pCtx->pFname);
    FILE * fp = fopen(pCtx->pFname, "r");
    if (fp == NULL)
    {
        return -1;
    }
    pCtx->processor(pCtx,fp);
    fclose(fp);
    return 0;
}

int  range(const char * pFname)
{
    MyFileReaderContext ctx = {{pFname,calc_range},0};
    if (read_file(&ctx.base)!=0)
    {
        fprintf(stderr, "Cannot open file'%s,.\n",pFname);
    }
    return ctx.result;
}
static int range_processor(FILE * fp)
{
    int min = INT_MAX;
    int max = INT_MIN;
    char buf[256];
    while ((fgets(buf, sizeof(buf), fp))!=NULL)
    {
        if (buf[0] == '\n')
        {
            return -1;
        }
        int value = atoi(buf);
        min = min>value?value:min;
        max = max>value?value:max;
    }
    return max-min;
}

static void calc_range(FileReaderContext * p,FILE * fp)
{
    MyFileReaderContext * pCtx = (MyFileReaderContext *)p;
    pCtx->result = range_processor(fp);
}

  

   atoi函数

atoi (表示 alphanumeric to integer)是把字符串转换成整型数的一个函数。

函数原型:int atoi(const char *nptr);
参数   nptr为字符串指针

说明:
参数nptr字符串,如果第一个非空格字符存在,是数字或者正负号则开始做类型转换,之后检测到非数字(包括结束符 \0) 字符时停止转换,返回整型数。否则,返回零。
包含在头文件stdlib.h中
实例:
#include <stdlib.h>
#include <stdio.h>
 
int main(void)
{
    int n;
    char *str = "12345.67";
    n = atoi(str);
    printf("int=%d\n",n);
    return0;
}
结果输出:
int=12345

方法说明:

1.定义FileReaderContext 对象,其中有一个向模版方法processor的函数指针

2.然后通过继承,在实际保存的结果对象中增加result成员

3.之后将结果保存在result中

4.最后将result作为range函数的结果返回。


二、处理其他文件资源

不仅仅是文件资源,在使用其他需要分配和释放的资源时,都可以使用模版方法模式。例如如果在运行时才决定需要多少存储空间,如上面代码中将fopen 和fclose替换为 malloc 和free即可。

那么当需要使用多种资源时该如何处理呢?例如将一个文件中的数据读取至内存中,进行排序后再输出至另一个文件中的的情况。当然,也可以编写一个分配和释放三种资源(读取的文件、内存、写入的文件)的函数,但是这样的代码缺少通用性,在其他地方很难服用。而且想要正确地写出这样的函数,本身就并非那么简单。

 
static long file_size(FILE * fp);

int  process_file( const char * pInputFileName,const char * pOutputFileName,void(* sorter) (void * pBuf))
{
    FILE * fpInp = fopen(pInputFileName, "rb");
    if (fpInp == NULL)
    {
        return FILE_OPEN_ERROR;
    }
    long size = file_size(fpInp);
    void * p = malloc(size);
    if (p == NULL)
    {
        return NO_MEMORY_ERROR;//资源泄漏
    }
    
    fread(p, size, 1, fpInp);
    //...
}
这段代码中,程序打开文件后先分配足够容纳下该文件的内存空间,然后读入文件。如果没有足够的内存空间,则返回错误。但是正如注释的那样,如果在这里return的话,就无法关闭文件了,文件会一直处于打开状态。可以预想到的是,这之后还有写入文件的处理,整体代码结构肯定是相当复杂的。

此时,不应当将处理多个资源的代码整合起来,而应当将它们分开进行管理。这样不仅代码会更加简洁,也更容易复用。

整体的处理流程如下:

  • 以只读方式打开要读取的文件
  • 使用fseek、ftell获取该文件的大小
  • 关闭该文件
  • 分配可以容纳该文件的内存空间
  • 以只读方式打开要读取的文件
  • 读取文件
  • 关闭文件
  • 将内存中的内容排序
  • 以可写方式打开要写入的文件
  • 写入该文件
  • 关闭该文件
  • 释放内存


如上图 通过分析可以通过对模版代码整合为一个函数。

1.file.accessor.h

typedef struct FileAccessorContext{
    const char * const pFname;
    const char * const pMode;
    void (* const processor)(struct FileAccessorContext * pThis,FILE * fp);
}FileAccessorContext;

bool access_file(FileAccessorContext * pCtx);


2.file.accessor.c

bool access_file(FileAccessorContext * pCtx)
{
    FILE * fp =fopen(pCtx->pFname, pCtx->pMode);
    if (fp == NULL)
    {
        return false;
    }
    
    pCtx->processor(pCtx,fp);
    fclose(fp);
    return true;
}


3.buffer.h

typedef struct BufferContext{
    void * pBuf;
    size_t size;
    void (* processor)(struct BufferContext * p);
}BufferContext;

bool buffer(BufferContext * pThis);


4.buffer.c

bool buffer(BufferContext * pThis)
{
    pThis->pBuf = malloc(pThis->size);
    if (pThis->pBuf == NULL)
    {
        return false;
    }
    pThis->processor(pThis);
    free(pThis->pBuf);
    return true;
}


5.int_sorter.h

#include <stdio.h>
#include "buffer.h"
typedef enum{
    ERR_CAT_OK = 0,
    ETT_CAT_FILE,
    ETT_CAT_MEMORY
} IntSorterError;

typedef struct{
    const char * const pFname;
    int  errorCategory;
} Context;

typedef struct{
    BufferContext base;
    Context * pAppCtx;
} MybufferContext;

IntSorterError int_sorter(const char * pFname);


6.int_sorter.c

#include "int_sorter.h"
#include <stdbool.h>
#include <stdlib.h>
#include <stddef.h>
#include <errno.h>
#include <string.h>
#include "file_accessor.h"
typedef struct{
    FileAccessorContext base;
    long size;
} SizeGetterContext;

typedef struct{
    FileAccessorContext base;
    MybufferContext * pBufCtx;
} MyFileAccessorContext;

static void size_reader(FileAccessorContext * p,FILE * fp);
static void file_error(Context * pCtx);

static void reader(FileAccessorContext * p,FILE * fp);
static void writer(FileAccessorContext * p,FILE * fp);

int comparator(const void *a, const void *b)
{
    return 0;
}
static void do_with_buffer(BufferContext * p)
{
    MybufferContext * pBufCtx= (MybufferContext *)p;
    MyFileAccessorContext readFileCtx = {{pBufCtx->pAppCtx->pFname,"rb",reader},pBufCtx};
    
    if (!access_file(&readFileCtx.base))
    {
        file_error(pBufCtx->pAppCtx);
        return;
    }
    qsort(p->pBuf, p->size/sizeof(int), sizeof(int), comparator);
    
    MyFileAccessorContext writeFileCtx = {{pBufCtx->pAppCtx->pFname,"wb",writer},pBufCtx};
    if (!access_file(&writeFileCtx.base))
    {
        file_error(pBufCtx->pAppCtx);
        return;
    }
}

static void reader(FileAccessorContext * p,FILE * fp)
{
    MyFileAccessorContext * pFileCtx = (MyFileAccessorContext *)p;
    MybufferContext * pBufCtx = pFileCtx->pBufCtx;
    if (pBufCtx->base.size!=fread(pBufCtx->base.pBuf, 1, pBufCtx->base.size, fp))
    {
        file_error(pBufCtx->pAppCtx);
    }
}

static void writer(FileAccessorContext * p,FILE * fp)
{
    MyFileAccessorContext * pFileCtx = (MyFileAccessorContext *)p;
    MybufferContext * pBufCtx = pFileCtx->pBufCtx;
    if (fwrite(pBufCtx->base.pBuf,1,  pBufCtx->base.size, fp)!=pBufCtx->base.size)
    {
        file_error(pBufCtx->pAppCtx);
    }
}

static long file_size(const char * pFname)
{
    SizeGetterContext ctx = {{pFname,"rb",size_reader},0};
    if (!access_file(&ctx.base))
    {
        return -1;
    }
    
    return ctx.size;
}

static void size_reader(FileAccessorContext * p,FILE * fp)
{
    SizeGetterContext * pThis = (SizeGetterContext*)p;
    pThis->size = -1;
    if (fseek(fp, 0, SEEK_END)==0)
    {
        pThis->size = ftell(fp);
    }
}

IntSorterError int_sorter(const char * pFname)
{
    Context ctx = {pFname,ERR_CAT_OK};
    long size = file_size(pFname);
    if (size == -1)
    {
        file_error(&ctx);
        return ctx.errorCategory;
    }

    if (!buffer(&bufCtx.base))
    {
        ctx.errorCategory = ETT_CAT_MEMORY;
    }
    return ctx.errorCategory;
}

static void file_error(Context * pCtx)
{
    
    fprintf(stderr, "%s:%s\n",pCtx->pFname,strerror(errno));
    pCtx->errorCategory = ETT_CAT_FILE;
}


6 上下文

   通过使用模版方法模式可以方便地改变一连串处理中的某一部分。前面的内容以资源分配于释放处理为例,但是最后的那个改善后的代码可能并非最优解决方案,例如一共有两次打开和关闭文件;首先打开文件计算文件大小,然后关闭文件;然后再次打开文件读取文件内容并关闭文件。


要想解决这个问题。只需要稍微改进下资源的访问方式即可

buffer函数在以函数指针方式调用用户自定义函数(processor的参数)之前分配了内存,在用户自定义函数结束之后释放内存。因此,在调用用户自定义函数前,buffer函数需要知道所分配内存的大小。但是如前所述,应用设计上有一个限制条件。因此,无法事先知道所需分配内存的大小,使得计算文件大小的处理无法从buffer函数中移动至用户自定义函数中。

现在重新修改一下设计,将获得所需内存的大小的处理推迟至调用用户自定义函数后。如下图展示了一种可能的设计流程。“上下文”这个词具有“语境”“环境”的意思,但是在程序中,“场所”这个解释可能更为确切。



根据上面的设计,代码修改如下:

6.1 buffer.h

typedef struct BufferContext{
    void * pBuf;
    size_t size;
    bool(* processor)(struct BufferContext * p);
}BufferContext;


6.2 buffer.c

#include <assert.h>
bool buffer(BufferContext * pThis)
{
    assert(pThis);
    bool ret = pThis->processor(pThis);
    free(pThis->pBuf);
    pThis->pBuf = malloc(pThis->size);
    return ret;
}

void  * allocate_buffer(BufferContext * pThis,size_t size)
{
    assert(pThis);
    assert(pThis->pBuf == NULL);
    pThis->pBuf = malloc(size);
    pThis->size = size;
    return pThis->pBuf;
}

  • buffer 函数不分配内存,直接调用用户自定义函数processor
  • 当用户自定义函数需要内存时,调用负责内存的函数并指定内存大小。
  • 内存分配函数分配指定大小的内存,并将其保存在上下文中返回给用户自定义函数
  • 用户自定义函数结束后,返回到buffer函数访问上下文,如果分配了内存则释放(实际上在初始化BufferContext时,会将pBuf的初始化值设为NULL,请注意free对NULL指针不会做任何处理)


6.3 重写 access_file 函数

file_accessor.h

typedef struct FileAccessorContext{
    FILE  * fp;
    const char * const pFname;
    const char * const pMode;
    bool (* const processor)(struct FileAccessorContext * pThis);
}FileAccessorContext;

bool access_file(FileAccessorContext * pThis);



file_accessor.c

#include <assert.h>
bool access_file(FileAccessorContext * pThis)
{
    assert(pThis);
    bool ret = pThis->processor;
    if (pThis->fp != NULL)
    {
        if (fclose(pThis->fp) != 0)
        {
            ret = false;
        }
    }

    return ret;
}

FILE * get_file_pointer(FileAccessorContext * pThis)
{
    assert(pThis);
    if (pThis->fp == NULL)
    {
        pThis->fp = fopen(pThis->pFname, pThis->pMode);
    }
    
    return pThis->fp;
}

  •  access_file函数与buffer函数一样,并不打开文件,只是调用用户自定义函数(processor)。
  • 文件打开被推迟至processor调用get_file_pointer函数时进行,此外,get_file_pointer 将打开的文件指针保存在上下文中,只要打开一次就可直接从上下文获取文件指针。
  • 当用户自定义函数结束后,access_file函数访问上下文中的文件指针。如果文件被打开了,则调用fclose函数关闭文件。

6.4 修改int_sorter.c

#include "int_sorter.h"
#include <stdbool.h>
#include <stdlib.h>
#include <stddef.h>
#include <errno.h>
#include <string.h>
#include <assert.h>
#include "file_accessor.h"
//1
typedef struct{
    FileAccessorContext base;
    long size;
} SizeGetterContext;

typedef struct{
    FileAccessorContext base;
    MybufferContext * pBufCtx;
} MyFileAccessorContext;

static void size_reader(FileAccessorContext * p,FILE * fp);
static void file_error(Context * pCtx);
static long file_size(FileAccessorContext * pThis);

static bool reader(FileAccessorContext * p);
static bool writer(FileAccessorContext * p);

static long file_current_pos(FileAccessorContext * pFileCtx);
static int set_file_pos(FileAccessorContext * pFileCtx,long offset,int whence);
int comparator(const void *a, const void *b)
{
    return 0;
}

static bool do_with_buffer(BufferContext * p)
{
    //3
    MybufferContext * pBufCtx= (MybufferContext *)p;
    MyFileAccessorContext readFileCtx = {{NULL,pBufCtx->pAppCtx->pFname,"rb",reader},pBufCtx};
    
    //4
    if (!access_file(&readFileCtx.base))
    {
        file_error(pBufCtx->pAppCtx);
        return false;
    }
    //11
    qsort(p->pBuf, p->size/sizeof(int), sizeof(int), comparator);
    
    MyFileAccessorContext writeFileCtx = {{NULL,pBufCtx->pAppCtx->pFname,"wb",writer},pBufCtx};
    if (!access_file(&writeFileCtx.base))
    {
        file_error(pBufCtx->pAppCtx);
        return false;
    }
    
    return true;
}

static bool reader(FileAccessorContext * p)
{
    
    //5
    MyFileAccessorContext * pFileCtx = (MyFileAccessorContext *)p;
    MybufferContext * pBufCtx = pFileCtx->pBufCtx;
    //6
    long size = file_size(p);
    if (size == -1)
    {
        file_error(pBufCtx->pAppCtx);
        return false;
    }
    
    //9
    if (!allocate_buffer(&pBufCtx->base, size))
    {
        pBufCtx->pAppCtx->errorCategory = ETT_CAT_MEMORY;
        return false;
    }
    
    FILE * fp = get_file_pointer(p);
    //10
    if (pBufCtx->base.size != fread(pBufCtx->base.pBuf, 1, pBufCtx->base.size, fp))
    {
        file_error(pBufCtx->pAppCtx);
        return false;
    }
    return true;
}

static bool writer(FileAccessorContext * p)
{
    //12
    MyFileAccessorContext * pFileCtx = (MyFileAccessorContext *)p;
    MybufferContext * pBufCtx = pFileCtx->pBufCtx;
    
    FILE * fp = get_file_pointer(p);
    if (fwrite(pBufCtx->base.pBuf,1,  pBufCtx->base.size, fp)!=pBufCtx->base.size)
    {
        file_error(pBufCtx->pAppCtx);
        return false;
    }
    
    return true;
    
}

static long file_size(FileAccessorContext * pThis)
{
    //7
    long save = file_current_pos(pThis);
    if(save<0)
    {
        return -1;
    }
    if (set_file_pos(pThis,save,SEEK_END)!=0)
    {
        return -1;
    }
    
    long  size = file_current_pos(pThis);
    if (set_file_pos(pThis,save,SEEK_SET)!=0)
    {
        return -1;
    }

    
    return size;
}

static long file_current_pos(FileAccessorContext * pFileCtx)
{
    assert(pFileCtx);
    //8
    FILE * fp = get_file_pointer(pFileCtx);
    if (fp == NULL)
    {
        return -1;
    }
    return ftell(fp);
}

static int set_file_pos(FileAccessorContext * pFileCtx,long offset,int whence)
{
    assert(pFileCtx);
    //8
    FILE * fp = get_file_pointer(pFileCtx);
    if (fp == NULL)
    {
        return -1;
    }
    return fseek(fp, offset, whence);
}
static void size_reader(FileAccessorContext * p,FILE * fp)
{
    SizeGetterContext * pThis = (SizeGetterContext*)p;
    pThis->size = -1;
    if (fseek(fp, 0, SEEK_END)==0)
    {
        pThis->size = ftell(fp);
    }
}
//1
IntSorterError int_sorter(const char * pFname)
{
    Context ctx = {pFname,ERR_CAT_OK};
    
    MybufferContext bufCtx = {{NULL,0,do_with_buffer},&ctx};
    
    buffer(&bufCtx.base);
    
    return ctx.errorCategory;

}

static void file_error(Context * pCtx)
{
    
    fprintf(stderr, "%s:%s\n",pCtx->pFname,strerror(errno));
    pCtx->errorCategory = ETT_CAT_FILE;
}



  • 1 结构体Context是应用程序的上下文。这里保存了应用程序自身的数据。
  • 2 int_sorter函数生成应用程序的上下文并调用buffer函数。由于不需要在调用buffer之前确定所需内存大小,因此这里没有取得文件大小的处理。
  • 3 buffer函数调用用户自定义函数do_with_buffer
  • 4 首先读取文件
  • 5 access_file 函数不打开文件,只生成文件处理的上下文,然后调用处理函数reader
  • 6 迪奥红计算文件大小的函数
  • 7 file_size函数保存了现在文件位置指针在文件中所处的位置后,通过seek到文件末尾即可计算出文件大小,最后返回所保存的位置。
  • 8 file_current_pos、set_file_pos函数负责获取和设置当前文件位置指针在文件中所处的位置。get_file_pointer函数初次被调用时会打开文件,之后只会返回指向已经被打开的文件的指针
  • 9 确定了所需内存大小后分配内存
  • 10 将文件内容读区至内存中。 reader函数处理结束后,access_file函数会关闭文件
  • 11 对文件内容进行排序
  • 最后将排序结果写入文件。写文件和读文件不同,只需简单地将内存中的结果写入文件即可。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值