大话设计模式学习笔记C++

一、简单工厂模式

1、面向对象的好处

通过封装、继承、多态把程序的耦合度降低。
用设计模式使得程序更加的灵活,容易修改,并且易于复用。
如实现计算机的功能时,将业务逻辑与界面逻辑分开,让它们的耦合度下降。只有分开,才可以达到容易维护或扩展。

2、紧耦合VS.送耦合

计算器有加、减、乘、除等功能,如果在实现加法功能时,影响到乘法功能,则说明两者的的耦合性高,否则为松耦合。

3、简单工厂模式

封装、继承和多态。
用类的方式实现封装,如加法类、减法类等;用父类、子类的方式实现继承,如运算类的派生类有加法类、减法类等;用虚函数的形式实现多态,如作为父类的运算类,其声明纯虚函数,在加法或减法类中实现该虚函数。额外用一个类(即简单工厂类)来确定实例化对象的类型,该类根据运算符的类型决定生成哪个类对象,根据该类对象在运行时的类型决定调用该虚函数的实现。
缺点:每添加新的运算功能,需要添加新的子类派生自运算类,还应在简单工厂类中添加该类的实例化代码。因此破坏了开放-封闭原则。
在这里插入图片描述

4、UML类图

在这里插入图片描述

(1)依赖关系
如动物依赖于氧气和水。用虚线+箭头,从动物分别指向氧气和水。
(2)合成(组合)关系
是一种强“拥有”关系,体现了严格的部分和整体的关系,部分和整体的生命周期一样。如鸟和其翅膀就是合成(组合关系)。合成关系用实心的棱形+实线箭头来表示。其中的数字"1"和数字“2”称为基数,表明这一端的类可以有几个实例,如鸟有两只翅膀。如果一个类可能有无数个实例,则就用’n’来表示。
(3)聚合关系
聚合表示一种弱“拥有”关系,体现的是A对象可以包含B对象,但B对象不是A对象的一部分。如大雁和雁群这两个类,大雁是群居动物,每只大雁都是属于一个雁群,一个雁群可以有多只大雁,所以它们之间满足聚合关系。聚合关系用空心的棱形+实线箭头来表示。
(4)继承关系
如大雁类继承自动物类。如果是抽象类,就用斜体显示。其中,"+“表示public,”-"表示private,“#”表示protected。
(5)关联关系
当一个类"知道"另一个类时,可以用关联。如企鹅需要知道气候的变化,需要了解气候规律。关联关系用实线箭头来表示。
(6)接口图
如大雁的"飞翔"接口。用虚线表示。

计算器的UML类图如下:
在这里插入图片描述

5、在简单工厂模式下,计算器的实现代码

// SimpleFactoryModel.h文件
#pragma once

// 操作基类
template<typename T>
class COperator
{
   
public:
	virtual T getResult() = 0;
	virtual void setArgs(T lpa, T rpa);
protected:
	T lah, rah;
};

template<typename T>
void COperator<T>::setArgs(T lpa, T rpa)
{
   
	lah = lpa;
	rah = rpa;
}
// 加法类
template<typename T>
class CAddOperator : public COperator<T>
{
   
public:
	virtual T getResult()
	{
   
		return COperator<T>::lah + COperator<T>::rah;
	}
};
// 减法类
template<typename T>
class CSubOperator : public COperator<T>
{
   
public:
	virtual T getResult()
	{
   
		return COperator<T>::lah - COperator<T>::rah;
	}
};
// 乘法类
template<typename T>
class CMulOperator : public COperator<T>
{
   
public:
	virtual T getResult()
	{
   
		return COperator<T>::lah * COperator<T>::rah;
	}
};
// 除法类
template<typename T>
class CDivOperator : public COperator<T>
{
   
public:
	virtual T getResult()
	{
   
		if (0 == COperator<T>::rah)
		{
   
			std::cout << "除数不能为0" << std::endl;
			return 0;
		}
		return COperator<T>::lah / COperator<T>::rah;
	}
};
// 工厂类
template<typename T>
class CCalculatorFactory
{
   
public:
	static COperator<T> * createObject(char c);
};

