面向对象之封装、继承、多态
面向对象的三个基本特征是:封装、继承、多态。
# 封装
封装是面向对象的特征之一,是对象和类概念的主要特性。
封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
# 继承
面向对象编程 (OOP) 语言的一个主要功能就是“继承”。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
通过继承创建的新类称为“子类”或“派生类”;被继承的类称为“基类”、“父类”或“超类”。
继承的过程,就是从一般到特殊的过程。
# 继承的作用
简化了代码,减少了冗余。继承简化了人们对事物的认识和描述,能清晰体现相关类间的层次结构关系,提高了代码的健壮性,安全性。
继承是在一些比较一般的类的基础上构造、建立和扩充新类的最有效的手段。
要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。
在某些 OOP 语言中,一个子类可以继承多个基类。但是一般情况下,一个子类只能有一个基类,要实现多重继承,可以通过多级继承来实现。
在考虑使用继承时,有一点需要注意,那就是两个类之间的关系应该是“属于”关系。例如,Employee 是一个人,Manager 也是一个人,因此这两个类都可以继承 Person 类。但是 Cat 类却不能继承 Person 类,因为猫并不是一个人。
# 单继承
class Person(object):
def __init__(self,name,age):
self.name = name
self.age = age
@staticmethod
def run():
print("run")
def eat(self,food):
print("eat "+food)
self.run()
class Student(Person):
def study(self):
print('I can study.')
class Employee(Person):
def work(self):
print('I can work.')
a = Student("wangming",18)
print(a.name)
a.run()
a.eat("orange")
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 多继承
class Immortal:
def fly(self):
print('神仙会飞')
class Monkey:
def eat_peach(self):
print('猴子喜欢吃桃')
class Wukong(Immortal,Monkey):
pass
swk = Wukong()
swk.fly()
swk.eat_peach()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 私有化
# 私有属性
以__开头,声明为属性私有,不能在类的外部被使用或者直接访问。
## 私有属性 以__开头,声明为属性私有,不能在类的外部被使用或者直接访问。
class Person(object):
def __init__(self):
self.__name = '我叫詹躲躲' ## 私有化
self.age = '21'
def __str__(self):
return '{}的年龄是{}'.format(self.__name,self.age)
person = Person()
## print(person.__name) #报错
print(person) #可以访问
## 我叫詹躲躲的年龄是21
## 私有属性,不能被子类继承
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 私有方法
class A(object):
def __eat(self):
print('吃饭')
def run(self):
print('跑步')
b = A()
b.__eat() ## 报错
b.run() ## 跑步
2
3
4
5
6
7
8
9
10
11
OO 开发范式大致为:划分对象→抽象类→将类组织成为层次化结构(继承和合成) →用类与实例进行设计和实现几个阶段。
# 多态
多态性(polymorphism)是允许你将父对象设置成为和一个或更多的它的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。同一操作作用与不同类的实例,将产生不同的执行结果,即不同类的对象收到相同的消息时,将得到不同的结果。
实现多态的三个必要条件:
- 继承
- 重写
- 父类引用指向子类对象
通俗来讲就是同一个函数,因为传递的参数列表不同,可以实现的不同的功能。
实现多态,有两种方式,覆盖,重载。
覆盖,是指子类重新定义父类的虚函数的做法。
重载,是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。
其实,重载的概念并不属于“面向对象编程”,重载的实现是:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。如,有两个同名函数:function func(p:integer):integer;和 function func(p:string):integer;。那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就已经确定了,是静态的(记住:是静态)。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!真正和多态相关的是“覆盖”。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态(记住:是动态!)的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚邦定)。结论就是:重载只是一种语言特性,与多态无关,与面向对象也无关!引用一句 Bruce Eckel 的话:“不要犯傻,如果它不是晚绑定,它就不是多态。”
那么,多态的作用是什么呢?我们知道,封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类);它们的目的都是为了——代码重用。而多态则是为了实现另一个目的——接口重用!多态的作用,就是为了类在继承和派生的时候,保证使用“家谱”中任一类的实例的某一属性时的正确调用。
# 多态的好处
- 增加程序的灵活性
- 增加程序的扩展性
可以增强程序的可扩展性及可维护性,使代码更加简洁。不但能减少编码的工作量,也能大大提高程序的可维护性及可扩展性。
# 继承与多态的区别
- 什么是继承,继承的特点?
子类继承父类的特征和行为,使得子类具有父类的各种属性和方法。或子类从父类继承方法,使得子类具有父类相同的行为。
特点:在继承关系中,父类更通用、子类更具体。父类具有更一般的特征和行为,而子类除了具有父类的特征和行为,还具有一些自己特殊的特征和行为。
在继承关系中。父类和子类需要满足 is-a 的关系。子类是父类。
- 为什么需要继承?什么时候应该继承?
使用继承可以有效实现代码复用,避免重复代码的出现。
当两个类具有相同的特征(属性)和行为(方法)时,可以将相同的部分抽取出来放到一个类中作为父类,其它两个类继承这个父类。
继承实现了面向对象的原则:write once,only once(编写一次、且编写一次
- 如何实现继承?
在父类中只定义一些通用的属性和方法。
子类自动继承父类的属性和方法,子类中可以定义特定的属性和方法。或子类重新定义父类的属性、重写父类的方法可以获得与父类不同的功能。
- 什么是方法重写?
如果在子类中定义的一个方法,其名称、返回类型及参数列表正好与父类中某个方法的名称、返回类型及参数列表相匹配,那么可以说,子类的方法重写了父类的方法。
#重写了bark
class Animal: #父类
def eat(self):
print("-----吃-----")
def drink(self):
print("-----喝-----")
class Dog(Animal):
def bark(self):
print("-----汪汪叫------")
class XTQ(Dog): #重写Dog方法
"""定义了一个哮天犬 类"""
def bark(self):
print("----嗷嗷叫-----")
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
方法重写在不同类,是实现多态的必要条件。
- super 关键字的用法和位置,super 关键字调用父类的构造方法,super 关键字调用父类的方法?
在子类的构造方法中,通过 super 关键字调用父类的构造方法。
如果子类中重写了父类的方法,可以通过 super 关键字调用父类的方法。
class PersonalInformation():
platform = 'python外卖平台'
def __init__(self, name, addr, tel):
self.name = name
self.addr = addr
self.tel = tel
# 商家类
class Merchants(PersonalInformation):
def __init__(self, name, addr, tel, food_info):
super().__init__(name, addr, tel) # 调用的是绑定方法,自动传入self
# super(Merchants, self).__init__(name, addr, tel) # 与上一行代码等价,在python2中必须这么写,python3可以简写
self.food_info = food_info
def receive_order(self):
print(f'{self.name}已经接单')
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
注意:创建对象时,先创建父类对象,在创建子类对象。如果没有显示调用父类的构造方法,将自动调用父类的无参构造方法。
- 一切类的老大(祖先)Object。
所有类都直接或者间接地继承了 object 类,object 类中定义了所有对象都具有的相同行为,是所有类的祖先。
一个类如果没有显式继承,那么这个类直接继承自 Object 类。
- 什么是多态?
多态的特征是表现出多种形态,具有多种实现方式。或者多态是具有表现多种形态的能力的特征。或者同一个实现接口,使用不同的实例而执行不同的操作。
- 如何实现多态?
一般做法是:写一个方法,它只接收父类作为参数,编写的代码只与父类打交道。调用这个方法时,实例化不同的子类对象(new 一个对象)。
更具体地说:
- 子类重写父类的方法。使子类具有不同的方法实现。
- 把父类类型作为参数类型,该父类及其子类对象作为参数转入,运行时根据实际创建的对象类型动态决定使用那个方法。