day22 封装、继承和程序设计模式
今日内容概要
- 封装
- 继承
- 程序设计模式
- 单例模式
- 工厂模式
昨日回顾
一对多组合关系
- 将多个类的对象作为属性,封装在新类中
继承
class 类名(父类名): 属性 方法
- isinstance和issubclass
- 子类会继承所有父类中的属性和方法,可以直接使用
- 子类可以重写父类中的属性和方法
- super方法可以在子类中调用父类中的方法
super(cls, obj)
,cls参数可以为任意类名,obj可以为任意类,只要合理即可
今日内容详细
封装
“封装”就是将抽象得到的数据和行为相结合,形成一个有机整体
- 元组,列表,字典等等:数据的封装,通过引用去使用数据
- 函数:算法的封装
- 如果没有函数,功能要靠每一行代码去直接执行
- 没有函数的话,耦合度会很高,复用性很差,开发效率也非常低
封装的目的是简化编程和增加安全性
- 使用者不必关系具体的实现细节
- 通过接口(万能的点
.
) - 还可以给予特定的访问权限来使用类的成员
- 明确区分内外
类的实现者
可以修改内部封装的东西而不影响外部调用者外部调用者
只需要直到自己可以使用该类对象的哪些功能
私有属性和私有方法
- 标识符以双下划线
__
开头的是私有成员,在外部不可以直接用这种属性或方法名调用,子类中也不能访问到这个数据 - 可以提供外界访问的接口
- 将不需要对外提供的内容都隐藏起来
- 把属性都隐藏,提供公共方法对其访问
- 双下划线开头的属性在继承给子类时,子类是无法覆盖的
私有方法示例:
class Person: def __init__(self, name, age): self.__name = name self.__age = age def get_age(self): return self.__age def set_age(self, age): self.__age = age xiaoming = Person('小明', 17) # print(xiaoming.__age) # 程序会报错,找不到__age属性 print(xiaoming.get_age()) xiaoming.set_age(20) print(xiaoming.get_age()) 输出的结果为: 17 20
私有属性和私有方法并不是绝对不可以在外部访问和修改。我们可以通过在私有属性或方法名前加上_类名
,就可以调用私有属性和方法了:
class Person: def __init__(self, name, age): self.__name = name self.__age = age def get_age(self): return self.__age def set_age(self, age): self.__age = age xiaoming = Person('小明', 17) print(xiaoming._Person__age) xiaoming._Person__age = 20 print(xiaoming._Person__age)
破解私有属性和私有方法:
- 在名称前加上
_类名
- 其实双下划线仅仅是一种变形操作:
- 类中所有双下划线开头的名称如
__x
都会自动变形成:_类名__x
的形式
- 类中所有双下划线开头的名称如
封装总结
- 对外隐藏类内的具体实现,只提供接口,供外界使用
- 可以将属性和方法设置成私有的,
__
,设置成私有后,类内部可以访问,类外部无法访问
多态
多态体现一,变量可以指向任意数据类型
多态的第一种体现是,不关注对象的数据类型,同一个变量完全可以在不同的时间引用不同的对象
Python本身就是一种多态语言,不关心对象的类型
- 对于弱类型的语言来说,变量并没有声明类型,因此同一个变量完全可以在不同的时间引用不同的对象
- 毫无疑问,在Python中,对象是一块内存,内存中除了包含属性和方法之外,还包含了对象类型。我们通过引用来访问对象,比如
a = A()
。首先,Python创建一个对象A,然后声明一个变量a,再将变量a与对象A联系起来。变量a是没有类型的,它的类型取决于其关联的对象。
a = 10 a = 'alex' # Python中的变量可以随时被赋值任意数据类型
我们曾经谈到过,Python中的每一个类实际上就是一个数据类型,因此,变量也可以指向不同类的对象:
class Bird: def move(self, field): print('鸟儿在%s上自由地飞翔~' % field) class Dog: def move(self, field): print('狗狗在%s里飞快地奔跑~' % field) x = Bird() x.move('天空') x = Dog() x.move('草地') 输出的结果为: 鸟儿在天空上自由地飞翔~ 狗狗在草地里飞快地奔跑~
同一个变量x在执行同一个move()
方法时,由于x指向的对象不同,因此呈现出不同的行为特征,这就是多态。
多态体现二,一类事物有多种形态(Polymorphic)
一个抽象类有多个子类,但方法实现不同
- 例如:动物类有多个子类,每个子类都重新实现了父类的某个方法,但方法的实现不同(比如动物类都有休息的方法,但不同的动物休息的方式不尽相同)
- 此时需要有继承,需要方法重写
class Girl(object): def __init__(self, name): self.name = name def game(self): print('%s蹦蹦跳跳地玩耍...' % self.name) class Nurse(Girl): def game(self): print('%s脱裤子打针' % self.name) class Boy(object): def __init__(self, name): self.name = name def game(self, girl): print('%s和%s快乐地玩耍' % (self.name, girl.name)) girl.game() xiaoming = Boy('小明') xiaohong = Girl('小红') xiaoli = Nurse('小丽') xiaoming.game(xiaohong) xiaoming.game(xiaoli) 输出的结果为: 小明和小红快乐地玩耍 小红蹦蹦跳跳地玩耍... 小明和小丽快乐地玩耍 小丽脱裤子打针
Nurse有Girl类的方法和属性,是它的子类。方法名虽然相同,但实现的方式略有差异。具有相同父类,继承相同方法,在子类中对父类的方法进行了修改,就是多态的第二种体现。
多态性
多态性(polymorphism)指的是,在多态基础上,定义统一的共同接口(在类的外面定义单独的函数),从而实现使用一个函数调用不同类的同名方法。
不同类的对象作为函数的参数时,因为方法不同,得到的结果也会有差异。
多态性的具体实现方法如下:
class Person: def relax(self): print('人需要休息') class Alex: def relax(self): print('睡觉') class BigB: def relax(self): print('买沙发') # 定义一个函数作为访问多个类的relax方法的统一接口 def relax_func(person): person.relax() a = Alex() b = BigB() p = Person() relax_func(a) relax_func(b) relax_func(p) 输出的结果为: 睡觉 买沙发 人需要休息
鸭子类型
鸭子类型是Python中比较推崇的多态的另一种体现形式。
鸭子类型不关心类型,不需要继承,只关注方法的实现,这种情况被称为鸭子类型。
鸭子类型名字的来源于一句话:“当看到一只鸟走起来像鸭子、游起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称作鸭子”。
套用过来就是,当两个类,有同名方法,不管这两个类是干什么用的,也不管它们之间有没有联系(依赖、组合或继承),都可以用同一个接口来调用。
在鸭子类型中,关注的不是对象的类型本身,而是它是如何使用的。
比如,人、鸭子、鱼、猪和船都会游泳,但是他们是不同的类,他们之间没有任何联系。而我们却可以在外面定义一个统一的接口来调用他们之间游泳的方法:
class Person: def swim(self): print('人可以游泳') class Duck: def swim(self): print('鸭子会游泳') class Fish: def swim(self): print('鱼会游泳') class Pig: def swim(self): print('猪会游泳') class Ship: def swim(self): print('船会游泳') # 建立鸭子类型 def swimming(obj): obj.swim() xiaoming = Person() xiaohuang = Duck() paopao = Fish() peiqi = Pig() titanic = Ship() swimming(xiaoming) swimming(xiaohuang) swimming(paopao) swimming(peiqi) swimming(titanic) 输出的结果为: 人可以游泳 鸭子会游泳 鱼会游泳 猪会游泳 船会游泳
多态总结
Python是一门天然支持多态的编程语言。
多态增加了程序的灵活性(通用性),以不变应万变,不论对象千变万化,使用者都是使用同一种形式(外部定义的一个函数)去调用。
增加程序的可扩展性,通过继承某个类创建的一个新类,接口使用者无需更改自己的代码,还是用原方法调用。
多态和多态性对比:
多态强调的是,一类事物由不同的形态。
多态性强调的是,同一操作,作用对象不同,表现出不同的实现方式(只关心行为结果)
常见设计模式
先想象一下这样一个场景:去麦当劳点餐,我们想吃麦辣鸡腿堡、薯条和可乐。我们当然可以单点,但是如果我们对收银小姐姐说,请给我一份麦辣鸡腿堡套餐,就会方便很多。套餐是餐厅和我们约定好了的一个规范,如果大家都按照这个规范来点餐,就能够提高点餐效率。
创建套餐是提高点餐效率的可重复使用的解决方法。套餐可以重复被点,而且提高了顾客和服务员之间的共同效率。
类似地,在编写程序的软件世界,对于统一情境,众多软件开发任意经过长时间总结出了最佳的可重用的解决方案,就是软件的设计模式。
软件世界本没有设计模式,开发人员多了,开发得久了,也便总结出了设计模式。
设计模式有很多很多种,对于不同的场景有不同的设计模式。这里只介绍两种非常常见的设计模式:单例模式和工厂模式。
单例模式
我们从前学习的类创建对象时,每个对象都是开辟相互独立的内存空间,它们各自的实例属性和方法是互无联系互不影响的。但是有的时候,我们只想创建一个对象,不管被实例化多少次,使用的对象都是一个。
比如在开发过程中,不同的程序员负责不同的模块。他们都需要使用到一个类对象,但是用了不同的名字。但是到后来当程序运行的时候,他们有需要这些对象都是同一个对象。这就需要用到单例模式。
单例模式是保证一个类仅有一个实例的设计模式。
单例模式保证了程序在不同位置都可以且尽可以取到同一个对象的实例:如果实例不存在,则会创建一个实例;如果对象存在,则会返回这个实力。
创建单例模式之前,我们要先讨论一下__new__()
方法。同__init__()
方法一样,__new__()
方法也是在创建对象时自动运行的。不同的是,__init__()
方法用来初始化对象,而__new()__
方法用来创建对象。
换句话说,__new__()
方法在对象创建之前就会运行,它是一个类方法。它的返回值就是新创建的对象。
有了这些基础,结合我们从前学过的东西,就可以创建一个单例模式的类了:
class Student: # 类属性,用来存放实例对象内存地址,同时也用来标记类是否已经被实例化,初始值为None instance = None # cls用来指代类,args和kwargs用来接收参数,PyCharm默认填入,不必理会也不必修改 def __new__(cls, *args, **kwargs): # 如果没有实例化,就对其进行实例化操作,将实例化后的对象储存到类属性instance中 if not cls.instance: # 这里的参数只放cls,不能放*args和**kwargs cls.instance = object.__new__(cls) # 不管开始的instance是否为空,此时它都已经储存了一个类对象,将其返回即可 return cls.instance def __init__(self, name, age): self.name = name self.age = age xiaoli = Student('小丽', 16) xiaoming = Student('小明', 18) print(xiaoli.name) print(xiaoming.name) 输出结果为: 小明 小明
需要注意的是,虽然不会创建新的实例对象,但是每当创建新对象时,初始化方法还是会执行的。因为是同一个对象,所以后面创建的实例属性就会覆盖从前创建的实例属性。
因为单例模式太过常用且重要,所以我们往往将其单独写成一个类。继承这个单例模式类的新类也都会是单例模式:
class Singleton: instance = None def __new__(cls, *args, **kwargs): if not cls.instance: cls.instance = object.__new__(cls) return cls.instance class Student(Singleton): def __init__(self, name, age): self.name = name self.age = age xiaoli = Student('小丽', 16) xiaoming = Student('小明', 18) print(xiaoli.name) print(xiaoming.name) 输出的结果仍然为: 小明 小明
工厂模式
我们现在有这样一个需求:我们玩王者荣耀,想要一个物理伤害,射程远,可以持续输出的英雄。如果我们自己去找,很慢而且很麻烦。需要我们遍历所有人物的职业,然后才能确定射手类是我们想要的。
但是我们其实可以把这件事情交给程序来做。只要我们提出需求(物理伤害,射程远,持续输出),程序自动给我们返回一个射手类的对象。这样就很方便了。
这个帮我们创建类的类就是一个工厂类。这种编程模式,就是工厂模式。
工厂模式就是不直接暴露对象的创建细节,而是通过一个共同的类来创建对象的模式,总共需要四个步骤:
- 创建基类
- 创建子类
- 创建工厂类
- 使用工厂模式
事实上,工厂类中待我们选择的类并不要求非要继承自同一个父类。而且其实Python中所有类都继承自project类。所以前两步可以合并为一步,创建类。
我们可以这样创建一个工厂模式的类:
# 创建基类 class Person(object): def __init__(self, name): self.name = name def get_name(self): return self.name # 创建子类 class Male(Person): def __str__(self): return 'Hello Mr.' + self.get_name() class Female(Person): def __str__(self): return 'Hello Miss' + self.get_name() # 创建工厂类 class Factory(object): def get_person(self, name, gender='M'): if gender == 'M': return Male(name) if gender == 'F': return Female(name) # 使用工厂模式 factory = Factory() person = fcatory.get_person('Bob', 'M') print(person)