home-88必发手机版 18

C++ home-88必发手机版template随笔,template随笔

C++ template随笔,template随笔

话题从重用开始说起:

最基本的重用,重用一个方法,被重用的逻辑被抽取封装成为方法,之后我们把方法当成一种工具来使用(处理数据,输入输出,或者改变状态)。

来到了面向对象的时代,如果这个方法出现父类上面就是继承,如果这个方法出现在其他对象上就是代理,如果子类想要重用父类的接口又不想重用实现那么就是多态。

但是这些重用都是基于相同的数据类型,方法创建出来后接收参数都是固定的类型,对于多态,可以通过子类来实现不同行为,但是方法总归还是接收一个固定父类型(或者接口)参数。

想想有没有这样的需求,两个完全不同的类型,他们之间不存在继承关系,但是却需要同样的处理逻辑,比如说各种类型都需要排序,比如说各种类型都有集合处理的需求。

那么能不能让方法接收不同类型来实现重用呢?C++的Template就是解决这样问题。

 

模板方法

既然这样需求不同类型却需要相同处理方法,所以有了模板方法,

template<typename T>
T Add(T a, T b)
{
    return a + b;
};

int main()
{
    string s1 = "Hello";
    string s2 = "World";
    cout << Add(12,13) << endl;
    cout << Add(s1,s2) << endl;

    return 0;

}; 输出: 25 HelloWorld

 模板类

方法都能模板化,那么类怎么能够不模板化,
作为面向对象的C++,所以有了模板类

把这些模板方法组合起来,再加上模板成员就形成了一个模板类,这些概念和行为和一般的类是一样的。

 

template<typename T>
class Calculator
{
public:
    T m_variable;
    virtual T Add(T a, T b)
    {
        return a + b;
    };

    T Minus(T a, T b)
    {
        return a - b;
    };
};

int main()
{
    Calculator<int> c;
    cout << c.Add(2,3) << endl;
    cout << c.Minus(9,5) << endl;

    string s1 = "Hello";
    string s2 = "World";
    Calculator<string> d;
    cout << d.Add(s1,s2) << endl;
    //cout << d.Minus(s1,s2) << endl;

    return 0

} 

 

想想为什么最后那句注释可以编译过,但是打开那句就编译不过了。

我的理解,当模板类实例化的过程,如果没有用到的方法不会被加入被实例化的模板类中,除非你显示调用了模板类的方法。

模板类继承

既然是类,当然不能少了继承,模板类的继承可以分为

直接从模板类继承

template<typename T>
class SuperCalculator : public Calculator<T>
{
public:
    T m_variable;
    virtual T Add(T a, T b)//多态
    {
        return a + b + b;
    };

    T Multi(T a, T b)//子类
    {
        return a * b;
    };
};


int main()
{
    SuperCalculator<int> sc;
    cout << sc.Add(2,3) << endl;
    cout << sc.Minus(9,5) << endl;
    cout << sc.Multi(9,5) << endl;
    return 0;
};

输出 8,4,45

从具体类继承

class SuperIntCalculator: public Calculator<int>
{
public:
    virtual int Add(int a, int b)
    {
        return a + b + a;
    };

    int Multi(int a, int b)
    {
        return a * b;
    };
};

int main()
{
    SuperIntCalculator sc;
    cout << sc.Add(2,3) << endl;
    cout << sc.Minus(9,5) << endl;
    cout << sc.Multi(9,5) << endl;


    return 0;
};

输出 7, 4, 45

 

模板特例化(偏特化) 

模板类可以通过继承可以在垂直方向变化,但是类型本身也是一个水平的维度,C++为这个维度提供了变化,对于某种具体类的模板类可以拥有特殊的行为,因此我们成为特例化。

template<typename T>
class TClass
{
public:
    void PrintInfo()
    {
        printf("Hello common\n");
    };
};

template<>
class TClass<int>//特例化
{
public:
    void PrintInfo()
    {
        printf("Hello int\n");
    };
};

template<typename T>
TClass<T> * GetTClassObject(T a)
{
    return new TClass<T>;
};


int main()
{
    GetTClassObject("Hello")->PrintInfo();
    GetTClassObject(4.5)->PrintInfo();
    GetTClassObject(5)->PrintInfo();

    return 0;
};

输出
Hello Common
Hello Common
Hello Int

如果一个模板类需要接受两个或者两个以上的类型来实现具体类,当其中一个类型是某个具体类的时候有特殊的行为,那么就成为偏特化。

继承是垂直方向上的特例化,但是特例化不是传统面向对象体系中的概念,可以类比但不要混淆,特例化是是另外一个维度(水平),因此特例化不仅仅可以作用在类上,也能作用在模板方法上。

 

比较

>>模板和模板模式(Template Method Design Pattern)

