面向对象编程
OOP编程是利用“类”和“对象”来创建各种模型来实现对真实世界的描述,使用面向对象编程的原因一方面是因为它可以使程序的维护和扩展变得更简单,并且可以大大提高程序开发效率 ,另外,基于面向对象的程序可以使它人更加容易理解你的代码逻辑,从而使团队开发变得更从容。
面向对象的几个核心特性如下
Class 类
一个类即是对一类拥有相同属性的对象的抽象、蓝图、原型。在类中定义了这些对象的都具备的属性(variables(data))、共同的方法
Object 对象
一个对象即是一个类的实例化后实例,一个类必须经过实例化后方可在程序中调用,一个类可以实例化多个对象,每个对象亦可以有不同的属性,就像人类是指所有人,每个人是指具体的对象,人与人之前有共性,亦有不同
Encapsulation 封装
在类中对数据的赋值、内部调用对外部用户是透明的,这使类变成了一个胶囊或容器,里面包含着类的数据和方法
Inheritance 继承
一个类可以派生出子类,在这个父类里定义的属性、方法自动被子类继承
Polymorphism 多态
多态是面向对象的重要特性,简单点说:“一个接口,多种实现”,指一个基类中派生出了不同的子类,且每个子类在继承了同样的方法名的同时又对父类的方法做了不同的实现,这就是同一种事物表现出的多种形态。
编程其实就是一个将具体世界进行抽象化的过程,多态就是抽象化的一种体现,把一系列具体事物的共同点抽象出来, 再通过这个抽象的事物, 与不同的具体事物进行对话。
对不同类的对象发出相同的消息将会有不同的行为。比如,你的老板让所有员工在九点钟开始工作, 他只要在九点钟的时候说:“开始工作”即可,而不需要对销售人员说:“开始销售工作”,对技术人员说:“开始技术工作”, 因为“员工”是一个抽象的事物, 只要是员工就可以开始工作,他知道这一点就行了。至于每个员工,当然会各司其职,做各自的工作。
多态允许将子类的对象当作父类的对象使用,某父类型的引用指向其子类型的对象,调用的方法是该子类型的方法。这里引用和调用方法的代码编译前就已经决定了,而引用所指向的对象可以在运行期间动态绑定
用OOP中的“类”来实现CS代码如下:
1 class Role(object): 2 def __init__(self,name,role,weapon,life_value=100,money=15000): 3 self.name = name 4 self.role = role 5 self.weapon = weapon 6 self.life_value = life_value 7 self.money = money 8 9 def shot(self): 10 print("shooting...") 11 12 def got_shot(self): 13 print("ah...,I got shot...") 14 15 def buy_gun(self,gun_name): 16 print("just bought %s" %gun_name) 17 18 r1 = Role('Alex','police','AK47’) #生成一个角色 19 r2 = Role('Jack','terrorist','B22’) #生成一个角色
- 代码量少了近一半
- 角色和它所具有的功能可以一目了然看出来
1 class Role(object): #定义一个类, class是定义类的语法,Role是类名,(object)是新式类的写法,必须这样写,以后再讲为什么 2 def __init__(self,name,role,weapon,life_value=100,money=15000): #初始化函数,在生成一个角色时要初始化的一些属性就填写在这里
#构造函数
#在实例化时做一些类的初始化工作
3 self.name = name #__init__中的第一个参数self,和这里的self都是什么意思? 看下面解释 4 self.role = role #实例变量,作用域就是实例本身; 5 self.weapon = weapon 6 self.life_value = life_value 7 self.money = money
上面的这个__init__()叫做初始化方法(或构造方法), 在类被调用时,这个方法(虽然它是函数形式,但在类中就不叫函数了,叫方法)会自动执行,进行一些初始化的动作,所以我们这里写的__init__(self,name,role,weapon,life_value=100,money=15000)就是要在创建一个角色时给它设置这些属性,那么这第一个参数self是干毛用的呢?
1 r1 = Role('Alex','police','AK47’) #生成一个角色 , 会自动把参数传给Role下面的__init__(...)方法 2 r2 = Role('Jack','terrorist','B22’) #生成一个角色
我们看到,上面的创建角色时,我们并没有给__init__传值,程序也没未报错,是因为,类在调用它自己的__init__(…)时自己帮你给self参数赋值了,
1 r1 = Role('Alex','police','AK47’) #此时self 相当于 r1 , Role(r1,'Alex','police','AK47’) 2 r2 = Role('Jack','terrorist','B22’)#此时self 相当于 r2, Role(r2,'Jack','terrorist','B22’)
- 在内存中开辟一块空间指向r1这个变量名
- 调用Role这个类并执行其中的__init__(…)方法,相当于Role.__init__(r1,'Alex','police',’AK47’),这么做是为什么呢? 是为了把'Alex','police',’AK47’这3个值跟刚开辟的r1关联起来,是为了把'Alex','police',’AK47’这3个值跟刚开辟的r1关联起来,是为了把'Alex','police',’AK47’这3个值跟刚开辟的r1关联起来,重要的事情说3次, 因为关联起来后,你就可以直接r1.name, r1.weapon 这样来调用啦。所以,为实现这种关联,在调用__init__方法时,就必须把r1这个变量也传进去,否则__init__不知道要把那3个参数跟谁关联呀。
- 明白了么哥?所以这个__init__(…)方法里的,self.name = name , self.role = role 等等的意思就是要把这几个值 存到r1的内存空间里。
1 def buy_gun(self,gun_name): 2 print(“%s has just bought %s” %(self.name,gun_name) )
上面这个方法通过类调用的话要写成如下:
1 r1 = Role('Alex','police','AK47') 2 r1.buy_gun("B21”) #python 会自动帮你转成 Role.buy_gun(r1,”B21")
- 上面的这个r1 = Role('Alex','police','AK47’)动作,叫做类的“实例化”, 就是把一个虚拟的抽象的类,通过这个动作,变成了一个具体的对象了, 这个对象就叫做实例
- 刚才定义的这个类体现了面向对象的第一个基本特性,封装,其实就是使用构造方法将内容封装到某个具体对象中,然后通过对象直接或者self间接获取被封装的内
1 class Role(object): n=123; #类变量,存在类的内存里 name = "我是类name" 2 def __init__(self,name,role,weapon,life_value=100,money=15000): #构造函数 #在实例化时做一些类的初始化工作 3 self.name = name #实例变量(静态属性),作用域就是实例本身 4 self.role = role 5 self.weapon = weapon 6 self.life_value = life_value 7 self.money = money def shot(self): #类的方法,功能(动态属性) print("I'm shooting") r2 = Role('Jack','terrorist','B22') #r2叫对象,也叫类的实例 print(r2.n,r2.name) #输出123,Jack #r2.name为什么输出为Jack,而r2.n输出确是123? 因为运行时会先在实例本身查找,如果没有再去类里面找 r1 = Role('wangsan','police','AK47') r2.bullet_prove = true #给r2在外部添加一个属性,可以吗? #可以,就相当于在构造函数执行r2.bullet)prove = true,但是结果只改变r2这个实例 del r2.weapon #删除r2的weapon属性,只改变r2 r1.n = "改变类变量" #外部改变类变量可以吗? 可以,相当于不会改变类变量,而是给r1新增一个属性 n = 改变类变量 print("r1:"r1.n) #r1:改变类变量 print("r2:"r2.n) #r2:123
类变量的用途?
大家共用的属性,节省开销
析构函数(析构方法):
在实例释放、销毁的时候自动执行的,通常用于一些收尾工作,如关闭一些数据库连接,关闭打开的临时文件
1 class Battle: 2 def __init__(self,name,role,weapen): 3 self.name = name 4 self.role = role 5 self.weapen = weapen 6 7 def buy_gun(self,gun_name): 8 print("%s has buyed %s"%(self.name,gun_name)) 9 10 def got_gun(self): 11 print("%s has got gun" % self.name) 12 13 def __del__(self): #析构函数 14 print("%s has daed...." %self.name) 15 16 r1 = Battle("June","Torrest","AK47") 17 r1.buy_gun("b51") 18 19 r2 = Battle("Tom","Police","MP4")
输出:
可以看到 r1在执行完后,实例自动消亡,r2也是自动消亡了
私有属性和私有方法
普通属性和方法前面加上__(双下划线)即变为私有属性和私有方法
1 class Battle: 2 def __init__(self,name,role,weapen,life_value=100): 3 self.name = name 4 self.role = role 5 self.weapen = weapen 6 self.__life_value = life_value #定义私有属性,在变量前面加__,私有方法同理 7 8 def ShowLifeValue(self): 9 self.__life_value -= 50 10 print("剩余生命值为%s"%self.__life_value) #可以通过定义方法访问,私有属性在内部可以改 11 12 def buy_gun(self,gun_name): 13 print("%s has buyed %s"%(self.name,gun_name)) 14 15 def got_gun(self): 16 print("%s has got gun" % self.name) 17 18 def __del__(self): 19 print("%s has daed...." %self.name) 20 21 r1 = Battle("June","Torrest","AK47") 22 r1.buy_gun("b51") 23 print(r1.__life_value) #ERROR AttributeError: 'Battle' object has no attribute '__life_value'
继承
1 #Author: ls Liu 2 class Person(object): 3 def __init__(self,name,age): 4 self.name = name 5 self.age = age 6 7 def run(self): 8 print("%s is running" %self.name) 9 10 def speak(self): 11 print("%s is speaking" %self.name) 12 13 def sleep(self): 14 print("%s is sleeping" %self.name) 15 16 class Relation(object): 17 def make_friends(self,obj): 18 print("%s is making friends with %s" %(self.name,obj.name)) 19 20 class Man(Relation,Person): 21 def __init__(self,name,age,money): #完全重构,覆盖,那么问题来了?覆盖后用户实例化还调父类的构造方法吗?不是,是直接调子类的构造方法 22 #Person.__init__(self,name,age) #父类可能存在很多初始化 23 super(Man,self).__init__(name,age) 24 self.money = money 25 print("%s 一出生就是有%s钱" %(self.name,self.money)) 26 27 def play(self): 28 print("%s is playing ball" %self.name) 29 30 class Woman(Person,Relation): 31 def get_birth(self): 32 print("%s is got a baby" %self.name) 33 34 m1 = Man('Jack',22,100) 35 #m1.speak() 36 37 w1 = Woman('Rose',26) 38 #w1.get_birth() 39 40 m1.make_friends(w1) 41 42 #问题一、Relation类没写构造函数,也没给它传name参数,它怎么可以直接调用self.name,obj.name? 43 #答:name,已经从Person继承了,就不用在重复从其他类继承,只要继承一些新功能,还是把m1传进去,此时m1已经有名字了,所以可以调m1.name 44 45 #问题二、那是因为先继承的是Person,如果先继承Relation会怎样? 46 #答:真正生成名字不是在Person生成的,在Man构造就生成了;本来要执行Person的__init()__,现在执行自己的__init()__,所在在执行其他方法时,name已经有了。 47 48 #问题三、那如果子类没有自己的构造方法呢?先找Relation,后找Person,Relation时候还没有name 49 #答:实例化时先找Relation的构造方法,Relation没有构造方法按道理就报错了,注意make_friends()还没执行;Relation里没有构造方法就去Person里找,这时make_friends()还没执行 50 #没执行,有没有这个变量无所谓,只有调用的时候才判断有没有这个变量,真正调用时早实例化完了。
面试题:新式类和经典类区别在哪里?(加object是新式类)
#体现在多继承顺序上
#py2 经典类是按深度优先来继承的,新式类是按广度优先来继承的;
#py3 经典类和新式类都是统一按广度优先来继承的(横向查找一遍,再往上级查找)
下图红色为广度优先,绿色为深度优先
继承练习:
1 #Author: ls Liu 2 class School(object): 3 def __init__(self,name,addr): 4 self.name = name 5 self.addr = addr 6 self.students = [] 7 self.staffs = [] 8 9 def enroll(self,stu_obj): 10 print("为学员%s 办理注册手续" %stu_obj.name) 11 self.students.append(stu_obj) 12 13 def hire(self,staff_obj): 14 print("雇佣新员工%s"%staff_obj.name) 15 self.staffs.append(staff_obj) 16 17 class SchoolMember(object): 18 def __init__(self,name,age,sex): 19 self.name = name 20 self.age = age 21 self.sex = sex 22 23 def tell(self): 24 pass 25 26 class Teacher(SchoolMember): 27 def __init__(self,name,age,sex,salary,course): 28 super(Teacher,self).__init__(name,age,sex) 29 self.salary = salary 30 self.course = course 31 32 def tell(self): 33 print(''' 34 ----info of Teacher:%s---- 35 Name:%s 36 Age;%s 37 Sex:%s 38 Salary:%s 39 Course:%s 40 ''' %(self.name,self.name,self.age,self.sex,self.salary,self.course)) 41 42 def teach(self): 43 print("%s is teaching course %s"%(self.name,self.course)) 44 45 class Student(SchoolMember): 46 def __init__(self,name,age,sex,stu_id,grade): 47 super(Student,self).__init__(name,age,sex) 48 self.stu_id = stu_id 49 self.grade = grade 50 51 def tell(self): 52 print(''' 53 ----info of Student:%s---- 54 Name:%s 55 Age;%s 56 Sex:%s 57 Stu_id:%s 58 Grade:%s 59 ''' % (self.name, self.name, self.age, self.sex, self.stu_id, self.grade)) 60 61 def pay_turtion(self,amount): 62 print("%s has pad for ¥%s" %(self.name,amount)) 63 64 school = School("老男孩IT","沙河") 65 t1 = Teacher("OldBoy",56,"M",150000,"Linux") 66 t2 = Teacher("Alex",22,"M",50000,"Python") 67 68 s1 = Student("wangsan",23,"M",1001,"Linux") 69 s2 = Student("zhangbing",23,"M",1001,"Python") 70 71 t1.tell() 72 s1.tell() 73 74 75 school.enroll(s1) 76 school.hire(t1) 77 print(school.students) 78 print(school.staffs) 79 school.staffs[0].teach()
多态
一种接口,多种实现(形态)
本节作业: 选课系统
角色:学校、学员、课程、讲师
要求:
1. 创建北京、上海 2 所学校
2. 创建linux , python , go 3个课程 , linux\py 在北京开, go 在上海开
3. 课程包含,周期,价格,通过学校创建课程
4. 通过学校创建班级, 班级关联课程、讲师
5. 创建学员时,选择学校,关联班级
5. 创建讲师角色时要关联学校,
6. 提供两个角色接口
6.1 学员视图, 可以注册, 交学费, 选择班级,
6.2 讲师视图, 讲师可管理自己的班级, 上课时选择班级, 查看班级学员列表 , 修改所管理的学员的成绩
6.3 管理视图,创建讲师, 创建班级,创建课程
7. 上面的操作产生的数据都通过pickle序列化保存到文件里