一.深入理解面向对象
- 向下:深入理解三大面向对象机制
- 封装,隐藏内部实现
- 继承:复用现有代码
- 多态:改写对象行为
二,软件设计目标
复用!拥有极高的复用性
重新认识面向对象
面向对象设计原则(1)
依赖倒置原则(DIP)
- 高层模块不应该(稳定)不应该依赖于底层模块(变好),二者都应该依赖于抽象(稳定)
- 抽象(稳定)不应该依赖于实现细节(变化),实现细节应该依赖于抽象(稳定)
//动物类,抽象类
class 动物类{
public:
//叫方法,纯虚函数
virtual void 叫() = 0;
};
//狗类,继承动物类
class 狗:public 动物类{
public:
//实现叫方法
void 叫(){
cout << "汪汪汪" << endl;
}
};
//猫类,继承动物类
class 猫:public 动物类{
public:
//实现叫方法
void 叫(){
cout << "喵喵喵" << endl;
}
};
//人类
class 人{
private:
//动物指针,指向动物对象
动物类* dong;
public:
//构造函数,给dong赋默认值
人(){
dong = nullptr;
}
//析构函数,释放dong指向的对象
~人(){
if(dong != nullptr){
delete dong;
}
}
//设置函数,接收动物对象
void 设置(动物类* d){
//如果dong不为空,先释放它指向的对象
if(dong != nullptr){
delete dong;
}
//给dong赋新值
dong = d;
}
//养动物方法,调用动物的叫方法
void 养动物(){
if(dong != nullptr){
dong->叫();
}
}
};
int main(){
//创建猫对象
猫* m = new 猫();
//创建人对象
人 r = 人();
//调用设置函数,传入猫对象
r.设置(m);
//调用养动物方法
r.养动物();
}
开放封闭原则(OCP)(2)
- 对模块开放,对更改封闭
- 类模块应该是可扩展的,但是不可修改的
单一职责原则(SRP)(3)
- 一个类应该仅有一个引起它变化的原因
- 变化的方向隐含着类的责任
Liskov替换原则(LSP)(4)
- 子类必须能够替换它们的基类(IS-A)
- 继承表达类型抽象
接口隔离原则(ISP)(5)
- 不应该强迫客户程序依赖它们不用的方法
- 接口应该小而完备
优先使用对象组合,而不是类继承(6)
- 类继承通常为"白箱复用",对象组合通常为"黑箱复用"
- 继承某种程度上破坏了封装性,子类父类耦合度搞
- 而对象组合则只要求被组合的对象具有来个很好定义的接口耦合度低
封装变化点(7)
- 使用封装来创建对象之间的分界层,让设计者可以在分界的一侧进行修改,
- 而不会对另一侧产生不良的影响从而实现层次间的松耦合
针对接口变成,而不是针对实现编程(8)
- 不将变量类型 声明为某个特定的具体类,而是声明为某个接口.
- 客户程序无需获知对象的具体类型,只需要知道对象所具有的巨口
- 减少系统中各部分的依赖关系,从而实现,"高内聚,松耦合"的类型设计方案
面向接口设计
接口标准化!
理解设计模式本身的作用才是关键
静态绑定->动态绑定
早绑定->晚绑定
继承->组合
编译时依赖->运行时依赖
紧耦合->松耦合
例子:
一.
template_lib.cpp
中有库开发人员直接提供的方法
template1_app.cpp中需要有我们定义的方法
然后需要我们同时调用库开发人员的方法,和我们直接定义的方法
我们有Step2和Step4在temaplate1中自己实现
后在Main调用template_lib中的方法一起实现
二.
库开发人员在lib文件中定义了
virtual Step2()=0; 但不做实现,需要派基类去实现
virtual Step4()=0;
它将原本需要在mian函数中定义的框架
放到了lib中并封装成了Run方法在Run中调用Step2,Step4
这样子的调用流程就改变了
原本是需要在我们在Main函数中去编写框架
现在变成了库开发人员直接在自己的库中实现了框架和方法
开发人员只需要在子类中实现父类的方法后调用父类提供的Run方法
就会形成所谓的多态模式调用
延迟到子类:在面向对象的术语中就是指父类定义纯虚函数让子类实现]
class AbstractClass {
public:
// 模板方法,调用基本方法组成一个算法
void templateMethod() {
primitiveOperation1();
primitiveOperation2();
concreteOperation();
hook();
}
// 基本方法1,声明为纯虚函数,由子类实现
virtual void primitiveOperation1() = 0;
// 基本方法2,声明为纯虚函数,由子类实现
virtual void primitiveOperation2() = 0;
// 基本方法3,在基类中已经实现,但子类可以选择覆盖
virtual void hook() {}
// 具体方法,在基类中已经实现,并且不允许子类修改
void concreteOperation() {
// some implementation
}
};
// 具体子类A,实现了基本方法1和2
class ConcreteClassA : public AbstractClass {
public:
void primitiveOperation1() override {
// some implementation
}
void primitiveOperation2() override {
// some implementation
}
};
// 具体子类B,实现了基本方法1和2,并且覆盖了基本方法3
class ConcreteClassB : public AbstractClass {
public:
void primitiveOperation1() override {
// some implementation
}
void primitiveOperation2() override {
// some implementation
}
void hook() override {
// some implementation
}
};
如果不是这种思想可能代码就会写成这样子
// 抽象基类,没有定义算法的骨架
class AbstractClass {
public:
// 基本方法1,声明为纯虚函数,由子类实现
virtual void primitiveOperation1() = 0;
// 基本方法2,声明为纯虚函数,由子类实现
virtual void primitiveOperation2() = 0;
// 基本方法3,在基类中已经实现,但子类可以选择覆盖
virtual void hook() {}
// 具体方法,在基类中已经实现,并且不允许子类修改
void concreteOperation() {
// some implementation
}
};
// 具体子类A,实现了基本方法1和2,并且调用了其他基本方法
class ConcreteClassA : public AbstractClass {
public:
void primitiveOperation1() override {
// some implementation
}
void primitiveOperation2() override {
// some implementation
}
// 定义了一个算法函数,调用了基本方法
void algorithm() {
primitiveOperation1();
primitiveOperation2();
concreteOperation();
hook();
}
};
// 具体子类B,实现了基本方法1和2,并且覆盖了基本方法3,并且调用了其他基本方法
class ConcreteClassB : public AbstractClass {
public:
void primitiveOperation1() override {
// some implementation
}
void primitiveOperation2() override {
// some implementation
}
void hook() override {
// some implementation
}
// 定义了一个算法函数,调用了基本方法
void algorithm() {
primitiveOperation1();
primitiveOperation2();
concreteOperation();
hook();
}
};
每一个子类中都需要定义一个algorithm导致了代码的复用
int main(){
AbstractClass *abstr = new ConcreteClassA()
abstr->templateMethod();
delete abstr;
}
||
int main() {
// 创建一个抽象基类的指针数组,存放不同的子类对象
AbstractClass* abstr[2];
abstr[0] = new ConcreteClassA();
abstr[1] = new ConcreteClassB();
// 遍历数组,调用每个子类对象的templateMethod()函数
for (int i = 0; i < 2; i++) {
abstr[i]->templateMethod();
}
// 释放内存
for (int i = 0; i < 2; i++) {
delete abstr[i];
}
}
template method模式的优缺点有以下几点:
优点: