Python之路(第二十四篇) 面向对象初级:多态、封装

时间:2023-03-08 15:51:35
Python之路(第二十四篇) 面向对象初级:多态、封装

一、多态

多态

多态:一类事物有多种形态,同一种事物的多种形态,动物分为鸡类,猪类、狗类

例子

  import abc
class H2o(metaclass=abc.ABCMeta):

def __init__(self,name,temperature):
self.name = name
self.temperature = temperature

@abc.abstractmethod
def tell_state(self):
pass

class Water(H2o):

def tell_state(self):
print("液态")

class Ice(H2o):

def tell_state(self):
print("固态")

class Steam(H2o):

def tell_state(self):
print("气态")

  

多态性

多态性:对象通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的类。

即对象调用同一种方法产生不同的结果,这个调用不用考虑对象自己的具体所属的类,多态性反应的是程序执行时的状态。

例子

  
  import abc
class H2o(metaclass=abc.ABCMeta):

def __init__(self,name,temperature):
self.name = name
self.temperature = temperature

@abc.abstractmethod
def tell_state(self):
pass

class Water(H2o):

def tell_state(self):
print("液态")

class Ice(H2o):

def tell_state(self):
print("固态")

class Steam(H2o):

def tell_state(self):
print("气态")


def tell_state(obj): #归一化设计,统一接口
if obj.temperature < 0:
Ice.tell_state(obj)
elif obj.temperature < 100:
Water.tell_state(obj)
elif obj.temperature > 100:
Steam.tell_state(obj)

w = Water("水",12)
i = Ice("冰",-3)
s = Steam("汽",120)

tell_state(w) #通过同一方法调用,传入不同的对象,产生不同的结果
tell_state(i)
tell_state(s)

  

​ python天生就是支持多态的,python是一种动态强类型语言,动态语言调用实例方法,不检查类型,只要方法存在,参数正确,就可以调用,而不会在开始就判断参数是否符合条件。因此,在python运行过程中,参数被传递过来之前并不知道参数的类型

但是python在'2'+3会报错,即字符串不能和整数相加,这里就显示了python作为强类型语言。所以说python是一种动态的强类型语言。

附:什么是强类型语言?什么是弱类型语言?动态性语言?静态性语言?

强类型语言是一种强制类型定义的语言,一旦某一个变量被定义类型,如果不经过强制转换,则它永远就是该数据类型了,强类型语言包括Java、.net 、Python、C++等语言。

弱类型语言是一种弱类型定义的语言,某一个变量被定义类型,该变量可以根据环境变化自动进行转换,不需要经过显性强制转换。弱类型语言对变量的类型检查不严格,程序运行时可以转换。

动态类型语言:在运行期进行类型检查的语言,也就是在编写代码的时候可以不指定变量的数据类型,比如Python和Ruby。

静态类型语言:它的数据类型是在编译期进行检查的,也就是说变量在使用前要声明变量的数据类型,这样的好处是把类型检查放在编译期,提前检查可能出现的类型错误,典型代表C/C++和Java。即变量使用要定义变量的类型,强类型语言在程序运行前会首先检查变量的类型设置是否正确。

Python之路(第二十四篇) 面向对象初级:多态、封装

Python之路(第二十四篇) 面向对象初级:多态、封装

鸭子类型

在程序设计中,鸭子类型(英语:duck typing)是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由当前方法和属性的集合决定。这个概念的名字来源于由James Whitcomb Riley提出的鸭子测试,“鸭子测试”可以这样表述:

“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子.“

在鸭子类型中,关注的不是对象的类型本身,而是它是如何使用的,不关心对象是什么类型,到底是不是鸭子,只关心行为。

比如list.extend()方法中,我们并不关心它的参数是不是list,只要它是可迭代的,所以它的参数可以是list/tuple/dict/字符串/生成器等.

比如len()方法,传入的参数可以是str\list\tuple