这个设计模式也是来解决不同类型的却有相同的处理逻辑,这个思想是一致的。但是实现却不同,模板模式由子类去实现模板方法的每一个步骤(或者某个步骤),而且这个模式的假设是所有的类型都是来自于相同的基类,没有解决我们最开始非固定类型。C++模板实现逻辑在类型外面(怎么感觉又有点像策略模式,但是策略模式需要相同的接口或者基类),而类型本身可以针对不同基类的类型。其实对于模板类(方法)而言,他们虽然没有共同的基类,但是再仔细想想,要在一个模板类中类型需要有一个抽象共性,但是这个共性不是以基类形式来表达,比如说排序,那么输入类型必须都要能比较大小,比如说集合,那么输入类型都要有“一个一个”的概念(好像不好理解,vector能处理流体问题吗?)

>>模板和宏

这两个东西很容易放在一起说,比如一个简单模板类可以通过宏来实现没有问题。

但是两者是从不同角度来解决重用问题的,模板是为不同类型提供相同逻辑的重用,是站在类型的角度上看问题。宏是为代码级别的重用,站在少写代码的角度来看问题。

所以他们有重叠的部分。也提供对方不能提供的功能

http://www.bkjia.com/cjjc/1097277.htmlwww.bkjia.comtruehttp://www.bkjia.com/cjjc/1097277.htmlTechArticleC++ template随笔,template随笔
话题从重用开始说起:
最基本的重用,重用一个方法,被重用的逻辑被抽取封装成为方法,之后我们把方法当成…

3. 模板方法

结构

home-88必发手机版 1

1.3 GOF-23 模式分类

  • 从目的来看:

    • 创建型(Creational)模式
      将对象的部分创建工作延迟到子类或者其他对象,从而对应需求变化为对象创建时具体类型实现引来的冲击。
    • 结构型(Structural)模式
      通过类继承或者对象组合的方式来获得更灵活的结构,从而应对需求变化为对象的结构带来的冲击。
    • 行为型(Behavioral)模式
      通过类继承或者对象组合来划分类与对象间的职责,从而应对需求变化为多个交互的对象带来的冲击。
  • 从范围来看

    • 类模式处理与子类的静态关系。
    • 对象模式处理对象间的动态关系。

home-88必发手机版 2

home-88必发手机版 3

结构

home-88必发手机版 4

2. 面向对象设计原则

1.2 什么是设计模式

每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样,你就能一次又一次地使用该方案而不必做重复劳动。
—— Christopher Alexander

一般而言,一个模式有四个基本要素:

  1. 模式名称(pattern name)
    一个助记名,它用一两个词来描述模式的问题、解决方案和效果。
  2. 问题(problem)
    描述了应该在何时使用模式。
  3. 解决方案(solution)
    描述了设计的组成成分,他们之间的相互关系及各自的职责和协作方式。
  4. 效果(consequences)
    描述了模式应用的效果及使用模式应权衡的问题。

home-88必发手机版 5

home-88必发手机版 6

home-88必发手机版 7

软件设计复杂的根本原因:变化
软件设计的目标:复用

要点总结

  • Bridge
    模式使用“对象间的组合关系”解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。所谓抽象和实现沿着各自维度的变化,即“子类化”它们。

  • Bridge
    模式有时候类似于多继承方案,但是多继承方案往往违背单一职责原则(即一个类只有一个变化的原因),复用性比较差。Bridge
    模式是比多继承方案更好的解决方法。

  • Bridge
    模式的应用一般在“两个非常强的变化维度”,有时一个类也有多于两个的变化维度,这时可以使用
    Bridge 的扩展模式。

「组件协作」模式

home-88必发手机版 8

7. 桥模式(Bridge)

将抽象部分(业务功能)与实现部分(平台实现)分离,使它们都可以独立地变化。
——《设计模式》GoF

6. 装饰模式

笔记

  • 编译时复用,运行时利用多态支持变化。

  • 装饰模式把每个要装饰的功能放在单独的类中,并让这个类包装它所要装饰的对象,因此,当需要执行特殊行为时,客户代码就可以在运行时根据需要有选择地、按顺序地使用装饰功能包装对象了。

装饰模式把类中的装饰功能从类中搬移去除,这样可以简化原有的类,有效地把类的核心职责和装饰功能区分开了,而且可以去除相关类中重复的装饰逻辑。

笔记

  • 对象的继承关系是在编译时就定义好了,所以无法在运行时改变从父类继承的实现。子类的实现与它的父类有非常紧密的依赖关系,以至于父类实现中的任何变化必然会导致子类发生变化。当你需要复用子类时,如果继承下来的实现不适合解决新的问题,则父类必须重写或被其他更适合的类替换。这种依赖关系限制了灵活性并最终限制了复用性。

  • 实现系统可能有多角度分类,每一种分类都有可能变化,那么就把这种多角度分离出来让它们独立变化,减少它们之间的耦合。

  • 只要真正深入地理解了设计原则,很多设计模式其实就是原则的应用而已,或许在不知不觉中就在使用设计模式了。