template<typename T>
COperator<T> * CCalculatorFactory<T>::createObject(char c)
{
   
	COperator<T> * oper;
	switch (c)
	{
   
	case '+':
		oper = new CAddOperator<T>();
		break;
	case '-':
		oper = new CSubOperator<T>();
		break;
	case '*':
		oper = new CMulOperator<T>();
		break;
	case '/':
		oper = new CDivOperator<T>();
		break;
	default:
		oper = new CAddOperator<T>();
		break;
	}
	return oper;
}

测试代码如下:

#include <iostream>
#include "SimpleFactoryModel.h"

int main()
{
   
	using namespace std;
    // 创建对象
	COperator<double> *p = CCalculatorFactory<double>::createObject('/');
	p->setArgs(23.23, 10.74);

	cout << p->getResult() << endl;
	delete p;

	p = CCalculatorFactory<double>::createObject('+');
	p->setArgs(23.23, 10.74);
	cout << p->getResult() << endl;
	delete p;

	p = CCalculatorFactory<double>::createObject('-');
	p->setArgs(23.23, 10.74);
	cout << p->getResult() << endl;
	delete p;

	p = CCalculatorFactory<double>::createObject('*');
	p->setArgs(23.23, 10.74);
	cout << p->getResult() << endl;
	delete p;

	getchar();
	return 0;

	getchar();
	return 0;
}

代码参考博文:
https://blog.csdn.net/konglongdanfo1/article/details/83380031

二、商场模式——策略模式

1、需求—商场收银系统

(1)正常情况,根据单价和数量计算收费;
(2)打折情况,如每件商品在原价的基础上打八折等;
(3)满减情况,如满300减30等。

2、简单工厂的实现

面向对象的百年城,并不是类越多越好,类的划分是为了封装,但分类的基础是抽象,具有相同属性和功能的对象的抽象集合才是类。
在这里插入图片描述
存在的问题:
商场可能经常性地更改打折额度和满减情况,每次维护或扩展收费方式要改动这个工厂,以至代码需要重新编译部署。

3、策略模式

   策略模式定义了算法家族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化,不会影响到使用算法的客户。
   “商场收银时如何促销,用打折还是返利,其实都是一些算法,用工厂来生成算法对象,这没有错,但算法本身只是一种策略,最重要的是这些算法随时都可能相互替换的,这就是变化点,而封装变化点是我们面向对象的一种很重要的思维方式。”

在这里插入图片描述

//抽象算法类
class Strategy
{
   
public:
     virtual void AlgorithmInterface()=0;
}

//各ConcreteStrategy,封装了具体的算法或行为,继承于Strategy
//具体算法A
class ConcreteStrategyA:public Strategy
{
   
public:
   //算法A实现方法
   void AlgorithmInterface()[
      std::cout << "算法A实现" << std::endl;
   }
}
//具体算法B
class ConcreteStrategyB:public Strategy
{
   
public:
   //算法A实现方法
   void AlgorithmInterface()[
      std::cout << "算法B实现" << std::endl;
   }
}
//具体算法C
class ConcreteStrategyC:public Strategy
{
   
public:
   //算法A实现方法
   void AlgorithmInterface()[
      std::cout << "算法C实现" << std::endl;
   }
}

//Context,用一个ConcreteStrategy来配置,维护一个对Strategy对象的引用
class Context
{
   
public:
    Context(Strategy strategy){
   //初始化时,传入具体策略对象
        this->strategy=strategy;
    }
    //上下文接口
    void ContextInterface(){
   //根据具体的策略对象,调用其算法的方法
        strategy.AlgorithmInterface();
    }
private:
    Strategy strategy;
}

客户端代码:

