2020-04-26学习日记(三) 设计模式

参考文章

一、设计模式的分类

创建型模式(五种):

工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

结构型模式(七种):

适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

行为型模式(十一种):

共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

二、设计模式的六大原则

总原则:开闭原则

开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,而是要扩展原有代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类等,后面的具体设计中我们会提到这点。

1、单一职责原则

不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,如若不然,就应该把类拆分。

2、里氏替换原则(Liskov Substitution Principle)

里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科
里氏替换原则中,子类对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。

3、依赖倒转原则(Dependence Inversion Principle)

这个是开闭原则的基础,具体内容:面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。

4、接口隔离原则(Interface Segregation Principle)

这个原则的意思是:每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。

5、迪米特法则(最少知道原则)(Demeter Principle)

就是说:一个类对自己依赖的类知道的越少越好。也就是说无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。
最少知道原则的另一个表达方式是:只与直接的朋友通信。类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。我们要求陌生的类不要作为局部变量出现在类中

6、合成复用原则(Composite Reuse Principle)

原则是尽量首先使用合成/聚合的方式,而不是使用继承。

三、设计模式

1. 工厂模式

工厂模式属于创建型模式,大致可以分为三类,简单工厂模式工厂方法模式抽象工厂模式。听上去差不多,都是工厂模式。下面一个个介绍,首先介绍简单工厂模式,它的主要特点是需要在工厂类中做判断,从而创造相应的产品。当增加新的产品时,就需要修改工厂类。有点抽象,举个例子就明白了。

1.1 简单工厂模式:

有一家生产处理器的厂家,它只有一个工厂,能够生产两种型号的处理器。客户需要什么样的处理器,一定要显示地告诉生产工厂。下面给出一种实现方案。

enum CTYPE {i5, i7};    
//CPU基类
class CPU    
{    
public:    
    virtual void Show() = 0;  
};    
//CPU_i5 子类    
class CPU_i5: public CPU    
{    
public:    
    void Show() { cout<<"CPU_i5"<<endl; }    
};    
//CPU_i7 子类    
class CPU_i7: public CPU    
{    
public:    
    void Show() { cout<<"CPU_i7"<<endl; }    
};    
//唯一的工厂,可以生产两种型号的处理器,在工厂内部做判断    
class Factory    
{    
public:     
    CPU* CreateCPU(enum CTYPE ctype)    
    {    
        if(ctype == i5) //工厂内部判断    
            return new CPU_i5(); //生产i5    
        else if(ctype == i7)    
            return new CPU_i7(); //生产i7    
        else    
            return NULL;    
    }    
};    

int main()
{
    Factory* factory = new Factory;
    CPU* cpu_i5 = factory->CreateCPU( i5);
    cpu_i5->Show();

    system("pause");
    return 0;
}

这样设计的主要缺点之前也提到过,就是要增加新的核类型时,就需要修改工厂类。这就违反了开放封闭原则:软件实体(类、模块、函数)可以扩展,但是不可修改。于是,工厂方法模式出现了。

1.2 工厂方法模式:

所谓工厂方法模式,是指定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类。
听起来很抽象,还是以刚才的例子解释。Intel赚了不少钱,于是决定将i5和i7的生产分开到两个不同的工厂。这时,客户要做的是找好工厂,比如要i5,就找i5的工厂;否则找i7的工厂要,不再需要告诉工厂具体要什么型号的处理器了。下面给出一个实现方案。

//CPU基类
class CPU    
{    
public:    
    virtual void Show() = 0;  
};    
//CPU_i5 子类    
class CPU_i5: public CPU    
{    
public:    
    void Show() { cout<<"CPU_i5"<<endl; }    
};    
//CPU_i7 子类    
class CPU_i7: public CPU    
{    
public:    
    void Show() { cout<<"CPU_i7"<<endl; }    
};    
//总部工厂 
class Factory    
{    
public:     
    virtual CPU* CreateCPU() = 0;    
}; 
//生产i5的工厂
class i5Factory : public Factory
{
public:
    virtual CPU* CreateCPU()
    {
        return new CPU_i5;
    }
}  ;
//生产i7的工厂
class i7Factory : public Factory
{
public:
    virtual CPU* CreateCPU()
    {
        return new CPU_i7;
    }
};

int main()
{
    //实例化一个工厂的子类对象
    Factory* i7factory = new  i7Factory;
    CPU* i7 = i7factory->createCPU();
    i7->Show();

    system("pause");
    return 0;
}    

工厂方法模式也有缺点,每增加一种产品,就需要增加一个对象的工厂。如果这家公司发展迅速,推出了很多新的处理器,那么就要开设相应的新工厂。在C++实现中,就是要定义一个个的工厂类。显然,相比简单工厂模式,工厂方法模式需要更多的类定义。

1.3 抽象工厂模式:

既然有了简单工厂模式和工厂方法模式,为什么还要有抽象工厂模式呢?它到底有什么作用呢?还是举这个例子,这家公司的技术不断进步,不仅可以生产CPU,也能生产GPU。现在简单工厂模式和工厂方法模式都鞭长莫及。抽象工厂模式登场了。它的定义为提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。具体这样应用,这家公司还是开设两个工厂,一个专门用来生产i5型号的CPU和GPU,而另一个工厂专门用来生产i7型号的CPU和GPU,下面给出实现的代码。

