《C++20设计模式》学习笔记——第1章引论

一、引论

(一)重要概念
(1) 奇异递归模板模式(Curiously Recurring Template Pattern, CRTP)
  1. 继承者将自身作为模板参数传递给基类。
struct Foo: SomeBase<Foo>
{
    //...
};
  1. 目的:可以在基类的实现中访问特定类型的this指针,比如在基类内部将this指针转换成派生类类型。
  2. 用处举例:第2章构造器模式中,2.8节构造器模式的继承性;以及第8章组合模式中,8.3节神经网络。
(2) Mixin继承
  1. 类可以继承它的模板参数;
template <typename T>
struct Mixin : T
{
    //...
};
  1. 目的:允许不同类型的分层组合,派生类可以准确地使用基类的特性;
  2. 用处举例:第9章装饰器模式中,9.3节静态装饰器。
(3)旧风格的静态多态

这种方法存在若干不足之处,暂不做具体介绍。

(4)概念与静态多态

使用c++20中的concept语法,来解决上一小节中存在的不足;可自行查阅相关资料。

(5)属性
  1. 属性只不过是一个(通常为私有的)类成员以及一个getter和setter方法的组合;
  2. C++标准并未将属性纳入其中,但有一个可在大多数编译器中使用的称为property的非标准声明符;
  3. 属性的setter方法会在第20章观察者模式中发挥作用。
(二)SOLID设计原则
(1)单一职责原则
  1. 定义:每个类只有一个职责,因此也只有一个修改该类的原因;
  2. 例子:对于日记“Journal”这个类,其职责是“添加记录”以及“维护日记记录”,而不是将它们写入磁盘。如果将写磁盘的功能添加到Journal类以及其他类似的类中,有关持久化方法的任何改动(例如决定将其写入云端而非磁盘)都需要在受该持久化方法影响的每一个类进行许多小改动,这种情况通常存在代码异味(code smell),表明有些东西不太正确。
    //1.5.1单一职责原则
    struct Journal
    {
        std::string title_;
        std::vector<std::string> entries_;

        explicit Journal(const std::string& title) : title_{title} {}

        void add(const std::string& entry)
        {
            static int count = 1;
            entries_.push_back(std::to_string(count++) + ": " + entry);
        }

        friend std::ostream& operator<<(std::ostream& os, const Journal& obj)
        {
            os << "title: " << obj.title_ << std::endl;
            for(const auto &item : obj.entries_)
            {
                os << item << std::endl;
            }
            return os;
        }

        //存在问题的持久化做法
        // void save(const std::string& filename)
        // {
        //     std::ofstream ofs(filename);
        //     for(auto& s : entries_)
        //     {
        //         ofs << s << std::endl;
        //     }
        // }
    };

    //单独考虑持久化功能,封装在独立的类中
    struct PersistenceManager
    {
        static void save(const Journal& j, const std::string& filemame)
        {
            std::ofstream ofs(filemame);
            for(auto& s : j.entries_)
            {
                ofs << s << std::endl;
            }
        }
    };

现在,如果我们想修改持久化机制,则在PersistenceManager类中更改。
违背单一职责原则的一个极端的反面模式被称为上帝对象(God Object)。指的是承担了尽可能多的职责的庞大的类,是一个极其难对付的庞大的“怪物”!

(2)开闭原则
  1. 定义:要求软件实体对扩展开放,对修改关闭;

  2. 例子:产品(或其他任何对象)过滤器

    1. 对于产品过滤器,从概念上将其划分为两个部分:过滤器(一个以日记的所有记录为输入并返回其中某些记录的处理过程)和规范(应用于数据元素的谓词的定义);
    2. 对规范接口进行非常简单的定义:
     template <typename T>
     struct Specification
     {
         virtual bool is_satisfied(T* item) = 0;
     };
    

    模板中的类型T可以是Product,也可以是其他东西,该方法对所有类型的数据都是可重用的。
    3. 基于Specification<T>定义过滤器:

    template <typename T>
    struct Filter
    {
        virtual vector<T*> filter(vector<T*> items, Specification<T>& spec) const = 0;
    };
    

    以上代码为名为filter的接口指定函数签名,该接口以所有记录和规范作为输入,并且返回符合规范的记录。
    4. 改进后的过滤器的实现显得很简洁:

    struct BetterFilter: Filter<Product>
    {
        vector<Product*> filter(vector<Product*> items, Specification<Product>& spec) const override
        {
            vector<Product*> result;
            for(auto& p : items)
            {
                if(spec.is_satisfied(p))
                    result.push_back(p);
            }
            return result;
        }
    };
    
    1. 要实现颜色过滤器,定义一个ColorSpecification
    struct ColorSpecification : Specification<Product>
    {
        Color color_;
        explicit ColorSpecification(const Color color) : color_{color} {}
        bool is_satisfied(Product* item) override
        {
            return item->color == color_;
        }
    };
    
    1. 按照如下方法给一堆产品做颜色筛选:
    Product app{"Apple", Color::Green, Size::Small};
    Product tree{"Tree", Color::Green, Size::Large};
    Product house{"House", Color::Blue, Size::Large};
    
    vector<Product*> all{&apple, &tree, &house};
    
    BetterFilter bf;
    ColorSpecification green(Color::Green);
    
    auto green_things = bf.filter(all, green);
    for(auto& x : green_things)
        cout << x->name << " is green";
    
    1. 再实现尺寸过滤器,类似地定义一个SizeSpecification
    2. 如果需要实现多条件过滤,只需定义一个规范组合器;例如对于逻辑“与”,可以定义如下:
    template <typename T>
    struct AndSpecification : Specification<T>
    {
        Specification<T>& first_;
        Specification<T>& second_;
        
        AndSpecification(Specification<T>& first, Specification<T>& second)
            : first_{first}, second_{second} {}
            
        bool is_satisfied(T* item) override
        {
            return first_.is_satisfied(item) && second_.is_satisfied(item);
        }
    };
    
    1. 现在可以基于简单的Specification自由地创建组合条件,例如要过滤“绿且大”的产品:
    ColorSpecification green(Color::Green);
    SizeSpecification large(Size::Large);
    AndSpecification<Product> green_and_large{large, green}; //注意这里使用了大括号,也可以使用圆括号;关于c++调用构造函数使用哪种括号,有许多细微的讲究,感兴趣可以查询资料;
    auto big_green_things = bf.filter(all, green_and_large);
    
    1. 总结:开闭原则地主旨是:我们不必返回到已经编写和测试地代码来修改它。我们创建了Specification和Filter,接下来地工作围绕着如何设计新地过滤器接口(不改变已有接口)来展开。
(3)里氏替换原则
  1. 定义:如果某个接口以基类Parent类型地对象为参数,那么它应该同等地接受子类Child类对象作为参数,并且程序不会产生任何异常。
  2. 例子:矩形和正方形的例子;作者认为正方形类根本不应该存在!可以设计一个能创建矩形和正方形的工厂。
(4)接口隔离原则
  1. 定义:将复杂的接口分离为多个单独的接口,以避免强制实现者必须实现某些他们实际上并不需要的接口。
(5)依赖倒转原则
  1. 高层模块不应该依赖低层模块,它们都应该依赖抽象接口;
  2. 抽象接口不应该依赖细节,细节应该依赖抽象接口。依赖接口或基类要优于依赖具体类型。
  3. 一种现代的做法是使用依赖注入:实质是使用一个库(例如Boost.DI)来满足某个特殊组件的依赖关系的需求。

第一章结束,在理解了SOLID设计原则之后,可以开始正式进入设计模式的学习之旅了!

  • 23
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值