int main()
{
   
    Context* context;
    //由于实例化不同的策略,所以最终在调用context->ContextInterface(),所获结果就不尽相同
    context=new Context(new ConcreteStrategyA());
    context->ContextInterface();
    delete context;
    
    context=new Context(new ConcreteStrategyB());
    context->ContextInterface();    
    delete context;
    
    context=new Context(new ConcreteStrategyC());
    context->ContextInterface();
    delete context;
    
    getchar();
    return 0;
} 

4、策略模式与简单工厂结合

在策略模式下实现的商场收银代码如下:
在这里插入图片描述

//现金收费抽象类
class CashSuper
{
   
public:
    virtual acceptCash(double money)=0;//
}

//正常收费子类
class CashNomal:public CashSuper
{
   
   return money;
}

//打折收费子类
class CashRebate:public CashSuper
{
   
public:
   CashRebate(string moneyRebate)
   {
   
      this->moneyRebate=moneyRebate;
   }
   double acceptCash(double)
   {
   
      return money*moneyRebate;
   }
private:
   double moneyRebate=ld;  
}

//返利收费子类
class CashReturn:public CashSuper
{
   
public:
    CashReturn(string monerCondition,string monerReturn)
    {
   
       this->moneyCondition=monerCondition;
       this->moneyReturn=monerReturn;
    }
    double acceptCash(double money)
    {
   
       double result=monry;
       if(money>=moneyCondition)
           result=money-(money/moneyCondition)*moneyReturn;
       return result;
    }
private:
    double moneyCondition=0.0;
    double moneyReturn=0.0;
}

//CashContext 类
class CrashContext
{
   
public:
     CrashContext(string type)//注意参数不是具体的收费策略对象,而是一个字符串,表示收费类型
     {
   
        //实例化具体策略的过程由客户端转移到Context类中。简单工厂的应用
         switch(type)
         {
   
            case "正常收费":
                CashNormal cs0=new CashNormal();
                cs=cs0;
                break;
            case "满300返100":
               CashReturn cr1=new CrashReturn("300","100");
               cs=cr1;
               break;
            case "打8折":
               CashRebate cr2=new CashRebate("0.8");
               cs=cr2;
               break;
         }
     }
     double GetResult(double money)
     {
   
        return cs.acceptCash(Money);
     }
private:
    CashSuper cs=nullptr;
}

客户端测试代码:

//在简单工厂模式下:
CashSuper csuper=CashFactory::createCashAccept("类型"); 
...=csuper.GetResult(...);
//策略模式与简单工厂模式结合的用法:
CashContext csuper=new CashContext("类型");
...=csuper.GetResult(...);

5、策略模式解析

(1)策略模式的优点
策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,他们可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。
策略模式的Strategy类层次为Context定义了一系列的可供重用的算法或行为。继承有助于析取出这些算法中的公共功能。对于打折、返利或者其他的算法,其实都是对实际商品收费的一种计算方式,通过继承,可以得到它们的公共功能,即获得计算费用的结果GetResult,这使得算法间有了抽象的父类CashSuper。
策略模式简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试。
每个算法可保证它没有错误,修改其中任一个时也不会影响其他的算法。
当不同的行为堆砌在一个类中时,就很难避免使用条件语句来选择合适的行为。将这些行为封装在一个个独立的Strategy类中,可以在使用这些行为的类中消除条件语句。
策略模式是用来封装算法的,但在实践中,我们发现可与用它来封装几乎任何类型的规则,只要在分析过程中听到需要在不同时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性。
在基本的策略模式中,选择所用具体实现的职责由客户对象承担,并转给策略模式的Context对象。这本身并没有解除客户端需要选择判断的压力,而策略模式与简单工厂模式结合后,选择具体实现的职责也可以由Context来承担,这就最大化地减轻了客户端的职责。
(2)缺点
因为在CashContext里还用到了switch,也就是说,如果我们需要增加一种算法,比如"满200送50",就必须更改CashContext中的switch代码。即任何需求的变更都是需要成本的。
反射技术可以解决上述问题。

三、拍摄UFO——单一职责原则