//CPU    
class CPU     
{    
public:    
    virtual void Show() = 0;  
};    
class CPU_i5: public CPU      
{    
public:    
    void Show() { cout<<"CPU_i5"<<endl; }    
};    
class CPU_i7 :public CPU    
{    
public:    
    void Show() { cout<<"CPU_i7"<<endl; }    
};    
//GPU    
class GPU      
{    
public:    
    virtual void Show() = 0;  
};    
class GPU_i5 : public GPU      
{    
public:    
    void Show() { cout<<"GPU_i5"<<endl; }    
    
};    
class GPU_i7 : public GPU
{    
public:    
    void Show() { cout<<"GPU_i7"<<endl; }    
};    
//工厂    
class Factory      
{    
public:    
    virtual CPU* CreateCPU() = 0;  
    virtual GPU* CreateGPU() = 0;  
};    
//i5工厂,专门用来生产i5型号的处理器    
class i5Factory :public Factory    
{    
public:    
    CPU* CreateCPU() { return new CPU_i5(); }    
    GPU* CreateGPU() { return new GPU_i5(); }    
};    
//i7工厂,专门用来生产i7型号的处理器    
class i7Factory  : public Factory    
{    
public:    
    CPU* CreateCPU() { return new GPU_i5(); }    
    GPU* CreateGPU() { return new GPU_i7(); }    
};   

int main()
{
    Factory * i5factory = new i5Factory;
    CPU * i5CPU = i5factory->CreateCPU();
    i5CPU->Show();
    GPU * i5GPU = i5factory->CreateGPU();
    i5GPU->Show();
      
    system("pause");
    return 0;
}

1.4 三种工厂模式的UML图

简单工厂模式:
image.png
工厂方法模式:
image.png
抽象工厂模式:
image.png

2. 观察者模式

观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。它还有两个别名,依赖(Dependents),发布-订阅(Publish-Subsrcibe)。可以举个博客订阅的例子,当博主发表新文章的时候,即博主状态发生了改变,那些订阅的读者就会收到通知,然后进行相应的动作,比如去看文章,或者收藏起来。博主与读者之间存在种一对多的依赖关系。下面给出相应的UML图设计。

image.png

可以看到博客类中有一个观察者链表(即订阅者),当博客的状态发生变化时,通过Notify成员函数通知所有的观察者,告诉他们博客的状态更新了。而观察者通过Update成员函数获取博客的状态信息。

观察者基类
//观察者基类
class Observer
{
public:
    Observer(){}
    virtual ~Observer() {}
    virtual void Update() {}
};
具体观察者类
class ObserverA:public Observer
{
private:
    string m_Name;//观察者姓名
    Blog * m_Blog;//观察的博客, 以链表形式更好,可以观察多个博客
public:
    //构造
    ObserverA(string obs_name, Blog* obs_blog) :m_Name(obs_name), m_Blog(obs_blog)
    {
    
    }
    //析构
    ~ObserverA()
    {

    }
    //重写update
    void Update()//获得更新
    {
        string status = m_Blog->GetStatus();
        cout << this->m_Name << "--------" << status << endl;
    }
};
博客基类
//博客基类
class Blog
{
public:
    Blog() {}
    virtual ~Blog() {}
    //向观察者链表中添加观察者
    void Attach(Observer* obs)
    {
        m_Observer.push_back(obs);
    }
    //移除观察者
    void Remove(Observer* obs)
    {
        m_Observer.remove(obs);
    }
    //通知观察者
    void Notify()
    {
        //遍历观察者链表
        for (list<Observer*>::iterator it = m_Observer.begin(); it != m_Observer.end(); it++)
        {
            (*it)->Update();
        }
    }
    //设置状态
    virtual void setStatus(string s)
    {
        m_Status = s;
    }
    //获得状态
    virtual string GetStatus()
    {
        return m_Status;
    }

private:
    list<Observer*> m_Observer;//观察者链表
protected:
    string m_Status;//状态
};
具体博客类
//具体博客实现类
class MyBlog:public Blog
{
private:
    string  m_Name;//博客名称
public:
    MyBlog(string name)
    {
        this->m_Name = name;
    }
    ~MyBlog()
    {

    }

    void setStatus(string s)
    {
        this->m_Status = "Blog通知: " + this->m_Name + s;
    }

    string GetStatus()
    {
        return this->m_Status;
    }
};
main()
//创建一个blog子类对象
    Blog * blogA = new MyBlog("C++设计模式");
    //创建一个observer子类对象
    Observer * obsA= new ObserverA("JiaoZJ", blogA);
    //博客添加观察者
    blogA->Attach(obsA);
    //博客设置状态
    blogA->setStatus("发表: 观察者模式实现");
    //博客通知观察者
    blogA->Notify();

    delete blogA;
    delete obsA;

3. 单例模式

通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象。最好的办法就是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法。
黑马提供的思路:

  1. 创建私有的 静态 类指针 变量(静态变量要类外初始化)
  2. 构造函数私有化 构造函数中实例化对象
  3. 创建一个静态方法返回该实例
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,366评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,521评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,689评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,925评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,942评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,727评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,447评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,349评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,820评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,990评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,127评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,812评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,471评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,017评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,142评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,388评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,066评论 2 355

推荐阅读更多精彩内容