「单一职责」模式

  • 在软件组件的设计中,如果责任划分的不清晰,使用继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码,这时候的关键是划清责任。
  • 典型模式
    • Decorator
    • Bridge

动机

  • 在某些情况下,我们可能会「过度地使用继承来扩展对象的功能」,由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀。

  • 如何使「对象功能的扩展」能够根据需要来动态地实现?同时避免「扩展功能的增多」带来的子类膨胀问题?从而使得任何「功能扩展变化」所导致的影响将为最低?

2.2 将设计原则提升为设计经验

home-88必发手机版 9


要点总结

  • Template Method
    模式是一种非常基础性的设计模式,在面向对象的系统中有着大量的应用。它用最简洁的机制(虚函数的多态性)为很多应用程序的框架提供了灵活的扩展点,是代码复用方面的基本实现结构。

  • 除了可以灵活对应子步骤的变化外,「不要调用我,让我来调用你」的反向控制结构是
    Template Method 的典型应用。

  • 在具体实现方面,被 Template Method
    调用的虚方法可以具有实现,也可以没有任何实现(抽象方法、纯虚方法),一般推荐将他们设置为
    protected 方法。

1. 设计模式简介

要点总结

  • Strategy
    及其子类为组件提供了一系列可重用的算法,从而可以使得类型在运行时方便的根据需要在各个算法之间进行切换。

  • Strategy
    模式提供了用条件判断语句以外的另一种选择,消除条件判断语句,就是在解耦合。含有许多条件判断语句的代码通常都需要
    Strategy
    模式。尤其是条件判断语句在未来会有增加可能性的时候,应该优先考虑
    Strategy 模式。

  • 如果 Strategy 对象没有实例变量,那么各个上下文可以共享同一个
    Strategy 对象,从而节省对象的开销。

5. 观察者模式

笔记

  • 既然用了继承,并且肯定这个继承有意义,就应该要成为子类的模板,所有重复的代码都应该要上升到父类去,而不是让每个子类都去重复。

  • 当我们要完成在某一细节层次一致的一个过程或一系列步骤,但其个别步骤在更详细的层次上实现可能不同时,我们通常考虑用模板方法模式来处理。

  • 模板方法模式是通过把不变行为搬移到基类,去除子类中的重复代码来体现它的优势。相当于提供了一个很好的代码复用平台。

  • 当不变的和可变的行为在方法的子类实现中混合在一起的时候,不变的行为就会在子类中重复出现。我们通过模板方法模式把这些行为搬移到单一的地方,这样就帮助子类摆脱重复的不变行为的纠缠。

  • 对于模板方法来说,有一个前提,就是 Template
    Method()必须要是稳定的。如果 Template
    Method()不稳定,那么没有一个稳定的软件的骨架,就不存在这样一种设计模式。假定,所有方式都是稳定,那么其实也没有必要使用该设计模式。

目录

动机

  • 在软件构建的过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂;而且有时候支持不使用的算法也是一种性能负担.

  • 如何在运行时根据需要透明地更改对象的算法?将算法与对象本身解耦,从而避免上述问题?

1.1 课程目标

  • 理解松耦合设计思想
  • 掌握面向对象设计原则
  • 掌握重构技法改善设计
  • 掌握 GOF 核心设计模式

1. 设计模式简介

1.4 使用设计模式的方法

home-88必发手机版 10

home-88必发手机版 11


笔记

  • 所谓的「复用」指的是二进制层面(编译单位的复用),而不是代码的「复制+粘贴」。

  • 面向对象的编程,并不是类越多越好,类的划分是为了封装,但分类的基础是抽象,具有相同属性和功能的对象的抽象的集合才是类。

  • 策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。

  • 策略模式的 Strategy 类层次为 Context
    定义了一系列的可供重用的算法或行为。继承有助于析取处这些算法中的公共功能。

  • 策略模式的另外一个优点是简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试。

  • 当不同的行为堆砌在一个类中时,就很难避免使用条件语句来选择合适的行为。将这些行为封装在一个个独立的
    Strategy 类中,可以在使用这些行为的类中消除条件语句。

  • 策略模式就是用来封装变化的,所以可以用它来封装几乎任何类型的规则,只要在分析过程中需要在不同时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性。

  • 在基本的策略模式中,选择所用具体实现的职责由客户端对象承担,并转给策略模式的
    Context 对象。

  • 任何需求的变更都是需要成本的。

结构

home-88必发手机版 12