1、单一原则职责

大多数时候,一件产品简单一些,职责单一一些,或许是更好的选择。这就和设计模式中的一大原则——单一职责的道理是一样的。
我们在做编程的时候,很自然就会给一个类加各种各样的功能,比如我们写一个窗体应用程序,一般都会生成一个Formal这样的类,于是我们就把各种各样的功能,像某种商业运算的算法呀,像数据库访问的SQL语句呀,都写到这样的类当中,这就意味着,无论任务需求要来,你都需要更改这个窗体类,维护麻烦,复用不可能,也缺乏灵活性。
单一职责原则(SRP):就一个类而言,应该仅有一个引起它变化的原因。

2、俄罗斯方块游戏的设计

(1)功能需求
首先方块下落动画的原理是画四个小方块,擦掉,然后再在下一行画四个方块。不断地绘出和擦掉就形成了动画,所以应该要有画和擦方块地代码。然后左右键实现左移和右移,下键实现加速,上键实现旋转,这其实都应该是函数,当然左右移动需要考虑碰撞的问题,下移需要考虑堆积和消层的问题。
(2)职责分析
下落、旋转、碰撞判断、移动、堆积这些游戏逻辑,和界面表示没有关系,不应该写在界面类中。
如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个给类完成其他职责的能力。这种耦合会导致脆弱的设计,当变化发生时,设计会遭受意向不到的破坏。
事实上,完全可以找出哪些是界面,哪些是游戏逻辑,然后进行分离。
所谓游戏逻辑,不过就是数组的每一项值变化的问题,下落、旋转、碰撞判断、移动、堆积这些都是在做数组具体项的值的变化。而界面表示逻辑,不过是根据数组的数据进行绘出和擦除,或者根据键盘命令调用数组的相应的方法进行改变。因此,至少应该考虑将此程序分为两个类,一个是游戏逻辑的类,一个是WInForm窗体的类。当有一天改变界面,或者更换界面时,不过是窗体类的变化,和游戏逻辑无关,以此达到复用的目的。
软件设计真正要做的许多内容,就是发现职责并把那些职责相互分离。其实要去判断是否应该分离出类来。也不难,那就是如果你能够想到多余一个的动机去改变一个类,那么这个类就具有多于一个的职责,就应该考虑类的职责分离
界面的变化是和游戏本身没有关系的,界面是容易变化的,而游戏逻辑是不太容易变化的,将它们分开有利于界面的改动。
单一职责的代码易维护、易扩展、易复用、灵活多样。

四、考研求职两不误——开放-封闭原则

1、开放-封闭原则

开放-封闭原则(OCR),是说软件实体(类、模块、函数等等)应该可以扩展,但是不可修改
这个原则有两个特征,一个是说对于“扩展是开放的”,另一个是说对于“更改是封闭的”。
在面对需求的变化时,设计的软件可以相对容易改变,不至于说,新需求一来,就要把整个程序推倒重来。怎样的设计才能面对需求的改变却可以保持相对稳定,从而使得系统可以在第一个版本以后不断推出新的版本呢?

2、如何应对变化

