zoukankan      html  css  js  c++  java
  • C基础 常用设计模式粗解

    引言

      面向对象, 设计模式是现代软件开发基石. C的面向过程已经很简洁, 但不代表C就没有面向对象.(libuv框架中C面向对象用的很多)

    因为思想是互通的.全当熟悉一下那些常用的设计模式.先假定有一些语法和设计基础.本文会通过C实现下面内容.

      a.封装,继承,多态

      b.单例模式

      c.工厂模式

      d.抽象工厂模式

      e.观察者模式

      f.命令模式

    (分析代码有点多和繁琐, 因为C去搭建, 都是从0到1, 能够复用的东西很少.) 主要在于回顾设计模式的思路.

    先从a.封装,继承,多态开始抛砖引玉. 下面先说 封装

    C面向对象,肯定从struct 上下功夫. 先展示一个 人的设计类

    struct person;
    typedef struct person * person_t;
    
    #define _INT_NAME (64)
    
    struct person {
        long id;
        char name[_INT_NAME];
        char sex;
        int age;
        char * address;
        
        // 说话方式
        void (* speek)(person_t this);
    };
    
    static void _speek_person(person_t this)
    {
        printf("My name is %s, age %d old.
    ", this->name, this->age);
    }
    
    // 具体的new函数
    person_t new_person(long id, const char name[_INT_NAME], char sex, int age, const char * address);
    // 具体的delete函数
    void delete_person(person_t * pthis);

    上面person_t就是我们构建的人的对象类, new_person 是构造函数, delete_person是析构函数. 对于C中对象类

    中方法, 第一参数通用为对象指针. (记得lua实现面向对象也是这么实现的.)  第一个例子说详细些, 完整测试demo 如下

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    struct person;
    typedef struct person * person_t;
    
    #define _INT_NAME (64)
    
    struct person {
        long id;
        char name[_INT_NAME];
        char sex;
        int age;
        char * address;
        
        // 说话方式
        void (* speek)(person_t this);
    };
    
    static void _speek_person(person_t this)
    {
        printf("My name is %s, age %d old.
    ", this->name, this->age);
    }
    
    // 具体的new函数
    person_t new_person(long id, const char name[_INT_NAME], char sex, int age, const char * address);
    // 具体的delete函数
    void delete_person(person_t * pthis);
    
    // 对象执行的方法
    #define OBJECT_CALL(obj, call, ...) obj->##call(obj, ##__VA_ARGS__)
    
    int main(int argc, char * argv[]) {
        
        person_t per = new_person(1, "hello", 0, 25, "东北一家人");
        
        per->speek(per);
        OBJECT_CALL(per, speek);
    
        delete_person(&per);
    
        return 0;
    }
    
    
    // 具体的new函数
    struct person * 
    new_person(long id, const char name[_INT_NAME], char sex, int age, const char * address)
    {
        struct person * per = malloc(sizeof(struct person));
        if(NULL == per) {
            fprintf(stderr, "new_person malloc is error!
    ");
            exit(EXIT_FAILURE);
        }
        per->id = id;
        strncpy(per->name, name, _INT_NAME);
        per->sex = sex;
        per->age = age;
        // strdup 不是标准规定接口, 推荐自己实现, 需要事后free
        per->address = strdup(address);
        per->speek = _speek_person;
        return per;
    }
    
    // 具体的delete函数
    void 
    delete_person(struct person ** pthis) {
        struct person * this;
        if((!pthis) || !(this = *pthis))
            return;
        
        // 释放内部变量
        if(this->address) {
            free(this->address);
            this->address = NULL;
        }
    
        // 释放本身用的变量
        free(this);
        *pthis = NULL;
    }
    View Code

    那我们再说一下继承, 刚才是 person_t 人的类, 现在来了个男人类 需要继承人类, 可以构建为如下

    struct man;
    typedef struct man * man_t;
    
    struct man {
        struct person person;
        double money;
    };
    
    // 具体的new函数
    person_t new_man(long id, const char name[_INT_NAME], char sex, int age, const char * address, double money);
    // 具体的delete函数
    void delete_man(man_t * pthis);

    对于多态, C实现也很容易. 请看下面demo演示 , 先定义一个人类接口说话的行为

    // 人接口有多态行为
    struct iperson {
        void (* speek)(void * this);
    };

    后面定义一个男人类

    // 男人类
    struct man;
    typedef struct man * man_t;
    
    struct man {
        struct iperson ipo;
    
        double money;
    };
    
    static void _speek_man(man_t this) {
        printf("man money = %lf
    ", this->money);
    }
    
    man_t new_man(double money) {
        man_t this = malloc(sizeof(struct man));
        if(NULL == this) {
            fprintf(stderr, "new_man malloc is error!
    ");
            exit(EXIT_FAILURE);
        }
        this->money = money;
        this->ipo.speek = _speek_man;
        return this;
    }
    
    void delete_man(man_t * pthis) {
        man_t this;
        if((!pthis) || !(this = *pthis))
            return;
        free(this);
        *pthis = NULL;
    }

    同样构建个女人类

    // 女人类
    struct woman;
    typedef struct woman * woman_t;
    
    struct woman {
        struct iperson ipo;
    
        double beauty;
    };
    
    static void _speek_woman(woman_t this) {
        printf("woman beauty = %lf
    ", this->beauty);
    }
    
    woman_t new_woman(double beauty) {
        woman_t this = malloc(sizeof(struct woman));
        if(NULL == this) {
            fprintf(stderr, "new_man malloc is error!
    ");
            exit(EXIT_FAILURE);
        }
        this->beauty = beauty;
        this->ipo.speek = _speek_woman;
        return this;
    }

    void delete_woman(woman_t * pthis)
    {
        woman_t this;
        if((!pthis) || !(this = *pthis))
            return;
        free(this);
        *pthis = NULL;
    }

    对于多态行为处理采用统一接口

    // 人接口有多态行为
    struct iperson {
        void (* speek)(void * this);
    };
    
    // 多态行为处理
    void speek_iperson(void * this) {
        struct iperson * iper = this;
        iper->speek(this);
    }

    这里采用的是运行时填充, 运行时确定那个行为被调用. 也用了C中万能类型 void *.  是不是感觉很有意思.

    在我们使用上层语言的面向对象的时候, 也是需要this的, 但是这个this是隐式的, 编译器帮我们处理了, 多数放在寄存器中. 

    编译器能够显示的找到它.  引言部分关于 [a.封装,继承,多态] 讲解就当这里了. 后面会逐个分析, 常用的设计模式. 感受软件设计的套路.

    前言

    b.单例模式

    单例模式在C中用的异常多, 也当初设计的缺陷例如很多 *_r函数就是 对单例模式函数的补丁. (老的单例模式线程不安全, 加了线程安全版).

    先举一个最简单的 单例, 可以说最简单最实用的单例就是 static. 单例是为了内存复用需求产生的.下面就是最简单的单例方式.

    static int _getid(void) {
        static int _id;
    
        return ATOM_ADD_FETCH(_id, 1);
    }

    对于文中用到的原子锁, 参照 C基础 读写锁中级剖析 http://www.cnblogs.com/life2refuel/p/5634658.html

    对于在堆上分配的单例对象 使用方法如下, 同样以上面 man_t 对象举例

    // 单例对象, 在堆区分配
    static man_t _signale_man;
    man_t single_man(void) {
        // 加锁使用, 为了多线程安全
        static int _lock;
    
        if (!_signale_man) {
            SCATOM_LOCK(_lock);
            if (!_signale_man) {
                _signale_man = calloc(1, sizeof(struct man));
                if (!_signale_man) {
                    fprintf(stderr, "single_man calloc is error!
    ");
                    exit(EXIT_FAILURE);
                }
                _signale_man->ipo.speek = _speek_man;
            }
            SCATOM_UNLOCK(_lock);
        }
    
        return _signale_man;
    }

    这个单例对象存在一次内存泄漏, 可以交给操作系统操作. 如果需要精细处理, 那就对 _signal_man 对象进行处理, 最后调用free函数试试. 扯一点,

    有没有发现malloc , calloc, realloc c中调用很繁琐. 下次单独封装一个内存管理使用库. 单例模式就这些内容, 最完美的单例就是静态变量.

     c.工厂模式

     工厂模式在面向对象较大项目中用的场景很多, 事务工厂, 任务工厂, 成就工厂等. 核心思路是按照不同需求生成不同的对象(产品). 生产方法走统一的接口.

    参照下面例子, 家庭会根据不同吃饭类型, 做饭. 是不是觉得工厂模式不过如此. 但是确实很实用.

    // 工厂类型
    enum emeal {
        Meal_Begin,                //开始位置
        Meal_Breakfast,            //早餐
        Meal_Lunch,                //晚餐
        Meal_Dinner,            //中餐
        Meal_Midnightsnack,        //宵夜
        Meal_End                //结束位置    
    };
    
    // 工厂生产的产品
    struct family {
        enum emeal type;
        void (* eat)(struct family * fiy);
    };
    
    // 具体工厂生产方法
    static void _meal_breakfast(struct family * fiy) {
        printf("beign eat breakfast, type = %d.
    ", fiy->type);
    }
    
    static void _meal_midnightsnack(struct family * fiy) {
        printf("beign eat midnightsnack, type = %d.
    ", fiy->type);
    }
    
    // 工厂开始按照需求生产
    struct family * new_meal(enum emeal type)
    {
        struct family * fly;
    
        if(type <= Meal_Begin || type >= Meal_End ) {
            fprintf(stderr, "new_meal type = %d is error!", type);
            exit(EXIT_FAILURE);
        }
        
        if((fly = calloc(1, sizeof(struct family))) == NULL) {
            fprintf(stderr, "new_meal calloc is error!", type);
            exit(EXIT_FAILURE);
        }
        fly->type = type;
    
        switch(type) {
        case Meal_Breakfast:            //早餐
            fly->eat = _meal_breakfast;
            break;
        case Meal_Lunch:                //晚餐
            break;
        case Meal_Dinner:                //中餐
            break;
        case Meal_Midnightsnack:        //宵夜    
            fly->eat = _meal_midnightsnack;
            break;
        }
    
        return fly;
    };

    扯一点C程序设计, C开发用枚举很少, 因为本质就是宏. 当你定义枚举的时候推荐第一个字符为'e', 后面采用头字母大写, 方便和宏区分开来.一看就知道这是枚举''宏''.

    每一分提升都是捉摸滚打, 从错误,感觉不好中优化提升美的意识.

    正文

    d.抽象工厂模式

    抽象工厂模式是对工厂模式的扩展. 工厂创建一种产品,抽象工厂创建的是一组产品.当你发现,有一个接口可以有多种实现的时候,可以考虑使用工厂方法来创建实例.
    当你返现,有一组接口可以有多种实现方案的时候,可以考虑使用抽象工厂创建实例组。对工厂再包装一层, 我们举个例子如下.

    #include <stdio.h>
    #include <stdlib.h>
    
    /*
     * 假定有两家冷饮制作厂, 都有制作冷饮和销售冷饮两个行为
     */
    
    // 制作冷饮的接口
    struct imakecooler {
        void (* make)();
    };
    
    // 销售冷饮的接口
    struct isellcooler {
        void (* sell)();
    };
    
    // 冷饮抽象工厂接口
    struct icooler {
        struct imakecooler * (* makecooler)();        // 得到制作冷饮接口
        struct isellcooler * (* sellcooler)();        // 得到销售冷饮接口
    };
    
    // 第一家冷饮店提供对应制作和销售接口实现
    static void _make_one() {
        puts("第一家冷饮店制作冰淇淋.");
    }
    
    static void _sell_one() {
        puts("第一家冷饮店销售和超市合作.");
    }
    
    // 第二家冷饮店制作和销售接口实现
    static void _make_two() {
        puts("第二家冷饮店制作老北京和大东北.");
    }
    
    static void _sell_two() {
        puts("第二家冷饮店销售是自营.");
    }
    
    // 第一家冷饮店制作和销售接口工厂实现
    static struct imakecooler * _make_one_create() {
        static struct imakecooler imake = { _make_one };
        return &imake;
    }
    
    static struct isellcooler * _sell_one_create() {
        static struct isellcooler isell = { _sell_one };
        return &isell;
    };
    
    // 第二家冷饮店制作和销售接口工厂实现
    static struct imakecooler * _make_two_create() {
        static struct imakecooler imake = { _make_two };
        return &imake;
    }
    
    static struct isellcooler * _sell_two_create() {
        static struct isellcooler isell = { _sell_two };
        return &isell;
    };
    
    // 具体抽象工厂创建
    enum ecooler {
        Cooler_Begin,            // 开始断点
        Cooler_One,                // 第一家冷饮厂
        Cooler_Two,                // 第二家冷饮厂
        Cooler_End                // 结束断点
    };
    
    struct icooler * cooler_create(enum ecooler type) {
        struct icooler * icr;
    
        if(type <= Cooler_Begin || type >= Cooler_End) {
            fprintf(stderr, "cooler_create type = %d is error!", type);
            exit(EXIT_FAILURE);
        }
    
        if((icr = malloc(sizeof(struct icooler))) == NULL) {
            fprintf(stderr, "cooler_create calloc is error!", type);
            exit(EXIT_FAILURE);
        }
    
        switch(type) {
        case Cooler_One:            //第一家冷饮工厂
            icr->makecooler = _make_one_create;
            icr->sellcooler = _sell_one_create;
            break;
        case Cooler_Two:            //第二家冷饮工厂
            icr->makecooler = _make_two_create;
            icr->sellcooler = _sell_two_create;
            break;
        }
    
        return icr;
    }
    
    /*
     * 这里分享抽象工厂例子, 创建使用和销毁
     * 
     */
    int main(int argc, char * argv[]) {
        // 创建抽象工厂并测试
        struct icooler * icr = cooler_create(Cooler_Two);
        icr->makecooler()->make();
        free(icr);
        return 0;
    }

    上面是完整的构建冷饮厂one和two, 并给出真实跑的例子, 还是很有意思的. 喜欢将抽象工厂模式理解为工厂模式的再包装一层. 多个生产工厂.

    e.观察者模式

    对于观察者模式,有时候也叫订阅模式. 等同于你定了小区酸奶,每天都会给你送来.观察者模式开发中还是很常见的, 消息发送, 消息同步.等.

    一般是实现包括, 订阅者, 订阅某个消息. 发布者, 发布消息之后订阅者就能收到通知. 看下面完整验证demo. 本质是

    订阅者 -> 订阅信息放入 订阅链表中

    发布者 -> 发布消息, 订阅链表循环一遍

    #include <stdio.h>
    #include <stdlib.h>
    
    // 注册消息体
    typedef void (* subscribe_f)(const char * str);
    
    // 观察者(订阅者)消息链
    struct observer {
        int id;                        // 唯一观察者id
        subscribe_f subscribe;        // 消息过来,观察者注册的消息回调
    
        struct observer * next;        // 订阅消息链, 下一个节点
    };
    
    // 开始添加注册代码, 实战中 订阅消息体 head 对于观察者是不可见的
    int observer_add(struct observer ** phead, subscribe_f subscribe);
    // 发布者发布消息
    void observer_update(struct observer * head, const char * str);
    // 观察者链销毁
    void observer_delete(struct observer * head);
    
    static void _subsleep(const char * str) {
        printf("等你都等睡着了 -> [%s]
    ", str);
    }
    
    static void _subgame(const char * str) {
        printf("打游戏又来烦我 -> [%s]
    ", str);
    }
    
    /*
     * 观察者模式, 处理
     */
    int main(int argc, char * argv[]) {
        
        struct observer * head = NULL;
        
        // 开始订阅
        observer_add(&head, _subsleep);
        observer_add(&head, _subgame);
    
        // 发布者发布消息
        observer_update(head, "苍老师");
    
        // 释放内存
        observer_delete(head);
        
        getchar();
        return 0;
    }
    
    // 开始添加注册代码, 实战中 订阅消息体 head 对于观察者是不可见的
    int 
    observer_add(struct observer ** phead, subscribe_f subscribe) {
        static int _id;
        struct observer * node = malloc(sizeof(struct observer));
        if(NULL == node) {
            fprintf(stderr, "observer_add malloc is error!");
            exit(EXIT_FAILURE);
        }
        node->id = ++_id;
        node->subscribe = subscribe;
        node->next = *phead;
    
        // 重新构建头指针, 尾查法
        *phead = node;
        return _id;
    }
    
    // 发布者发布消息
    void 
    observer_update(struct observer * head, const char * str) {
        while(head) {
            head->subscribe(str);
            head = head->next;
        }
    }
    
    // 观察者链销毁
    void 
    observer_delete(struct observer * head) {
        while(head) {
            struct observer * next = head->next;;
            free(head);
            head = next;
        }
    }

    f.命令模式

      命令模式在C开发很少见, 在其它语言开发中碰到过几次, 例如在任务系统中, 不同命令功能封装成一个类.命令模式的主要职责是把命令的发布者和

    命令的执行者分离开. 举个例子, 公司董事长想做个新项目, 通知了每个leader, leader知道意思了, 肯定不是自己做, 那就底下的大头兵开始搞.

    这就是发布命令和执行命令分开. 在C中举个简单例子 , 线程池中注册执行线程(发布命令)

    /*
     * 在当前线程池中添加待处理的线程对象.
     * pool        : 线程池对象, sp_new 创建的那个
     * run        : 运行的函数体, 返回值void, 参数void*
     * arg        : 传入运行的参数
     *            : 不需要返回值
     */
    void 
    sp_add(threadpool_t pool, vdel_f run, void* arg)
    {
        struct threadjob* job = _new_threadjob(run, arg);
        pthread_mutex_t* mtx = &pool->mutex;
        
        pthread_mutex_lock(mtx);
        if(!pool->head) //线程池中没有线程头,那就设置线程头
            pool->head = job;
        else
            pool->tail->next = job;
        pool->tail = job;
        
        // 有空闲线程,添加到处理任务队列中,直接返回
        if(pool->idle > 0){
            pthread_mutex_unlock(mtx);
            // 这是一种算法, 先释放锁后发送信号激活线程,速度快,缺点丧失线程执行优先级
            pthread_cond_signal(&pool->threads->cond);
        }
        else if(pool->curr < pool->size){ // 没有那就新建线程, 条件不满足那就等待
            pthread_t tid;
            if(pthread_create(&tid, NULL, (void* (*)(void*))_consumer, pool) == 0)
                ++pool->curr;
            //添加开启线程的信息
            _thread_add(pool, tid);
            pthread_mutex_unlock(mtx);
        }
    }

    但是什么时候开始执行我们不知道. 将命令发布和命令的执行区分开来.  具体可以参看 C 实现有追求的线程池 探究 http://www.cnblogs.com/life2refuel/p/5322567.html

    后记

      到这里C相关设计模式基本就讲解完毕了, 其实C中设计模式将的极少, 最多的还是面向过程(切片). 强调结构和过程!

    设计模式是开发中总结出来的可以复用的套路. 重要的是在于思想. 这里就用C简单模拟了一下. 错误是难免的, 期待更有意思.

  • 相关阅读:
    8086汇编 学习1
    微信小程序 websocket+node.js(ws)
    kali安装google 浏览器
    bugkuctf Easy_Re
    课设记录-Day06
    GitLab使用教程
    课设记录-Day05
    课设记录-Day04
    课设记录-Day03
    课设记录-Day02
  • 原文地址:https://www.cnblogs.com/life2refuel/p/5681788.html
Copyright © 2011-2022 走看看