面向对象程序设计最主要的有三个特征: 封装、继承、多态
在我们程序开发过程中,定义好类型之后就可以通过类型来创建对象 如:我们定义一个中华人民共和国公民的类型
# 创建一个人的类型
class Person(object):
def __init__(name, age):
self.name = name
self.age = age
此时如果我们创建好对象之后,对对象的数据进行如下修改,大家是否认为合适呢?
# 创建一个人的对象
xiaoMing = Person("小明", 18)
# 修改属性
xiaoMing.age = 1000
我们会发现,上面的代码在运行时是正确的,也就是可以修改age属性为1000 此时我们需要明确一个概念:代码运行正确,但是不代表符合业务逻辑,这样的代码我们一般会说代码处理不合法!
对于上面这样的问题,我们应该怎么处理呢
常规的方案就是:
# 定义类型
class Person(object):
def __init__(self, name, age):
self.__name = name;
self.age = age
# 创建对象
xiaoMing = Person("小明明", 18)
# 访问属性
print(xiaoMing.age)
~ 18,可以访问,age只是一个普通的成员属性
print(xiaoMing.__name)
~ 出现错误,AttributeError: 'Person' object has no attribute '__name'
这样我们就限制了变量的访问范围。 但是变量定义出来就是为了被访问和操作的,如果上述代码中的name一旦限制了不让访问,就木有存在的价值了。所以,我们通过自定义的方法给name属性添加访问控制
# 创建一个人的类型
class Person(object):
# 对象的初始化方法
def __init__(self, name):
# 初始化私有成员变量__name
self.__name = name
# 定义获取__name的函数
def get_name(self):
return self.__name
# 定义设置__name的函数
def set_name(self, name):
self.__name = name
# 根据类型创建对象
xiaoMing = Person("小明");
# 访问数据
xiaoMing.set_name("小明明");
print(xiaoMing.get_name())
# 执行结果:小明明
OK,通过以上对属性进行私有化(属性名称双下划线开头),给属性提供set/get的访问方法完成封装过程,此时就可以对本文开头的年龄设置问题进行一定的逻辑限制了
# 定义一个人的类型
class Person(object):
# 初始化变量
def __init__(self, name, age):
self.__name = name
if(age >= 0 and age <= 100):
self.__age = age
else:
age = 18
# 定义访问属性的get方法
def get_name(self):
return self.__name
def get_age(self):
return self.__age
# 定义访问属性的get方法
def set_name(self, name):
self.__name = name;
def set_age(self, age):
if(age >= 0 and age <= 100):
self.__age = age
else:
print("您设置的年龄不合法,还原默认值")
# 创建对象
p = Person("张小凡", 19)
p.set_age(1000)
print(p.get_age())
# 执行结果
~ 您设置的年龄不合法,还原默认值
~ 19
以上执行的结果,才是我们想要的结果
什么是封装
封装,就是将对象敏感的数据封装在类的内部,不让外界直接访问,但是提供了让外界可以间接访问的set/get方法,我们可以在set/get方法中添加数据的访问限制逻辑,完善我们的代码,提高程序的健壮性
我们从上面的代码中已经看到了,可以通过函数操作的形式来进行属性的处理 但是某些情况下,函数操作的形式并不是特别美妙,我们突发奇想~想再提供了set/get访问方法的情况下,对属性的操作还能像以前那样直接赋值或者取值进行操作
# 封装以后通过函数操作的方式
p.set_name("tom")
print(p.get_name())
# 封装以前通过属性直接操作的方式
p.name = "tom"
print(p.name)
将类中的set/get方法操作的形式,转换成属性直接操作的形式,python中是可以的
首先:给get方法上添加@property
注解,(关于注解的东东,之前的函数装饰器章节中已经有使用,可以参考一下操作原理),就可以让get方法当成属性进行直接取值操作了
其次,@property
同时它会自动生成一个@get方法名称.sette
r注解,将@get方法名称.setter
注解写在set
方法上,就可以让set
方法进行直接赋值操作了,代码如下:
class Person(object):
def __init__(self, name):
self.__name = name;
@property
def get_name(self):
return self.__name
@get_name.setter
def set_name(self, name):
self.__name = name
# 创建对象
p = Person("tom")
print(p.get_name)
p.set_name = "jerry"
print(p.get_name)
# 执行结果
~ tom
~ jerry
上述代码我们可以看出来,set/get方法已经可以当成普通的属性取值赋值的方式进行快捷的操作了,我们继续改造一下上述代码,让set/get更加符合属性取值赋值的方式
class Person(object):
def __init__(self, name):
self.__name = name;
@property
def name(self):
return self.__name
@name.setter
def name(self, name):
self.__name = name
# 创建对象
p = Person("tom")
print(p.name)
p.name= "jerry"
print(p.name)
# 执行结果
~ tom
~ jerry
此时,你还能在不看原来类型定义中的get/set,区分出来name是否是Person类型的属性还是方法呢?
封装的注解方式,在一定程度上,能隐藏我们方法在底层的实现,让调用者的操作变得简单。但是同时也降低了代码的可读性,后续的操作中,我们还是遵循原来封装的操作方案将类的属性私有化,提供set/get方法进行属性的操作。
继承是让我们抽象的对象之间存在一定的所属关系 在继承关系中,我们一定要明确会出现这样的一种关系~父类、子类,子类继承自父类,可以继承父类中的公开的属性和方法(不能继承私有的属性或者方法)
其实我们在前面定义对象的时候已经使用过了继承,python中所有的对象都是直接或者间接继承自object对象的
class Person(object):
pass
上述代码中,我们定义了一个类型Person,这个Person后面有一个括号,括号中就是继承的类型;python中继承的语法是
class 类型名称(父类名称):
pass
下面是一个简单的继承的案例
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
# 定义一个人的类型
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
class Person(object):
def __init__(self, name, age, gender):
self.__name = name
self.__age = age
self.__gender = gender
# 定义get获取属性的方法
def get_name(self):
return self.__name
def get_age(self):
return self.__age
def get_gender(self):
return self.__gender
# 定义set设置属性的方法
def set_name(self, name):
self.__name = name
def set_age(self, age):
self.__age= age
def set_gender(self, gender):
self.__gender= gender
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
# 定义一个男人的类型,继承自Person类型
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
class Man(Person):
def __init__(self, name, age):
Person.__init__(self, name, age, "男")
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
# 定义一个女人的类型,继承自Person类型
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
class Women(Person):
def __init__(self, name, age):
Person.__init__(self, name, age, "女")
# 创建男人对象和女人对象,测试是否可以给属性进行赋值取值
m = Man("tom", 18)
w = Women("jerry", 16)
print(m.get_gender()) # 执行结果:男
print(w.get_gender()) # 执行结果:女
我们可以看到,在自定义类Man和Women中,只是简单定义了一个init方法,没有其他的代码,但是我们创建的Man类型的对象和Women类型的对象,却可以使用父类Person中定义的方法get_gender()以及其他,在一定程度上,简化了我们的开发,同时提高了程序的扩展性
继承:就是将一部分表示数据类型相似的类,抽取他们共同的属性和方法单独封装成父类,让这些类继承这个父类,实现代码的复用的同时提高程序的扩展性。
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
# 定义一个人的类型,男人是人,女人也是人
# 一个父类,多个子类
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
# 父类
class Person(object):
pass
# 子类
class Man(Person):
pass
# 子类
class Women(Person):
pass
某些情况下,我们生活中会出现这样的情况,一个小孩既是父亲的儿子,要具备儿子应该具备的功能,同时也是一个学生要具备学生应该具备的功能,此时就需要使用Python中的多继承来实现了
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
# 定义一个儿子的类型~要孝顺
# 定义一个学生的类型~要好好学习天天向上
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
class Son(object):
def fealty(self):
print("孝顺父母")
class Student(object):
def study(self):
print("好好学习,天天向上")
# 定义子类,继承儿子类型和学生类型
class Person(Son, Student):
pass
# 创建对象,使用父类的方法
p = Person()
p.fealty()
p.study()
# 执行结果
~ 孝顺父母
~ 好好学习,天天向上
# 父类
class Person(object):
def __init__(self, name, age):
self.__name = name
self.__age = age
def play(self):
print(self.__name + "在玩游戏")
# 子类
class Man(Person):
def __init__(self, name, age):
# 通过父类名称访问父类中初始化的方法
Person.__init__(self, name,age)
# 子类
class Women(Person):
def __init__(self, name, age):
# 通过super()访问父类初始化的方法
# super(Women, self).__init....super中的参数可以省略
super().__init__(self, name, age)
在子类继承自父类之后,可以直接使用父类中定义的公开的方法进行操作
# 父类
class Person(object):
def play(self):
print("Person中玩游戏的方法执行了...")
# 子类
class Children(Person):
pass
# 创建子类对象
c = Children()
c.play()
# 执行结果
~Person中玩游戏的方法执行了...
在子类中,我们可以重新编写继承自父类的方法
# 父类
class Person(object):
def play(self):
print("Person中玩游戏的方法执行了...")
# 子类
class Children(Person):
# 重写父类中play方法
def play(self):
print("Children中玩游戏的方法执行.....")
# 创建子类对象
c = Children()
c.play()
# 执行结果
~Children中玩游戏的方法执行.....
在继承关系下,在子类中将继承自父类的方法进行重新定义的过程,称为方法重写或者方法覆盖,经过重写或者覆盖的方法,子类执行该方法时,执行的就是重写过的方法。
多态是让我们的程序在运行的过程中,在不同的状态下进行动态的切换,实现复杂的功能为目的的一种程序开发手段
在之前的章节中,实现了类型的继承关系之后,其实我们已经见过多态的一种操作了:方法重写实现的运行时多态,对象在执行具体的方法时,会直接执行父类中继承的对应的方法,如果该方法在子类中重写了,就会执行子类中重写过的方法,实现的是一种运行过程中的多态处理,代码如下:
# 定义父类
class Person(object):
def __init__(self, name):
self.__name = name
def playing(self):
print(self.__name + "正在游戏中...")
# 定义子类,继承自Person
class Man(Person):
def __init__(self, name):
Person.__init__(self, name)
# 定义子类,继承自Person
class Women(Person):
def __init__(self, name):
Person.__init__(self, name)
def playing(self):
print(self.__name + "正在游戏封装找茬中...")
# 创建对象
man = Man("tom")
women = Women("jerry")
man.playing() # 子类中没有重写,直接执行从父类继承的playing()函数
women.playing() #子类中重写了,直接执行子类中的playing()函数
我们定义一个这样的医疗系统,所有的男人、女人、小孩等等都可以去医院看病,然后康复的一个过程。
# 定义一个人的类型
class Person(object):
def __init__(self, name, age, gender):
self.__name = name
self.__age = age
self.gender = gender
def health(self):
print(self.__name + "康复了..")
# 定义医院的类型
class Hospital(object):
# 定义一个治疗的方法,可以给人治病
def care(self, person):
print("正在治病")
person.health()
# 定义男人类型,继承自Person类型
class Man(Person):
def __init__(self, name, age):
Person.__init__(self, name, age, "男")
def health(self):
print(self.__name + "~介个男人康复了..")
# 定义女人类型,继承自Person类型
class Women(Person):
def __init__(self, name, age):
Person.__init__(self, name, age, "男")
def health(self):
print(self.__name + "~介个男人康复了..")
# 创建人物对象
man = Man("小凡", 19)
women = Women("碧瑶",16)
# 创建医院对象
h = Hospital()
# 治病救人
h.care(man) # 治疗Man类型的对象
h.care(women) # 治疗Women类型的对象
上面的代码中,我们已经可以看到,只要是从Person类型继承过来的类型所创建的对象,都可以在医院Hospital对象的care()中进行治疗。已经是一种多态。
同时如果功能需要扩展,需要多出来一个人物类型:小孩,小孩也会生病,也需要治疗~此时对于功能的扩展非常简洁,值需要添加如下代码就可以搞定:
# 创建一个小孩类型,继承自Person
class Children(Person):
def __init__(self, name):
Person.__init__(self, name)
# 创建具体的小孩对象
c = Children("小家伙")
h.care(c) # 执行结果~小家伙康复了
可以看到这里扩展一个功能变得非常的简单,对象和对象之间的协同关系由于继承多态的存在让功能的扩展实现起来比较快捷了。
上面的代码中,我们其实是存在一定的缺陷的 上述代码设计的初衷是医院对象可以治病救人,也就是Hosiptal对象的care()函数可以治疗Person派生出来的对象。 但是从代码逻辑中,我们可以看到只要传递给care()函数的参数对象中包含health()函数就可以进行处理,而并非必须是Person对象。
此时需要在函数中进行判断处理,如果是Person对象就进行care()治疗的处理,如果不是Person对象,就提示不做治疗操作。
对象和类型的判断可以通过isinstance(obj, Type)
进行类型的判断,如:
# 创建各种对象
lx = [1,2,3,4,5]
ld = {"1":"a", "2":"b"}
ls = {"1", "2", "3"}
man = Man("tom", 22)
women = Women("jerry", 21)
# isinstance(obj, Type)判断是否属于某种类型
isinstance(lx, list) # 执行结果:True
isinstance(ld, dict) # 执行结果:True
isinstance(ls, set) # 执行结果:True
isinstance(man, Man) # 执行结果:True
isinstance(women, Women) # 执行结果:True
isinstance(man, Person) # 执行结果:True
isinstance(women, Person) # 执行结果:True
isinstance(women, list) # 执行结果:False
上述代码中,我们可以观察到通过isinstance()
函数进行变量所属数据类型的判断了,同时在继承关系中,有一个情理之中的判断结果:man是Man类型的,同时也是Person类型的,因为Man类型是从Person类型继承过来的。
所以可以对之前的Hospital的care()函数进行如下改造:
# 改造Hospital对象
class Hospital(object):
# 改造care()函数进行处理
def care(person):
if isinstance(person, Person):
print("正在治疗中...")
person.health()
else:
print("不是合适的对象")
此时如果再传递参数,就要求必须是Person类型才可以进行治疗
# 定义一个Animal类型
class Animal :
def __init__(self, name):
self.__name = name
def health(self):
print(self.__name + "正在康复中...")
# 创建对象
a = Animal("shuke")
# 治疗
h.care(a)
# 执行结果:不是合适的对象
来源:http://www.jianshu.com/p/11e0e0fa0515 https://www.jianshu.com/p/2b1ebcc37ba1 http://www.jianshu.com/p/7263a799d7ea