开放-封闭原则的意思就是说,你设计的时候,时刻要考虑,尽量让这个类是足够好,写好了就不要修改了,如果新需求来,我们增加一些类就完事了,原来的代码能不动则不动。
然而,绝对的对修改关闭是不可能的。无论模块多么的“封闭”,都会存在一些无法对之封闭的变化。既然不可能完全封闭,设计人员必须对于他设计的模块应该对哪种变化封闭做出选择。他必须先猜测最有可能发生的变化的中类,然后构造抽象来隔离那些变化。
预测变化往往是困难的,但可以等到变化发生时立即采取行动
在我们最初编写代码时,假设变化不会发生。当发生变化时,我们就创建抽象来隔离以后发生的同类变化。
比如,第一章提到的加法程序,可以很快在一个client类中完成,此时变化还没有发生。然后让你加一个减法功能,你发现,增加功能需要修改原来这个类,这就违背了“开放-封闭原则”,于是你就考虑重构程序,增加一个抽象的运算类,通过一些面向对象的手段,如继承,多态等隔离具体加法、减法与client耦合,需求依然可以满足,还能应对变化。这时如果再加乘除法功能,就不需要再去修改client以及加法减法的类了,而是增加乘法和触发子类就可。即面对需求,对程序的改动是通过增加新代码进行的,而不是更改现有的代码。这就是开放-封闭原则的精神所在。
在这里插入图片描述
我们希望的是在开发工作展开不久就知道可能发生的变化。查明可能发生的变化所等待的时间越长,要创建正确的抽象就越困难。
开放-封闭原则是面向对象设计的核心所在遵循这个原则可以带来面向对象技术所声称的巨大好处,也就是在可维护、可扩展、可复用、灵活性好。开发人员应该对程序中呈现出频繁变化的那些部分做出抽象,然而,对于应用程序中的每个部分都刻意地进行抽象同样不是一个好主意。拒绝不成熟的抽象和抽象本身一样重要。

五、会修电脑不会修收音机?——依赖倒转原则

1、依赖倒转原则

依赖倒转原则,也翻译成依赖倒置原则,原意是抽象不应该依赖细节,细节应该依赖抽象,即针对接口编程,不要对实现编程。无论主板、CPU、内存、硬盘都是针对接口设计的,如果针对实现来设计,内存 就要对应到具体的某个品牌的主板。
概括来说:
高层模块不应该依赖低层模块。两个都应该依赖抽象;抽象不应该依赖细节。细节应该依赖抽象。
在这里插入图片描述

2、里氏代换原则

一个软件实体如果使用的是一个父类的话,那么一定适用其子类,而且它察觉不出父类和子类对象的区别。也就是说,在软件里面,把父类都替换成它的子类,程序的行为没有变化,简单地说,子类型必须能够替换掉它们的父类型。
在这里插入图片描述
只有当子类可以替换掉父类,软件单位的功能不受到影响时,父类才能真正被复用,而子类也能够在父类的基础上增加新的行为。
正是由于子类类型的可替换性才使得使用父类类型的模块在无需修改的情况下就可以扩展
结合里氏代换原则,理解“高层模块不应该依赖低层模块,两个都应该依赖抽象”。
在这里插入图片描述
依赖倒转其实就是谁也不依赖谁,除了约定的接口,大家可以灵活自如。
依赖倒转其实可以说是面向对象设计的标志,用哪种语言来编写程序不重要,如果编写时考虑的都是如何针对抽象编程而不是针对细节编程,即程序中所有的依赖关系都是终止于抽象类或者接口类,俺就是面向对象的设计,反之那就是过程化的设计了

六、穿什么有这么重要?——装饰模式

1、需求

写一个可以给人搭配不同的服饰的系统,比如类似QQ、网络游戏或论坛都有的Avatar系统。
服饰有大T恤、垮裤、破球鞋、西装、领带、皮鞋等。可以任意组合搭配。

2、装饰模式

动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更加灵活
在这里插入图片描述
具体代码如下:

