面向对象编程三大特性(封装、继承、多态)
在介绍面向对象编程三大特性之前,我们首先需要清楚什么是对象和类,那么先来介绍一下对象和类的概念。
注意:C语言属于面向过程的语言,而C++、JAVA、python都属于面向对象的语言。
一、 对象和类
1.定义:
==类(Class)==是现实或思维世界中的实体在计算机中的反映,它将数据以及这些数据上的操作封装在一起。是一个抽象画的概念。
==对象(Object)==是具有类型的变量,即:将类实例化/具体化产生的值。类和对象是面向对象编程技术中的最基本的概念。
比如:类和对象 的区别就是鱼和三文鱼的区别; 猫和蓝猫的区别。
2.如何定义类?
class 类的名称:
pass
如:
#class 类的名称:
class Person: #定义类的方式
pass #占位关键字,什么也不做
print(Person)
#运行结果
<class '__main__.Person'> #表示存储于当前脚本的Person类。__main__表示当前脚本
- 如何将类转换成对象?
实例化是指在面向对象的编程中,把用类创建对象的过程称为实例化。
是将一个抽象的概念类,具体到该类实物的过程。
实例化过程中一般由类名对象名 = 类名(参数1,参数2…参数n)构成。
如下:
# 对象:将类实例化/具体化产生的值
personObj = Person()
print(personObj) # 当前脚本的Person类实例化出来的对象存储的内存地址是0x7f28164b04d0
#运行结果:
<__main__.Person object at 0x7f28164b04d0>
二、封装特性
1.封装的定义:
封装,顾名思义就是将内容封装到某个地方,以后再去调用被封装在某处的内容。
=通俗来讲就是将对象和属性绑定在一起的操作,用对象进行调用。
所以,在使用面向对象的封装特性时,需要:
1). 将内容封装到某处
2). 从某处调用被封装的内容
调用被封装内容的方式有:
1). 通过对象直接调用被封装的内容:对象名.属性名
2). 通过self间接调用被封装的内容:self.属性名
3). 通过self间接调用被封装的内容: self.方法名()
如:案例:实现数据的封装:
lass Student:
def __init__(self, name, score1, score2):
# 将对象和属性封装在一起
self.name = name
self.score1 = score1
self.score2 = score2
def compute_sum_score(self):
# 获取封装的属性信息方法一: 通过self.属性名的方式获取。
return self.score1 + self.score2
stu1 = Student(name="小红", score1=100, score2=99)
# 获取封装的属性信息方法二: 通过对象名.属性名的方式获取。
print("学生姓名: ", stu1.name)
sum_scores = stu1.compute_sum_score()
print("总分数: ", sum_scores)
构造方法__init__与其他普通方法不同的地方在于,在创建对象(或实例化对象)时会立即调用构造方法。自动执行构造方法里面的内容。
例如:
class Student:
# 实例化对象的过程中自动执行的函数
def __init__(self): # self是形参
# self实质上实例化出来的对象。系统自动将实例化的对象传递给构造方法。
print("self: ", self)
print("正在运行构造方法........")
print(Student) # <class '__main__.Student'>
# 实例化产生对象的过程
stu1 = Student()
print("stu1: ", stu1)
#打印结果:
<class '__main__.Student'>
self: <__main__.Student object at 0x000001B0A0D76CC0>
正在运行构造方法........
stu1: <__main__.Student object at 0x000001B0A0D76CC0>
2.封装的特性
== 对于面向对象的封装来说,其实就是使用构造方法将内容封装到对象中,然后通过对象直接或者self间接获取被封装的内容。==
1)【好处】
将变化隔离;
便于使用;
提高复用性;
提高安全性;
2)【封装原则】
将不需要对外提供的内容都隐藏起来;
把属性都隐藏,提供公共方法对其访问。
封装练习:创建一个类People,拥有的属性为姓名, 性别和年龄, 拥有的方法为购物,玩游戏,学习;实例
化对象,执行相应的方法。 显示如下:
小明,18岁,男,去西安赛格购物广场购物
小王,22岁,男,去游乐场玩耍
小红,10岁,女,在学校学习
提示:
属性:name,age,gender
方法:shopping(), playGame(), learning()
代码如下:
class People:
def __init__(self, name, gender, age):
self.name = name
self.gender = gender
self.age = age
def shopping(self):
print("%s,%d岁,%s,去西安赛格购物广场购物" %(self.name,self.age,self.gender))
def playGame(self):
print("%s,%d岁,%s,去游乐场玩耍" %(self.name,self.age,self.gender))
def learning(self):
print("%s,%d岁,%s,在学校学习" % (self.name, self.age, self.gender))
People1=People(name="小明",gender="男",age=18)
People2=People(name="小王",gender="男",age=22)
People3=People(name="小红",gender="女",age=10)
People1.shopping()
People1.learning()
People3.playGame()
三、 继承特性
1.继承特性
==继承描述的是事物之间的所属关系,==当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类、扩展类(Subclass),而被继承的class称为基类(Baseclass)、父类或超类(Superclass)。
2.如何实现继承?
子类在继承的时候,在定义类时,小括号()中为父类的名字
3.继承的工作机制是什么?
会将父类的属性、方法,会被继承给子类。
1)举例如下: 如果子类没有定义__init__方法,父类有, 那么在子类继承父类的时候这个方法就被继承了,所以只要创建对象,就默认执行了那个继承过来的__init__方法。
2)调用父类的方法:
1>父类名.父类的方法名()
2>super(): py2.2+的功能
class Student(object):
def __init__(self,name,age,gender):
self.name=name
self.age=age
self.gender=gender
def learn(self):
print("%s正在学习编程...." %(self.name))
# 父类: Student 子类: MathStudent,EnglishStudent
# 如果子类没有定义__init__方法,父类有,
# 那么在子类继承父类的时候这个方法就被继承了,所以只要创建对象,就默认执行了那个继承过来的__init__方法。
class MathStudent(Student):
pass
class EnglishStudent(Student):
pass
s1=Student("小明",10,'male')
print(s1.name,s1.age,s1.gender)
s2=MathStudent("塞尔斯基",100,'male')
s2.learn() #子类没有定义__init__方法,所以会直接继承父类的__init__方法
s1.learn()
4.重写父类方法:
就是子类中,有一个和父类相同名字的方法,在子类中的方法会覆盖掉父类中同名的方法。
如下:
class Student(object): #父类
def __init__(self,name,age,gender):
self.name=name
self.age=age
self.gender=gender
def learn(self):
print("%s正在学习编程...." %(self.name))
class MathStudent(Student): #子类
def learn(self):
super(MathStudent,self).learn()
print("%s正在学习英语四级......"%(self.name))
s1=Student("小明",10,'male')
print(s1.name,s1.age,s1.gender)
s2=MathStudent("塞尔斯基",100,'male')
s2.learn() #既打印了父类的learn方法,也打印了子类的learn方法
s1.learn()
5.多继承
即子类有多个父类,并且具有它们的特征
如骡子的父类有马和驴
#继承的顺序可以通过print(Student.mro)即(print(子类名.mro)进行查看:
class TeacherMajor(object): #父类1
def __init__(self, students_count):
self.student_count = students_count
class DoctorMajor(object): #父类2
def __init__(self, patients_count):
self.patients_count = patients_count
# 子类Student拥有2个父类TeacherMajor和DoctorMajor
class Student(TeacherMajor, DoctorMajor): #子类
def __init__(self, name, students_count, patients_count):
self.name = name
TeacherMajor.__init__(self, students_count)
DoctorMajor.__init__(self, patients_count)
# # 继承的顺序如何查看?
print(Student.__mro__)
stu1 = Student("粉条", 90, 95)
print(stu1.patients_count)
print(stu1.student_count)
#打印结果:
(<class '__main__.Student'>, <class '__main__.TeacherMajor'>, <class '__main__.DoctorMajor'>, <class 'object'>)
95
90
6.新式类与经典类
在Python 2及以前的版本中,由任意内置类型派生出的类,都属于“新式类”,都会获得所有“新式类”的特性;反之,即不由任意内置类型派生出的类则称之为“经典类”。
1)新式类定义:
class 类名(object):
pass
2)经典类定义:
class 类名:
pass
“新式类”和“经典类”的区分在Python 3之后就已经不存在,在Python3.x之后的版本,因为所有的类都派生自内置类型object(即使没有显示继承object类型),即所有的类都是“新式类”。==
3)二者区别:
最明显的区别在于继承搜索的顺序不同,即:经典类多继承搜索顺序(深度优先算法):先深入继承树左侧查找,然后再返回,开始查找右侧。新式类多继承搜索顺序(广度优先算法):先在水平方向查找,然后再向上查找。
7.私有属性与私有方法
默认情况下,属性在 Python 中都是“public”, 大多数 OO 语言提供“访问控制符”来限定成员函数的访问。
在 Python中,实例的变量名如果以 __ 开头,就变成了一个私有变量/属性(private),
实例的函数名如果以 __开头,就变成了一个私有函数/方法(private)
被“访问控制符”限定的属性或者方法只有内部可以访问,外部不能访问。
如:
# 私有属性和私有方法:
class Student(object):
def __init__(self, name, age, score):
self.name = name
self.age = age
# self.__score是私有属性, 只能在类的内部访问, 类的外部不可以访问。
self.__score = score
# __get_level是私有方法,只能在类的内部访问, 类的外部不可以访问。
def __get_level(self):
if 90 <= self.__score <= 100:
return "优秀"
elif 80 <= self.__score < 90:
return "良好"
elif 60 <= self.__score < 80:
return "及格"
else:
return "不及格"
stu1 = Student("粉条", 10, 59)
print(stu1.__score)
print("学生分数的等级: ", stu1.__get_level())
#打印结果:
print("学生分数的等级: ", stu1.__get_level())
AttributeError: 'Student' object has no attribute '__get_level'
可见,给属性和方法加__变成私有属性和私有方法之后,从外部反复问会失败,那么,我们如果还是想访问该怎么解决呢?
很简单:给修改过的属性或者方法前面加上_类名,就又可以访问到了。
即:python2版本不能直接访问 __属性名 是因为 Python 解释器对外把 __属性名 改成了 _类名__属性名 ,
所以,仍然可以通过 _类名__属性名 来访问 __属性名 。 因为不同版本的 Python 解释器可能会把 __属性名 改成不同的变量名。
如:
class Student(object):
def __init__(self, name, age, score):
self.name = name
self.age = age
# self.__score是私有属性, 只能在类的内部访问, 类的外部不可以访问。
self.__score = score
# __get_level是私有方法,只能在类的内部访问, 类的外部不可以访问。
def __get_level(self):
if 90 <= self.__score <= 100:
return "优秀"
elif 80 <= self.__score < 90:
return "良好"
elif 60 <= self.__score < 80:
return "及格"
else:
return "不及格"
stu1 = Student("小明", 10, 99)
# print(stu1.__score) #私有属性,外部无法访问
# print("学生分数的等级: ", stu1.__get_level()) #私有方法,外部无法访问
# Python解释器自动将私有属性和私有方法重命名了, 命名方式一般是_类名__属性名、_类名__方法名。(此时就可以访问了)
print(stu1._Student__score)
print(stu1._Student__get_level())
私有属性与私有方法优势
确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护, 代码更加健壮。
如果又要允许外部代码修改属性怎么办?可以给类增加专门设置属性方法。
为什么大费周折?因为在方法中,可以对参数做检查,避免传入无效的参数。
多态特点:
多态的好处就是:
当我们需要传入更多的子类,只需要继承父类就可以了,而方法既可以直接不重写(即使用父类的),也可以重写一个特有的。
调用方只管调用,不管细节,而当我们新增一种的子类时,只要确保新方法编写正确,而不用管原来的代码。这就是著名的“开闭”原则:
对扩展开放(Open for extension):允许子类重写方法函数。
对修改封闭(Closed for modification):不重写,直接继承父类方法函数。