鸭子类型在动态语言中经常使用,非常灵活,使得python不想java那样专门要一些专门的设计模式。

简单的鸭子模型

  class duck():
def walk(self):
print('I walk like a duck')
def swim(self):
print('i swim like a duck')

class person():
def walk(self):
print('this one walk like a duck')
def swim(self):
print('this man swim like a duck')

  

对于一个鸭子类型来说,我们并不关心这个对象的类型本身或是这个类继承,而是这个类是如何被使用的。

可以直接调用类的方法而不用关心具体是哪个类

例子


 class List:
def __len__(self):
print("计算list的长度")
class Tuple:
def __len__(self):
print("计算tuple的长度")


def len(obj): #不管传入的参数类型
return obj.__len__()

len([1,2])
len((2,3,4))

  

​ 多态实际上是依附于继承的两种含义的,改变和拓展需要一种机制实现父类和子类的不同,所以多态是实现继承的细节。

二、封装

广义的封装:封装是一种对代码的保护,面向对象的思想本身就是一种封装的设计,封装目的是只让自己的对象调用自己的类方法的一种机制。

狭义上的封装:把属性和方法都隐藏起来,不让你看见, 隐藏对象的属性和实现细节,仅对外提供公共访问方式。

第一层意义上的封装:类就像麻袋,这本身就是一种封装

第二层意义上的封装:类中定义私有的,只有在类的内部使用,外部无法直接访问。(这种封装是一种约定)

第三层意义上的封装:明确区分内外,内部实现逻辑,外部无法知晓,并且为封装到内部的逻辑提供一个外部的访问接口,给外部使用。

私有属性和私有方法

在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)。

所有的私有属性或者方法都是在变量的左边加上双下划线,包括:

  • 对象的私有属性

  • 类中的私有方法

  • 类中的静态私有属性

例子

  

class People:

__country = "China" #在属性前加上__,变成私有数据属性

def __init__(self,name,age,gender):
self.name = name
self.__age = age
self.__gender = gender

