-
要谈面向对象编程,首先应该拿面向过程编程来对比。
举个生活中例子,我觉得面向过程写出来的程序就是一份炒饭(如蛋炒饭),而面向对象写出来的程序就是一份盖浇饭(南方木桶饭)。蛋炒饭的优点是鸡蛋和米饭葱花等混合入味均匀吃起来香而且吃得快。但是如果你吃了一口感觉今天不想吃鸡蛋,想吃肉炒饭,那因为鸡蛋和米饭已经混合均匀,只能把这一份全部倒了重新炒一个肉丝炒饭。但是如果你点的是一份葱花鸡蛋盖浇饭,端上来发现不想吃鸡蛋而想吃葱爆肉片,那比较简单,把米饭上面的葱花鸡蛋拨掉,重新盖上一份葱爆肉片,米饭还可以重复利用。但是盖浇饭的缺点就是入味不均,吃起来也比较慢。
面向过程编程(炒饭)》运行性能好(吃起来方便)》耦合度高,复用性差(材料混合,不区分是饭还是菜混合起来炒)。
面向对象编程(盖浇饭)》运行性能差(吃起来不够方便)》耦合度低,复用性好(饭菜分离)。
-
面向对象的三大特性——封装、继承、多态
【封装】
把那些具有相同属性和行为的对象抽象出数据变量和函数然后包装成一个单独的单元也就是类。设计类的过程就是在封装。比如前面提到的做菜,可以吧米饭当做一个类,菜当做一个类。
【继承】
继承是可以让某个类型的对象获得另一个类型的对象的属性和行为。一般是在一些相似的类中再抽象出相同的属性和行为,再封装成一个父类,这样可以减少代码冗余代码,提高复用性。比如前面提到的做菜,菜可以是更抽象的基类,蔬菜和肉菜可以是更具体一点的子类,蔬菜和肉菜都是菜,is-a关系。
注意:private修饰的类数据成员和成员方法都不能被子类继承。
[访问权限]:
private——只能被类中的成员函数以及友元函数访问。不能被子类继承以及类的实例对象访问。所以数据成员一般设为private私有的。若class类成员省略了权限修饰符,那么默认是private,如果是struct类成员则默认是public。
protected——能被类中的成员函数、友元函数、子类继承后子类的函数访问。但还是不能被类的实例对象访问。
public——都能访问,开放的。所以类的成员函数一般设为public,以便让实例对象调用。特别是构造函数。
friend修饰的友元函数有3种——1、普通函数即不是某个类的成员函数。2、某个类的某些成员函数。3、在类中声明友元类friend class A; 则A的所有成员函数都成为该类的友元函数,还需注意相同class的不同对象自动互为友元。注意:友元关系不能被继承也不能被传递。友元的优点是高效缺点是破坏了封装。
【多态】
一个类的相同方法在不同的情形下表现出多种不同的状态。一般是使用父类的指针去指向子类的实例对象,用该父类指针去调用父类和子类的相同方法时,每次会根据子类对方法的重写而表现出不同的状态。比如前面提到的做菜,我只说让老板给我做一个菜,但是没说具体什么菜,调用cooking()这个方法,如果老板用蔬菜做了,那我就吃到了蔬菜,用荤菜做的我就吃了荤菜。
-
面向对象的几个基本原则
【单一职责原则】
对于一个类来说,应该只有一个引起它变化的原因。如果一个类承担的职责过多,就等于把这些职责耦合起来了,一个职责的变化可能会影响这个类完成其他的职责。比如前面提到的蛋炒饭,把它当做一个类既完成做饭又完成做菜的职责,两个职责高度耦合,当想换个菜时,饭也用不了了。单一职责避免高度耦合。
【开放-封闭原则】
对扩展开放,对修改封闭。是说对于一个软件的模块类函数等,不能修改已有的,但是可以对于新的需求扩展新的。就像香港回归的“一国两制”,大陆的社会主义制度不能修改,香港又暂时没法改变原有的资本主义制度,为了回归的大局,那么增加一种新的制度也是可以有的。比如前面提到的做菜,对于一条草鱼,刚开始只有水煮鱼这一种菜,现在厨师想加酸菜做成酸菜鱼,与其修改水煮鱼的做法,还不如扩展一个子类酸菜鱼,增加一个加酸菜的方法。这里体现了可复用、可扩展、灵活性好的优点。
【依赖倒转原则】
高层模块不应该依赖低层模块。而是应该将各层模块的依赖终止于抽象类或者接口,也就是谁也不要依赖谁,除了约定的接口。比如电脑的主板可以认为是高层模块,内存条可以认为是低层模块,他们之间都不应该相互依赖,而是在主板上安排了内存条的接口可以轻松插拔更换内存条。比如观察者模式中,被观察的主题(通知者)应该依赖抽象的观察者类,而不是具体的观察者,同样的具体的观察者也应该依赖抽象的主题而不是具体的某个主题。
【替换原则】在软件里面,把父类都替换成子类,程序的行为没有变化。
【接口分离原则】模块间要通过抽象接口隔离开,而不是通过具体的类强耦合起来。
【高内聚、低耦合】从电脑的cpu、内存条、硬盘、主板等这些东西的关系就很好的体现了这句话。
-
类与类的几种关系
【继承inheritance】
类A继承类B,UML类图是实线加三角形由A指向B。A is-a B.
public继承:父类中的成员访问权限不变。
pretected继承:父类的访问权限中public权限降为protected,其他不变。
private继承:父类中public和protected权限的成员全变为private,权限降级,即子类只有类成员变量和友元函数能够访问这些。
【依赖dependency】
类A依赖类B,UML类图是虚线加箭头由A指向B。A use-a B.
一般来说,依赖是指A的某些方法功能要用到B,常表现为B作为A的成员方法的形参或局部变量或返回值,即只和类成员方法有关。
【关联association】
类A和类B双向关联,UML类图是A——B一根实线连接。A与B互相作为类的数据成员变量。
【组合composition】
类A组合了类B,UML类图是A实心菱形再实线和箭头指向B。类A中定义了类B作为数据成员,B在A中定义构造。A和B的对象生命周期一样。A拥有完整的B,强拥有关系。
【聚合aggregation】
类A聚合了类B,UML类图是A空心菱形再实线和箭头指向B。类A中定义了类B的指针作为数据成员,类B的实例化在其他地方。A和B的对象生命周期不一样。A拥有不完整的B,弱拥有。可以认为是 composition by reference == aggregation 或者也可以叫做委托delegation。
-
面向对象的几个设计模式
【模板方法模式】
技巧总结:抽象父类+抽象方法+继承+多态
大致描述:抽象父类中的抽象方法(纯虚函数)可以被认为是一种模板,子类必须去实现这些抽象方法。然后利用多态性质,使用父类的指针引用可以调用子类实现的不同的具体方法。 很常见的设计模式,大家肯定都用过,只是不知道这也是一种设计模式。
【单例模式】
技巧总结:聚合了一个自身的静态对象指针+私有构造+公有静态getInstance()方法
大致描述:保证一个类只有一个实例,将构造方法设为私有这样外部不能轻易的定义构造新的对象,而是提供一个共有的静态的getInstance()方法通过类名调用来返回对象。而且可以在getInstance()方法中使用双重锁定来支持多线程的单例模式。更简单的是可以在该函数中定义一个局部静态对象(依赖关系?),在局部静态对象的创建过程中C++11会默认进行多线程互斥,因此不需要显示的加锁。
private:
Singleton(){}; //私有构造方法
static Singleton * instance; //私有静态对象指针,聚合自身
public:
static Singleton * getInstance()
{
if( NULL == instance)
{
mutex.lock();
if( NULL == instance)
{
instance = new Singleton(); //唯一的一次实例化对象
}
mutex.unlock();
}
return instance;
//最简单的线程安全做法,上面的全不要,甚至私有的静态对象指针都可以不要
static Singleton instance;
return &instance;
}
懒汉模式的单例模式:是指new一个实例对象发生在第一次调用getInstance()函数后,用时间换取空间。
饿汉模式是在该类静态初始化的时候,就在类外new了一个全局对象给类中的静态对象指针,属于用空间换取时间。
https://www.jianshu.com/p/e47a8a778c58 单例模式的各种实现分析
【简单工厂模式】
技巧总结:继承+依赖+多态+前后端分离
大致描述:比如想做一个计算器。如果在一个类里面把加减乘除操作都做完,那么就违背了【单一职责原则】,当发现除法功能设计的还有问题想去修改时,必须去修改整个类,这样也违背了【开放—封闭原则】。如果把这些做成4个子类,都继承一个抽象的父类Operation,只有一个抽象方法getResult()。这样就算解耦了很多,此时要修改除法类也不会影响其他类。若要增加一个求次方的功能,也可以增加一个次方类继承Operation。使用的时候,在前端代码中,可以直接创建各个方法的实例去实现对应的计算功能。但是,这样做的话前端工程师也必须知道后台算法怎么用,并没有做好前后端开发分离。
一种好的解决前后端开发分离的方式就是,给Opration类建一个工厂类OperationFactory,工厂类依赖运算类,也就是在一个create生产方法中用多态和根据参数用switch选择判断来new一个子类,生产方法返回父类Operation的指针。前端那边只要知道使用这个工厂类和给定选择条件,就可以生产出需要的运算实例对象了,而子类运算对象创建的过程被封装起来了。如果要增加新的次方功能,也就只需增加一个次方类和修改工厂类中的判断加一条分支,客户端不需要改动。
【工厂方法模式】
技巧总结:继承+依赖+多态+简单工厂升级(增加具体的工厂子类)
大致描述:在简单工厂中,我们将工厂类设计成选择判断动态实例化运算子类,使得这里耦合严重,每次要增加新的运算功能,都需要在工厂类的生产方法中增加新的判断分支,这里违反了【开放—封闭原则】。工厂方法模式主要就是解耦合这个工厂类,将每个switch分支生产一个运算子类实例的功能分离出一个工厂子类,工厂类升级成抽象父类。不过这样做又把选择使用哪种算法的步骤丢给了前端,也还是不够好。
【策略模式】
技巧总结:继承+依赖+多态+聚合(简单工厂多一个聚合和多一个算法调用函数)
大致描述:策略模式主要是将一些具有相似功能的算法分别封装成子类然后继承一个抽象父类,客户端使用这些算法时通过一个中间接口Context,完全不用知道后台有这些算法的存在。在简单工厂模式中,工厂类是只有一个create生产方法来依赖父类创建运算子类实例。客户端使用的时候至少还要知道有抽象父类Operation在。而策略模式是在中间类Context中聚合了一个父类Operation对象的引用,然后create方法中switch分支判断生产出来一个运算子类实例对象后赋给该父类引用,然后再在一个getResult()成员函数中用该父类引用去调用子类的算法函数得到运算结果。如此一来,客户端只需要知道有这么个中间类Context在就可以了,完全不用知道后台还有多少算法,前后端充分解耦。
【观察者模式】
技巧总结:继承+聚合+依赖+多态
大致描述:观察者模式和策略模式很像,将多种算法换成多个观察者,Context类换成Subject然后聚合一个list的观察者指针,然后有attach()方法往list中增加观察者还有detach()方法删除观察者,Subject状态发生改变时可以调用通知方法去遍历list调用每一个观察者的update方法。
还有一种升级版观察者模式是直接将每个观察者的update()方法的函数指针组成一个list指令集,在Subject里注册一个EventHandle去挨个执行这些指令集。这样做的好处是将主题和观察者解耦,主题不用再知道有哪些观察者。
网友评论