设计模式
设计模式(design pattern)是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案
关于面向对象:
- 向下:深入理解三大面向对象机制
- 封装,隐藏内部实现
- 继承,复用现有代码
- 多态,改写对象行为
- 向上:深刻把握面向对象机制所带来的抽象意义,理解如何使用这些机制来表达现实世界,掌握什么是“好的面向对象设计
七大原则
- 开闭原则
- 依赖倒转(倒置)原则
- 单一职责原则
- 接口隔离原则
- 里氏替换原则
- 迪米特法则
- 合成复用原则
2.2.1 开闭原则(Open-Closed Principle, OCP)
- 概念
- 类模块应该是可扩展的,但是不可修改。
- 对扩展开放,对更改关闭。
- 方法:
- 可通过虚函数来扩展订单类型。而无需修改现有的 类
2.2.2 单一职责(Simple Responsibility Pinciple,SRP)
- 概念
- 每个应该实现单一职责,否则就应该把类拆分,便于后期的维护
- 不要存在多余一个导致类变更的原因。一个类应该应该只负责一项职责。
- 由于第一点要求可能导致类的数量变得多,所以尽量通过组合而不是继承来实现功能,使得类的数量不是太多
- 方法:
-
为了遵循单一职责,我们应该将文件操作、日志记录、压缩操作分为独立的类来使用,甚至我们还可以进一步来优化,设计一个抽象类编译更好的代码扩展和替换。
-
2.2.3 依赖倒置原则(Dependence Inversion Principle,DIP)
- 高层模块(稳定)不应该依赖于底层模块(变化),二者都应该依赖于抽象(稳定)
- 抽象(稳定)不应该依赖于实现细节(变化),实现细节应该依赖于抽象(稳定).
2.2.4 里氏替换原则(Liskov Substitution Principle,LSP)
-
- 子类必须能够替换它们的基类(IS-A)。
- 子类必须保持和父类接口的一致性
- 子类可以扩展父类的功能
- 子类不应该引入新的错误或异常
通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。【尽量不用重写父类的方法】
2.2.5 接口隔离原则(Interface Segregation Principle,ISP)
- 使用多个专门的接口,而不使用单一的总接口,不应该强迫客户程序依赖它们不用的方法
- 接口尽量细化,同时接口中的方法尽量少并且完备。
- 在实际中使用,根据业务职业将抽象类接口分为多个接口,按照接口职责来进行区分,因为抽象类中,有很多纯虚函数,所以在继承他的时候,就必须要把所有的纯虚函数都给实现,这样就导致冗余,所以在实现的时候,因为把需要的部分摘取出来,重新成立一个类,或者把那个总类单独抽取出来,一开始就分出来,
(注:这里和"单一职责"是有区别的,单一职责强调类的职责单一,站在业务逻辑的角度,这里强调的是接口,要求接口的方法尽可能的少而完备)
2.2.6 迪米特法则(Law of Demeter LoD)
迪米特原则(Law of Demeter LoD)是指一个对象应该对其他对象保持最少的了解,又叫最少知道原则(Least Knowledge Principle,LKP),尽量降低类与类之间的耦合。迪米特原则主要强调只和朋友交流,不和陌生人说话。出现在成员变量、方法的输入、输出参数中的类都可以称之为成员朋友类,而出现在方法体内部的类不属于朋友类。
2.2.7 合成复用原则(Composite Reuse Principle,CRP)
合成复用原则(Composite Reuse Principle,CRP)是面向对象设计中的一个重要原则,它强调在系统设计中应该优先使用组合(Composition)而不是继承(Inheritance)来实现代码的复用和组织。这个原则的核心思想是:要尽量通过将现有的类组合起来创建新的功能,而不是通过继承已有的类来实现复用。
继承的方法:
比如现在要画一个带颜色的形状,比如颜色有红绿蓝,形状有正方形、长方形、圆形。如果是继承的方法,那就要分别定义9个类,比如红圆、绿圆、蓝圆,等等九个类,再分别定义他们的方法
class Shape {
public:
virtual void draw() = 0;
};
class RedCircle : public Shape {
public:
void draw() override {
std::cout << "Drawing Red Circle" << std::endl;
}
};
组合的方法:
只需要预先定义六个类,shape类中有三个,正方形、长方形、圆形类,color类中有三个,红类、绿类、蓝类。
然后我们在实现的时候自己组合即可
class Color {
public:
virtual void fill() = 0;
};
class Red : public Color {
public:
void fill() override {
std::cout << "Filling with Red" << std::endl;
}
};
class Shape {
public:
Shape(Color* color) : color(color) {}
virtual void draw() = 0;
protected:
Color* color;
};
class Circle : public Shape {
public:
Circle(Color* color) : Shape(color) {}
void draw() override {
std::cout << "Drawing Circle with ";
color->fill();
}
};
Color* red = new Red();
Shape* redCircle = new Circle(red);
在类多的情况下,组合很好。
- 总结
- 我们发现合成复用原则和里氏替换原则好像是冲突的。其实并不是,他们是相辅相成的面向对象设计原则。合成复用原则(CRP)强调通过组合已有的类来实现复用,而不是通过继承来实现。它鼓励将对象组合在一起以创建新的功能,以降低类之间的耦合度,提高灵活性和可维护性。
里氏替换原则(LSP)强调子类应该能够替换其基类而不影响程序的正确性。LSP指出,
派生类应该保持与基类一致的行为,遵守基类的契约和约定,以确保多态性的正确性。
- 当我们需要降低代码耦合性、组合复杂的功能、设计组件替换的时候可以选择CRP。
- 当我们需要提供代码重用性、提高代码扩展性、提高代码稳定性的时候可以选择LSP。