def get_age(self
return self.__age #在类的内部调用私有属性可以直接写self.__属性名

def __get_gender(self): #私有函数属性(私有方法)
return self.__gender

def get_gender(self): #正常的方法调用私有的方法
return self.__get_gender()

@classmethod #使用类方法,通过类也能调用类的私有数据属性,通过对象也能调用类的私有属性
def get_country(cls):
return cls.__country

def tell_info(self):
print("名字%s,年龄%s,性别%s"%(self.name,self.__age,self.__gender))

p1 = People("nick",18,"male")
print(p1._People__age) # 其实python是通过加上类名实现隐藏的,即_类名__属性名。虽然这样在类的外部也可以调用私有属性,
# 但是不推荐这样做。
print(p1.get_age()) #python中调用私有属性需要写一个调用私有属性的普通方法get_age()
print(p1.get_gender()) #调用私有方法也需要单独写一个调用私有方法的普通方法
print(People.get_country()) #单独写了类方法,类可以调用类的私有数据属性
print(p1.get_country()) #单独写了类方法,对象也调用类的私有数据属性

  

​ 封装的真谛在于明确地区分内外,封装的属性可以直接在内部使用,而不能被外部直接使用,然而定义属性的目的终归是要用,外部要想用类隐藏的属性,需要我们为其开辟接口,让外部能够间接地用到我们隐藏起来的属性。

封装的目的

1、封装数据:将数据隐藏起来这不是目的。隐藏起来然后对外提供操作该数据的接口,然后我们可以在接口附加上对该数据操作的限制,以此完成对数据属性操作的严格控制。

2、封装方法:目的是隔离复杂度,把一些实现的方法隐藏起来,只提供一个接口。

封装原则

​ 1. 将不需要对外提供的内容都隐藏起来;

​ 2. 把属性都隐藏,提供公共方法对其访问。

封装的调用

类的内部可以直接用`.__内置属性直接调用,类的外部一般需要写一个调用的方法,不推荐直接调用,虽然可以直接调用。

例子1

  
  class People:

def __init__(self, name, age):
self.__name = name #隐藏属性
self.__age = age

def tell_info(self):
print("名字是%s,年龄是%s" % (self.__name, self.__age)) #隐藏属性在类的内部可以直接调用

def set_info(self, name, age): #这里通过增加设置属性的方法修改隐藏的属性,不直接修改隐藏的属性
if not isinstance(name, str):
print("name必须是字符串类型")
return
if not isinstance(age, int):
print("age必须是整数类型")
return
self.__name = name
self.__age = age


p = People("nick", 18)
p.tell_info()
p.set_info("nick", 20) #通过设置方法修改属性,不直接修改隐藏的属性
p.set_info("nick","18")
#输出结果 age必须是整数类型
#通过设置方法对修改属性的过程增加各种判断

  

分析:封装数据明确区分内外,控制外部对内部隐藏的属性的操作行为

例子2

  class Atm:

def __init__(self,number):
self.number = number

def repay(self):
print("付款")

def withdraw(self):
print("取现")

def transfer(self):
print("转账")

c =Atm(55645566)
c.transfer()
c.withdraw()
c.repay()

  

分析:通过类封装了各种方法,在外部直接调用方法实现功能即可,而不用关心类的内部如何实现的,隔离的复杂度,同时也提升了安全性。

父类的私有属性、私有方法不能被子类调用

例子

  
  class A:

def __init__(self, name, age):
self.name = name
self.age = age
self.__money = 1000000

def __make_money(self):
print("父类赚钱的技能")


class B(A):
pass

p = B("nick",18)
print(p.__money) #这里会报错,无法继承父类的私有属性,这里的私有属性实际名字是_B__money,而父类的名字是_A__money
p.__make_money() #这里也报错,无法继承父类的私有方法,与私有属性类似,私有方法实际是_A__make_money

  

分析:在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的

再说property

用@property语法糖装饰器将类的函数属性变成可以不用加括号直接的类似数据属性。可以封装逻辑,让用户感觉是在调用一个普通的数据属性。将类的一些通过计算才能得到的属性封装,不用以调用方法的形式进行调用。访问它时会执行一段功能(函数)然后给出返回值。

将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则

例子

  
  class People:

def __init__(self, name, height, weight):
self.name = name
self.height = height
self.weight = weight

@property #使用property装饰器,使对象调用时直接返回数值
def pmi(self):
return self.weight / (self.height ** 2)

p = People("nick",1.75,60)
print(p.pmi) #直接给出返回值,不用使用常规的调用类的方法的形式(加括号)

  

例子2

修改和删除  设置了property装饰器的方法

class Goods:

    def __init__(self,name,amount,price):
self.name = name
self.amount = amount
self.__price = price @property
def price(self): #为这个对象的价格加1,用property直接调用
print("价格加1")
return self.__price + 1 @price.setter #修改价格,首先必须有同名的方法用了property装饰器,之后在这里用方法名.setter装饰器可以直接修改
def price(self,val):
print("再次修改价格啦")
self.__price = val @price.deleter #删除price属性
def price(self):
print("不能删除") g = Goods("apple",1,5)
print(g.price) #输出结果 6
g.price = 3
print(g.price) #输出结果 4,注意这里是先执行@price.setter 下的price方法再执行 @property下的price方法
del g.price

  输出结果

价格加1
6
再次修改价格啦
价格加1
4
不能删除

  

面向对象的封装有三种方式

【public】
这种其实就是不封装,是对外公开的,即类中普通的数据属性、静态属性,在类的内部和外部都可以调用
【protected】
这种封装方式对外不公开,即隐藏属性和隐藏方法,私有属性、私有方法,只能在类的内部进行调用
【private】
这种封装对谁都不公开(python中没有这种)