3. 模板方法(template method)

home-88必发手机版 13

home-88必发手机版 14

笔记

  • 多继承:继承一个基类,其它都是接口,比较合理;同时继承多个基类会有耦合问题,不太好。

  • 将一个系统分割成一系列相互协作的类有一个很不好的副作用,那就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便。而观察者模式的关键对象是主题
    Subject 和观察者 Observer,一个 Subject 可以有任意数目的依赖它的
    Observer,一旦 Subject 的状态发生了改变,所有的 Observer
    都可以得到通知。Subject
    发出通知时并不需要知道谁是它的观察者,也就是说,具体观察者是谁,它根本不需要知道。而任何一个具体观察者不知道也不需要知道其他观察者的存在。

4. 策略模式

要点总结

  • 使用面向对象的抽象,Observer
    模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达到松耦合。

  • 目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。

  • 观察者自己决定是否需要订阅通知,目标对象对此一无所知。

  • Observer 模式是基于事件的 UI 框架中非常常用的设计模式,也是
    MVC 模式的一个重要组成部分。


动机

  • 在软件构建的过程中,我们需要为某些对象建立一种「通知依赖关系」——
    一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使得软件不能很好地抵御变化。

  • 使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系,从而实现软件体系结构的松耦合。

6. 装饰模式(Decorator)

动态(组合)地给一个对象增加一些额外的职责。就增加功能而言,Decorator
模式比生成子类(继承)更为灵活(消除重复代码 & 减少子类个数)。
——《设计模式》GoF

要点总结

  • 通过采用组合而非继承的手法,Decorator
    模式实现了在运动时动态扩展对象功能的能力,而且可以根据需要扩展多个功能。避免了使用集成带来的「灵活性差」和「多子类衍生问题」。

  • Decorator 类在接口上变现为 is-a Component 的继承关系,即
    Decorator 类继承了 Component 类所具有的接口。但在实现上又表现为
    has-a Component 的组合关系,即 Decorator 类又使用了另外一个
    Component 类。

  • Decorator 模式的目的并非解决「多子类衍生的多继承」问题,Decorator
    模式应用的要点在于解决「主体类在多个方向上的扩展功能」——是为「装饰」的含义。

动机

  • 由于某些类型的固有的实现逻辑,使得它们具有两个变化的维度,乃至多个维度的变化。

  • 如何应对这种「多维度的变化」?如何利用面向对象技术来使得类型可以轻松地沿着两个乃至多个方向变化,而不用引入额外的复杂度?

结构

home-88必发手机版 15

7. 桥模式


4. 策略模式(strategy)

定义一系列算法,把他们一个个封装起来,并且是他们可以互相替换(变化)。该模式使得算法可独立于使用它的客户程序(稳定的)而变化(扩展,子类化)。
——《设计模式》GoF

5. 观察者模式(Observer)

home-88必发手机版 16

2. 面向对象设计原则

变化复用的天敌!
面向对象设计最大的优势:抵御变化

home-88必发手机版 17

2.1 面向对象的原则

  1. 依赖倒置原则(DIP)

    • 高层模块(稳定的)不应该依赖于低层模块(容易变化的),二者都应该依赖于抽象(稳定的)。
    • 抽象(稳定的)不应该依赖于实现细节(容易变化的),实现细节应该依赖于抽象(稳定的)。
  2. 开放封闭原则(OCP)

    • 对扩展开放,对更改封闭。
    • 类模块应该是可扩展的,但是不可修改。
  3. 单一职责原则(SRP)

    • 一个类应该仅有一个引起它变化的原因。
    • 变化的方向隐含着类的责任。
  4. Liskov 替换原则(LSP)

    • 子类必须能够替换他们的基类(is-a)。
    • 继承表达类型抽象。
  5. 接口隔离原则(ISP)

    • 不应该强迫客户程序依赖他们不用的方法。
    • 接口应该小而完备。
  6. 优先使用对象组合,而不是类继承

    • 类继承通常为「白箱复用」,对象组合通常为「黑箱复用」。
    • 继承在某种程度上破坏了封装性,子类父类耦合度高。
  • 而对象组合则只要求被组合的对象具有良好定义的接口,耦合度低。
  1. 封装变化点

    • 使用封装来创建对象之间的分界层,让设计者可以在分界层的一侧进行修改,而不会对另一侧产生不良的影响,从而实现层次间的松耦合
  2. 针对接口编程,而不是针对实现编程

    • 不将变量类型声明为某个特定的具体类,而是声明为某个接口。
    • 客户程序无需获知对象的具体类型,只需要知道对象所具有的接口。
    • 减少系统中个部分的依赖关系,从而实现「高内聚、松耦合」的类型设计方案。

产业强盛的标志:接口标准化

结构

home-88必发手机版 18