[学习][笔记]设计模式(基于C/C++实现)之 设计基础(一)

简介: [学习][笔记]设计模式(基于C/C++实现)之 设计基础

前言

设计原则

单一职责原则(Single Responsibility Principle)

定义

不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。

问题由来

类 T 负责两个不同的职责:职责 P1,职责 P2。

当由于职责 P1 需求发生改变而需 要修改类 T 时,有可能会导致原本运行正常的职责 P2 功能发生故障。

解决方案

将类 T 分成两个不同的类来实现。比如 C 语言中会将头文件分类 string.h stdio.h, Qt 中 QLable QButton 等。C++中会有 string fstream 类,就是单一原则的体现。

开闭原则(Open Closed Principle)

定义

一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。

问题由来

在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改 时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需 要原有代码经过重新测试。

解决方案

当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已 有的代码来实现变化。

例如:

man 可以做doAction,

不考虑开闭,就是man添加 jump,run,read方法。直接使用

但是这样每次都会修改 影响代码的维护。

class Man{
public:
  void jump();
  void read();
  void run();
}
void main(){
  Man *aman = new Man;
  man.jump();
  man.read();
  man.run();
}

遵循开闭原则的话,man就通过调用Ido的do()来实现动作 也不需要每个man把三个对象都调用一遍

class Ido{
public:
   virtual void do() = 0;
}
class Read :public Ido{
    void do(){
       //read();
    }
}
class Run:public Ido{
    void do(){
       //run();
    }
}
class Jump:public Ido{
    void do(){
       //jump();
    }
}
class Man{
public:
  void doAction(Ido *doptr){ doptr.do()}
}
void main(){
  Man *aman = new Man;
  Ido* it= new Read();
  man.doAction(it);
    Ido* it2= new Run();
  man.doAction(it2);
    Ido* it3= new Jump();
  man.doAction(it3);
}

依赖倒置原则(Dependence Inversion Principle)

定义

高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细 节应该依赖抽象。

问题由来

类 A 直接依赖类 B,假如要将类 A 改为依赖类 C,则必须通过修改类 A 的代码来达成。这种场景下,类 A 一般是高层模块,负责复杂的业务逻辑;类 B 和类 C 是低层模 块,负责基本的原子操作;假如修改类 A,会给程序带来不必要的风险。

解决办法

接口分离原则(Interface Segregation Principle)

定义

客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接 口上。

问题由来

类 A 通过接口 I 依赖类 B,类 C 通过接口 I 依赖类 D,如果接口 I 对于类 A 和类 B 来说不是最小接口,则类 B 和类 D 必须去实现他们不需要的方法。

解决办法

将臃肿的接口 I 拆分为独立的几个接口,类 A 和类 C 分别与他们需要的接口建立依 赖关系。也就是采用接口隔离原则。

迪米特法则(Law of Demeter)

定义

面向对象有一个概念是迪米特法则,其规则如下:

每个对象对其他对象的认识必须限制,只能与自己最近的对象

每个对象应当只和它的朋友联系,而不是陌生人

每个对象应当只和他的直接朋友联系。

问题由来

在于降低类之间的耦合。由于每个类尽量减少对其他类的依赖,因此,很容易使得系统的功能模块功能独立,相互之间不存在(或很少有)依赖关系。

解决方案

迪米特法则不希望类之间建立直接的联系。如果真的有需要建立联系,也希望能通过它的友元类来转达。因此,应用迪米特法则有可能造成的一个后果就是:系统中存在大量的中介类,这些类之所以存在完全是为了传递类之间的相互调用关系——这在一定程度上增加了系统的复杂度。

设计模式的门面模式(Facade)和中介模式(Mediator),都是迪米特法则应用的例子。

里氏替换原则(Liskov Substitution Principle)

定义

如果对每一个类型为 T1 的对象 o1,都有类型为 T2 的对象 o2,使得以 T1 定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那 么类型 T2 是类型 T1 的子类型。 所有引用基类的地方必须能透明地使用其子类的对象

问题由来

当使用继承时,遵循里氏替换原则。类 B 继承类 A 时,除添加新的方法完成新增 功能 P2 外,尽量不要 shadow(遮盖)父类 A 的方法。 继承作为面向对象三大特性之一,在给程序设计带来巨大便利的同时,也带来了弊 端。

比如使用继承会给程序带来侵入性,程序的可移植性降低,增加了对象间的耦合性, 如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且 父类修改后,所有涉及到子类的功能都有可能会产生故障。

优点:

1. 提高代码的重用性,子类拥有父类的方法和属性;

2. 提高代码的可扩展性,子类可形似于父类,但异于父类,保留自我的特性;

缺点:侵入性、不够灵活、高耦合

1. 继承是侵入性的,只要继承就必须拥有父类的所有方法和属性,在一定程度上约束了子类,降低了代码的灵活性;

2. 增加了耦合,当父类的常量、变量或者方法被修改了,需要考虑子类的修改,所以一旦父类有了变动,很可能会造成非常糟糕的结果,要重构大量的代码。

解决方案

里氏替换原则通俗的来讲就是: 子类可以扩展父类的功能,但不能改变父类原有的 功能。 它包含以下 4层含义:

1.子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法。

2.子类中可以增加自己特有的方法。

3.当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。

4.当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

用我自己的话讲就是:

子类可以完全替换父类,而不影响程序编译;尽量不要重写父类。

UML图

设计视图

类的关系

类图

泛化

定义 是一种继承关系, 表示一般与特殊的关系, 它指定了子类如何特化父类的所有特征 和行为。

箭头指向 带三角箭头的实线,箭头指向父类。

类图关系:

实现

定义 是一种类与接口的关系,表示类是接口所有特征和行为的实现

箭头指向 带三角箭头的虚线,箭头指向接口。

类图关系



相关文章
|
9天前
|
编译器 开发工具 C语言
配置C++的学习环境
这篇教程介绍了学习C++语言所需的环境配置和软件选择。首先,你需要一个文本编辑器(如Visual Studio Code、Visual Studio、Vim、Emacs或Eclipse)和一个C++编译器(如GCC)。在不同操作系统上安装GCC的方法包括:在Linux或UNIX上使用命令行检查或安装GCC,在Mac OS X上通过Apple的Xcode,而在Windows上则需要安装MinGW。教程还提供了使用Visual Studio创建和编译C++程序的步骤。最后,文章简述了g++编译器的使用及其常用命令选项。
20 0
|
5天前
|
设计模式 开发框架 算法
C++中的设计模式:基本概念与应用
C++中的设计模式:基本概念与应用
20 2
|
12天前
|
算法 C++
c++算法学习笔记 (21) STL
c++算法学习笔记 (21) STL
|
12天前
|
算法 C++
c++算法学习笔记 (20) 哈希表
c++算法学习笔记 (20) 哈希表
|
12天前
|
算法 C++
c++算法学习笔记 (19) 堆
c++算法学习笔记 (19) 堆
|
12天前
|
人工智能 算法 C++
c++算法学习笔记 (18) 约数
c++算法学习笔记 (18) 约数
|
12天前
|
人工智能 算法 C++
c++算法学习笔记 (17) 质数
c++算法学习笔记 (17) 质数
|
12天前
|
算法 C++
c++算法学习笔记 (16) 并查集
c++算法学习笔记 (16) 并查集
|
12天前
|
算法 C++
c++算法学习笔记 (15) 单调栈与单调队列
c++算法学习笔记 (15) 单调栈与单调队列
|
12天前
|
算法 C++
c++算法学习笔记 (14) 栈与队列
c++算法学习笔记 (14) 栈与队列