//Component类
class Component
{
   
public:
    virtual void Operation()=0;
}
//ConcreteComponent类
class ConcreteComponent:public Component
{
   
public:
    void Operation(){
   
       cout<<"具体对象的操作"<<endl;
    }
}
//Decorator 类
class Decorator:public Component
{
   
public:
    void SetComponent(Component* component){
   //设置Component
       this->m_component=component;
    }
   void Operation()//重写Operation(),实际执行的是Component的Operation()
   {
   
      if(m_component!=nullptr)
      {
   
         m_component->Operation();
      }
protected:
    Componnent* m_component;    
}
//ConcreteDecoratorA类
class ConcreteDecoratorA:public Decorator
{
   
public:
    virtual void Operation()
    {
   
       m_component->Operation();//首先运行原Componnent的Operation(),再执行本类的功能,                         
                                //如addedState,相当于对原Component进行了装饰
       addedState="New State";
       cout<<"具体装饰对象A的操作"<<endl;
    }
private:
   string addedState;//本类的独有功能,以区别ConcreteDecoratorB
}
//ConcreteDecoratorB类
class ConcreteDecoratorB:public Decorator
{
   
public:
    virtual void Operation()
    {
   
       m_component->Operation();//首先运行原Componnent的Operation(),再执行本类的功能,                         
                                //如addedState,相当于对原Component进行了装饰
       AddedBehavior();
       cout<<"具体装饰对象A的操作"<<endl;
    }
private:
   void AddedBehavior(){
   }//本类的独有方法,以区别ConcreteDecoratorA
}
//测试代码如下
int main()
{
   
    ConcreteComponent c=new ConcreteComponent();
    ConcreteDecoratorA d1=new ConcreteDecoratorA();
    ConcreteDecoratorB d2=new ConcreteDecoratorB();
    d1->SetComponent(c);//装饰的方法是:首先用ConcreteComponent实例化对象c,然后用ConcreteDecoratorA的实例化对象d1来包装c,
    d2->SetComponent(d1);//再用ConcreteDecoratorB的对象d2来包装d1,
    d2->Operation();//最终执行d2的Operation()
    delete c;
    delete d1;
    delete d2;
    getchar();
    return 0;
}

装饰模式是利用SetComponent来对对象进行包装的。这样每个装饰对象的实现就和如何使用这个对象分开了,每个装饰对象只关心自己的功能,不需要关信如何被添加到对象链当中。
注:这里的“实现”是指包装的内容,而“使用”是指包装。

3、装饰模式下的服饰搭配

如果只有一个ConcreteComponent类而没有抽象的Component类,那么Decorator类是可以是ConcreteComponent的一个子类。同样的道理,如果只有一个ConcreteDecorator类,那么就没有必要建立一个单独的Decorator类,而可以把Decorator和ConcreteDecorator的责任合成一个类。
这里没有必要有Component类,直接让服饰类Decorator继承人类ConcreteComponent。
在这里插入图片描述
代码如下:

//Person类(ConcreteComponent)
class Person
{
   
public:
    Person(string name)
    {
   
       this->name=name;
    }
    virtual void Show()
    {
   
        cout<<"装扮的{0}"<<name<<endl;
    }
private:
    string name;
}
//服饰类(Decorator)
class Finery:public Person
{
   
public:
     void Decorate(Person* component)
     {
   
        this->m_component=component;
     }
     virtual void Show()//重写Show(),实际执行的是Component的Show()
     {
   
         if(nullptr!=m_component)
            m_component->Show();
     }
protected:
   Person* m_component;
}
//具体服饰类(ConcreteDecorator)
class TShirts:public Finery
{
   
public:
   void Show()
   {
   
      cout<<"大T恤"<<endl;
     m_component->Show();//运行Person的Show()
   }
}
//具体服饰类(ConcreteDecorator)
class BigTrouser:public Finery
{
   
public:
   void Show()
   {
   
      cout<<"垮裤"<<endl;
     m_component->Show();//首先运行原Person的Show()
   }
}
//其余类似,省略

测试代码如下:

int main()
{
   
   Person xc=new Person("周周");
   cout<<"第一种装扮:"<<endl;
   
   Sneekers pqs=new  Sneekers();
   BigThrouser kk=new BigThrouser();
   TShirts dtx=new TShirts();
   
   pqs->Decorate(xc);
   kk->Decorate(pqs);
   dtx->Decorate(kk);
   dtx->Show();
   
   delete pqs;
   delete kk;
   delete dts;
   
   cout<<"第二种装扮:"<<endl;
   
   LeatherShoes px=new  LeatherShoes();
   Tie ld=new Tie();
   Suit xz=new Suit();
   
   px->Decorate(xc);
   ld->Decorate(px);
   xz->Decorate(ld);
   xz->Show();
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值