面向对象的4大特性+组合替换继承

面向对象的四大特性:封装、抽象、继承、多态

一、封装

1、定义

也叫:信息隐藏或数据访问保护,使用protected和private关键字。

2、使用原因

A.如果对类属性访问不做控制,任何代码都可访问和修改,虽灵活但也意味着不可控。
B. 只暴露几个必要的操作,提高类的易用性,调用者也会减少出错。(一个冰箱N多按钮,则研究很久还不一定按对,但若只暴露开/关/调节按钮,操作简单不易出错)

二、抽象

1、定义

封装讲的是如何隐藏和保护信息,抽象讲的是隐藏方法的具体实现。让访问者只关心方法提供的功能,无需知道功能如何实现。

2、实现方式

A.借助接口类(interface)和抽象类(abstract)来实现。
B.函数也是一种抽象,使用函数并不需要研究内部实现逻辑,直接使用即可。
自定义函数也需要使用抽象思维,如:getAliyunPictureUrl()就不具有抽象性,因为某天图片改存到腾讯云,
这个命名也要改,可改成getPictureUrl()更好。

三、继承

1、定义

is-a关系,分为单继承和多继承,区别是前者分别是一个子类继承一个/多个父类。
java和php只支持单继承;C++和python支持单继承和多继承。

2、优缺点

优点:代码复用。
缺点:多次继承的话,会出现多个子类重复写代码的问题;子类与父类高度耦合,修改父类代码直接影响子类。推荐用[组合+委托]替换继承。

3、多重继承的优缺点

#include <iostream>
using namespace std;  
class Person {
public:
	void sleep() {cout << "sleep" << endl;}
	void eat() {cout << "eat" << endl;}
};

class Author : public Person {//Author继承自Person
public:
	void writeBook() {cout << "write Book" << endl;}
};

class Programmer : public Person {//Programmer继承自Person
public:
	void writeCode() {cout << "write Code" << endl;}
};

class Programmer_Author : public Programmer, public Author {//多重继承

};

int main() {
	Programmer_Author pa;
	pa.writeBook();   //调用基类Author的方法
	pa.writeCode();   //调用基类Programmer的方法
	//pa.eat();         //编译错误,eat()定义不明确
	//pa.sleep();       //编译错误,sleep()定义不明确
	return 0;
}

3.1、优点:多继承,可以调用多个基类的不同方法

对象可以调用多个基类中的接口,如代码25行与代码26行对象pa分别调用Author类的writeBook()函数和Programmer类的writeCode()函数。

3.2、缺点:多继承的类,如果调用基类的基类中的方法,容易造成歧义

如果派生类所继承的多个基类有相同的基类,而派生类对象需要调用这个祖先类的接口方法,就会容易出现二义性。代码27、28行就是因为这个原因而出现编译错误的。因为通过多重继承的Programmer_Author类拥有Author类和Programmer类的一份拷贝,而Author类和Programmer类都分别拥有Person类的一份拷贝,所以Programmer_Author类拥有Person类的两份拷贝,在调用Person类的接口时,编译器会不清楚需要调用哪一份拷贝,从而产生错误。对于这个问题通常有两个解决方案:

3.3 解决方案

(1)加上全局符确定调用哪一份拷贝。比如pa.Author::eat()调用属于Author的拷贝。
(2)使用虚拟继承,使得多重继承类Programmer_Author只拥有Person类的一份拷贝。比如在11行和17行的继承语句中加入virtual就可以了。

class Author : virtual public Person //Author虚拟继承自Person
class Programmer : virtual public Person //Programmer虚拟继承自Person

四、多态

1、定义

接口的多种不同的实现方式(一个接口,多种实现),或一个类可以派生出多个特殊类。
继承是多态,接口与实现是多态,两个完全不相关类的相同方法名,也可以叫多态。

2、好处

提高代码的可扩展和复用性。

五、组合代替继承

1、继承的缺点

多用组合,少用继承:
举例:大部分鸟都会飞,在AbstractBird抽象类中,定义一个fly(),但鸵鸟不会飞,
法1:虽然可以在鸵鸟子类中重写fly(),让抛出UnSupportedMethodException异常,
该法不好,因为不会飞的鸟还有很多,如果都重写fly()抛异常,增加工作量的同时违背最小知识原则,暴露不该暴露的接口给外部,增加类使用过程中被误用的概率。
法2:抽象类派生出更细的抽象类AbstractFlyableBird和AbstractUnFlyableBird,但当再关注鸟会不会叫,是否会下蛋,设计又得拆,这个高度耦合,子类依赖父类。

2、接口+组合+委托

法3(终极:接口+组合+委托):接口针对某种行为特性,会飞定义Flyable接口,让会飞的鸟实现该接口;会叫定义Tweetable,会下蛋定义EggLayable,

interface Flyable{
    
    
	function fly();
}
interface Tweetable{
    
    
	function tweet();
}
interface EggLayable{
    
    
	function layEgg(); 
}
class Ostrich implements Tweetable,EggLayable{
    
    //鸵鸟
	function tweet(){
    
    ...}
	function layEgg(){
    
    ...}
}
class Sparrow implements Flyable,Tweetable,Egglayable{
    
    
	function fly();
	function tweet();
	function layEgg();
}

问题:但是接口只声明方法,不定义实现,所以每个会飞的鸟都要实现一遍fly(),且实现逻辑一样,导致代码重复。
解决:组合+委托。针对这三个接口再定义三个实现类,分别实现fly()方法的FlyAbility,实现tweet()方法的TweetAbility,
实现layEgg()方法EggLayAbility。

interface Flyable{
    
    
	function fly();
}
interface Tweetable{
    
    
	function tweet();
}
interface EggLayable{
    
    
	function layEgg(); 
}

class FlyAbility implements Flyable{
    
    
	function fly() {
    
    echo 'fly<hr>';}
}
class TweetAbility implements Tweetable{
    
    
	function tweet() {
    
    echo 'tweet<hr>';}
}
class EggLayAbility implements EggLayable{
    
    
	function layEgg() {
    
    echo 'layEgg<hr>';}
}

class Ostrich implements Tweetable,EggLayable{
    
    
	function tweet() {
    
    
		$tweetObj = new TweetAbility();//组合
		$tweetObj->tweet();//委托
	}
	function layEgg() {
    
    
		$eggLayObj = new EggLayAbility();//组合
		$eggLayObj->layEgg();//委托
	}
}

$obj = new Ostrich();
$obj->tweet();//tweet
$obj->layEgg();//layEgg

猜你喜欢

转载自blog.csdn.net/wuhuagu_wuhuaguo/article/details/108857135
今日推荐