一、谈谈你对面向对象的三大特性的理解
说明:要知道专业术语的表述和自己的理解!
二、各个击破
(1)封装
概念:将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象的内部信息,而是通过该类所提供的方法来实现对内部信息的操作和访问!
通俗理解:根据职责将属性和方法封装到一个抽象的类中,模块化编程!
思考:为什么封装?
封装数据的主要原因是:隐藏类的实现细节,保护隐私(作为男人的你,脸上就写着:我喜欢男人,你害怕么?)
封装方法的主要原因是:隔离复杂度(快门就是傻瓜相机为傻瓜们提供的方法,该方法将内部复杂的照相功能都隐藏起来)
提示:在编程语言里,对外提供的接口,就是函数,称为接口函数,这与接口的概念还不一样,接口代表一组接口函数的集合体。
思考:封装的层面?
封装其实分为两个层面,但无论哪种层面的封装,都要对外界提供好访问你内部隐藏内容的接口(接口可以理解为入口,有了这个入口,使用者无需且不能够直接访问到内部隐藏的细节,只能走接口,并且我们可以在接口的实现上附加更多的处理逻辑,从而严格控制使用者的访问)
接口:可以理解为API
层面1:什么都不用做,创建类和对象会分别创建二者的名称空间,我们只能用类名.或者obj.的方式去访问里面的名字,这本身就是一种封装!
注意:对于这一层面的封装(隐藏),类名.和实例名.就是访问隐藏属性的接口!
########分割线########
层面2:类中把某些属性和方法隐藏起来(私有),只在类的内部使用(外部无法访问)或者留下少量接口(函数)供外部访问。
私有变量:private
私有变量:可以把属性的名称前加上两个下划线__,如果要让内部属性不被外部访问!
私有方法:可以把实例方法的名称前加上两个下划线__,只能内部访问!
对象的状态:数据和行为!
class Person:
# (1)定义一个私有的成员类变量-->"__"开头
__male = 'female'
# (2)给外部提供接口来访问内部的私有数据
def getmale(self):
return self.__male
# (3)定义一个私有的方法
def __fun(self):
print('我是私有的方法')
# (4)函数的相互调用
def call(self):
self.__fun()
# 测试1:访问私有类变量
# 错误的方式
# print(Person.__male)
# 方式1:给外界提供接口的方式
print(Person().getmale())
# 方式2:通过对象直接暴力读取--->Python没有提供真正的隐藏机制
print(Person()._Person__male)
# 实质(上面的底层也是这样调用的): 单下划线+类名+__变量名
# 关于修改省略!
# 明确对于对象赋值的含义!
# 测试2:访问私有方法-->同上!
封装的含义:该隐藏的隐藏,该暴露的暴露,怎么感觉有点污了~~~
(2)继承
继承的理解:我的理解就是继承家产,少部分无法继承(没有达到遗嘱的要求)
派生和扩展的理解;一般和个性的理解
多继承:区别于Java的单继承!
继承目的:继承实现了代码复用,相同的代码不需要重复的编写!
结果:子类继承了父类,继承了父类定义的共有属性和的方法!
细节:python2.x版本,必须显示的继承object,而python3.x默认继承object(超类或者基类)
练习1:继承关于方法的练习
class Parent(object):
# 类变量
x = 1
class Child1(Parent):
pass
class Child2(Parent):
pass
print(Parent.x, Child1.x, Child2.x) # 1 1 1
# 说明:给指定对象添加属性,不影响类变量
Child1.x = 2
print(Parent.x, Child1.x, Child2.x) # 1 2 1
# 说明:修改类变量的数值,在继承的过程中,类变量中没有此变量就使用继承的类变量
Parent.x = 3
print(Parent.x, Child1.x, Child2.x) # 3 2 3
考点:继承的过程中类变量同名如何抉择!
练习2:调用被重写的方法
方法1:使用未绑定的方法调用(通过类名来调用,需要自己传递对象)
方式2:使用super函数来调用
构造方法
class Animal:
def __init__(self,name):
self.name=name
print(name)
def drink(self):
print('喝水')
def eat(self):
print('吃东西')
class Cat(Animal):
# 原因:自定义之后,就不会采用默认的方法了!
# def __init__(self):
# super().__init__()
def __init__(self):
# 默认(先创建父类对象-->利用其父类的构造方法):super
print('想唱歌')
# 说明:位置不一定要放到首位置!
super().__init__('校花')
继承创建对象的过程:先利用父类的构造方法创建父类对象,再创建子类对象!
一般的方法重写
class Animal:
def drink(self):
print('喝水')
def eat(self):
print('吃东西')
class Cat(Animal):
def run(self):
print("跑的块")
class Kitty(Cat):
def eat(self):
# 调用基类的方法(不一定是直接基类)-->方法重写!
# 说明:严格意义说是扩展!
super().run()
print('加速')
Kitty().eat()
说明:我们知道子类和父类是共性和个性的关系,子类针对自己特有的需求,此时就需要重写!
练习3:多继承的练习
class A:
def test(self):
print('A1')
def tt(self):
print('A2')
class B:
def test(self):
print('B1')
def tt(self):
print('B2')
# 说明:变化顺序!
class C(A,B):
pass
C().test()
C().tt()
# object(python2.和python3.x的区别)
说明:多继承中是有先后顺序的,只有自己没有,父类1也没有,才会从父类2找!
(3)多态
多态概念:不同的子类对象调用相同的父类方法,产生不同的执行结果!
说明:具体呈现哪种行为由该变量所引用的对象来确定!
优点:多态可以增加代码的灵活度
前提:以继承和重写父类方法为前提
备注:是调用方法的技巧,不会影响到类的内部设计!
练习
class Dog(object):
def __init__(self,name):
self.name = name
def game(self):
print('%s 开心的玩~' %(self.name))
class Gaofei(Dog):
# 父类方法不能满足子类的需求,重写game方法
def game(self):
print('%s和米老鼠一起玩~~~' %(self.name))
# 说明:wc变量被赋值为Gaofei对象
wc = Gaofei('高飞')
wc.game()
# 说明:wc变量被赋值为Dog对象!
wc = Dog('大黄')
wc.game()
# 多态:wc指向对象不同,呈现不同的行为特征!
关于多态的两个方法
1)issubclass(cls,class or tuple) -->判断cls是否是某个类的子类或元组中的子类
2)isinstance(obj,class or tuple) -->判断obj是否是某个类的子类或元组中的子类对象
执行逻辑:先检查,再调用方法!
注意:区别于Java的设计模式!
(4)设计模式
4.1)单例模式
首先探讨下:对象的创建过程
1)为对象在内存中分配空间 --->__new__方法是类从object中继承过来的!
2)调用构造方法为对对象进行初始化 ---->__init__构造方法初始化
练习:单例模式
class MusicPlayer(object):
instance = None
def __new__(cls, *args, **kwargs):
# 判断类属性是否是空对象
if cls.instance is None:
# 调用父类方法 为第一个对象分配空间
cls.instance = object.__new__(cls)
return cls.instance
player1 = MusicPlayer()
player2 = MusicPlayer()
player3 = MusicPlayer()
print(player1)
print(player2)
print(player3)
说明:数据库的实例就是一个单例对象!
(5)其它概念
新式类:默认以object为基类的类,存活于python3.X
经典类:不以object为基类的类,存活于python2.x
object:是Python为所有对象提供的基类,提供有一些内置的属性和方法,可以使用dir函数查看
在python3.X中定义的类时,如果没有指定父类,会默认使用object作为基类--python3.x中定义的类都是新式类
在python2.x中定义类时,如果没有指定父类,则不会以object作为基类
兼容性建议:为保证编写的代码能够同时在python2.x和python3.x运行,今后在定义类时,建议统一继承自object!