python之面向对象的三大特点-----封装、继承、多态

1、封装

封装是面向对象编程的一大特点,将属性和方法封装到一个抽象的类中
数据被保存在内部,程序的其他部分只有通过被授权的操作(成员方法)才能对数据进行操作。外界使用类创建对象,然后让对象调用方法
通过在实例上调用方法,我们就直接操作了对象内部的数据,但无需知道方法内部的实现细节。对象方法的细节都被封装在类的内部

------------------- 练习------------------

需求
1.小明体重75.0公斤
2.小明每次跑步都会减肥0.5公斤
3.小明每次吃东西体重都会增加1公斤

需求
1.小明和小美都爱跑步
2.小美体重45.0公斤
2.每次跑步都会减肥0.5公斤
3.每次吃东西体重都会增加1公斤

class Person:
    def __init__(self,name,weight):
        self.name = name
        self.weight = weight
    def __str__(self):
        return '我的名字叫 %s 体重是 %.2f' %(self.name,self.weight)
# 在对象的方法内部,是可以直接访问对象的属性的
    def run(self):
        print('%s 去跑步~~~~' %(self.name))
        self.weight -= 0.5
    def eat(self):
        print('%s 去吃东西~~~' %(self.name))
        self.weight += 1
xiaoming = Person('小名',75.0)
xiaoming.run()
xiaoming.eat()
print(xiaoming)
xiaomei = Person('小美',45.0)
xiaomei.eat()
print(xiaomei)

------------------- 练习------------------

根据学生的成绩得到成绩的等级

class Student:
    def __init__(self,name,score):
        #初始化方法,当使用类创建实例的时候都会调该方法
        self.name = name
        self.score = score
    def get_grade(self):
        #对数据封装
        if self.score > 90:
            print('A')
        elif self.score >80:
            print('B')
        else:
            print('C')

toto = Student('toto',95)  # 创建对象
toto.get_grade()  # 直接使用对象调用方法,得到结果

#输出结果:
A

一个对象的属性 可以是另一个对象创建的类

------------------- 练习------------------

需求:
1.房子有户型,总面积和家具名称列表
新房子是没有家具的
2.家具有名字和占地面积,其中
床:5
桌子:4
椅子:6
3.将以上三件家具添加到房子中
4.打印房子的时候,要求输出:户型 总面积 剩余面积 家具名称列表

class Furniture:   # 限定义家具类
    def __init__(self,name,area):
        self.name = name
        self.area = area
    def __str__(self):
        return 'this is %s, %d square '

#使用类创建好对象之后,该对象可以作为参数用于其他函数调用。

class House:  #在定义房子类
    def __init__(self,type,squa):
        self.fu_list = []
        self.type = type
        self.leftsqua = self.allsqua = squa
    def __str__(self):
        return ' 户型是: %s\n 总面积是: %.2f\n 剩余面积: %.2f\n 家具列表:%s' \
               %(self.type,self.allsqua,self.leftsqua,self.fu_list)
    def add_furn(self,item):  # 在房子类的方法中使用的参数是家具类的一个实例
        if item.area<self.allsqua:
            self.fu_list.append(item.name)
            self.leftsqua -=item.area
        else:
            return
bed = Furniture('bed',5)
desk = Furniture('desk',4)
chair = Furniture('cabinet',6)   # 先实例化 家具

house1 = House('三室',120)  # 在实例化房子
house1.add_furn(bed)   # 再使用房子对象的方法,传入的参赛是家具实例
house1.add_furn(desk)
house1.add_furn(chair)
print(house1)

#输出结果:
 户型是: 三室
 总面积是: 120.00
 剩余面积: 105.00
 家具列表:['bed', 'desk', 'cabinet']

------------------- 练习------------------

1.士兵瑞恩有一把AK47
2.士兵可以开火(士兵开火扣动的是扳机)
3.枪 能够 发射子弹(把子弹发射出去)
4.枪 能够 装填子弹 --增加子弹的数量

class Gun:   # 定义枪类
    def __init__(self,name):
        self.name = name
        self.count = 3   # 属性:枪名称以及子弹数量
    def add_bullet(self):  # 方法:添加子弹,将子弹数充值3
        self.count = 3
    def launch_bullet(self):   # 发射子弹
        if self.count <= 0:
            self.add_bullet()   #如果没有子弹,先进行添加子弹
        self.count -= 1   # 然后子弹减少一个
        print('%s 已经成功发射子弹 剩余子弹%d' %(self.name,self.count))

class Solair:   # 定义士兵类
    def __init__(self,name):
        self.name = name   # 设置属性 :name
    def shoot(self,gun):  # 定义方法:直接调用枪对象的方法
        gun.launch_bullet()


AK47 = Gun('AK47')   # 实例化一个枪对象
ryan = Solair('Ryan')   #实例化一个士兵对象
ryan.shoot(AK47)  # 士兵对象调用开火方法
ryan.shoot(AK47)
ryan.shoot(AK47)
ryan.shoot(AK47)

#结果:
AK47 已经成功发射子弹 剩余子弹2
AK47 已经成功发射子弹 剩余子弹1
AK47 已经成功发射子弹 剩余子弹0
AK47 已经成功发射子弹 剩余子弹2

2、继承

在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)

最大的好处是子类获得了父类的全部功能,实现代码的重用,相同的代码不需要重复的写。

class Animal():  #定义动物类
    def run(self):
        print('running~~')
    def call(self):
        print('hahahah')   # 动物类中存在两个方法

class Cat(Animal):   # 定义猫类,继承动物类
    pass  
tom = Cat()  # 常见一个猫类实例
tom.run()  # 该实例可以直接调用动物类的方法

#输出结果:
running~~

可以在子类中添加自己特定功能的一些方法

class Animal():
    def run(self):
        print('running~~')
    def call(self):
        print('hahahah')
class Cat(Animal):
    def eat(self):
        print('爱吃鱼')
tom = Cat()
tom.run()  # 调用父类的方法
tom.eat()  # 使用自己类的方法

#输出结果:
running~~
爱吃鱼

当父类方法的实现不能满虚子类的需求的时候可以对方法进行重写,我们说,子类的方法覆盖了父类的方法,在代码运行的时候,总是会调用子类的该方法。

class Animal():
    def run(self):
        print('running~~')   #父类中存在run方法
    def call(self):
        print('hahahah')
class Cat(Animal):
    def run(self):
        print('跑得快')    # 子类中也存在run方法
    def eat(self):
        print('爱吃鱼')
tom = Cat()
tom.run()   # 当实例调用run方法的时候,总是调用的子类中的程序

#运行结果:
跑得快

可以对父类方法进行扩展。保留父类方法的内容,在其基础上增加新的内容。只需要子子类中定义和父类中相同的方法,并且在该方法中写入super().call()(父类.方法(self) python2.x通过个该实现),然后添加需要扩展的内容即可。

class Animal():
    def run(self):
        print('running~~')
    def call(self):
        print('hahahah')
class Cat(Animal):
    def run(self):
        super().run()    # 继承父类给方法的内容,同时添加扩展的内容
        print('跑得快')
    def eat(self):
        print('爱吃鱼')
tom = Cat()
tom.run()

#输出结果:
running~~
跑得快

当父类中使用了初始化方法,在子类中也想使用初始化方法的时候,需要使用在子类初始化方法中使用super().init(),表示对父类初始化方法的继承扩展,如若没有,会出现冲突报错。

class Animal():
    def __init__(self,name):
        self.name = name
    def run(self):
        print('running~~')
    def call(self):
        print('hahahah')
class Cat(Animal):
    def __init__(self,color,name):
        super().__init__(name)   #在子类中必须使用该语句,表示继承父类中的属性。
        self.color = color
    def run(self):
        super().run()
        print('跑得快')
    def eat(self):
        print('爱吃鱼')
tom = Cat('tom','red')
print(tom.name)
print(tom.color)

#输出结果:
red
tom

当子类继承子多个父类的时候,并且多个父类中存在相同的方法,这时候该子类的实例取调用这个方法的时候,就会出现那个被继承的父类写在前面,就使用哪一个父类的方法。建议在定义方法的时候,尽量不要使用相同的名称

class A:   #定义A 类
    def lala(self):
        print('lala')
    def toto(self):
        print('toto')  # 其中由来两个方法
class B:  # 定义B类
    def lala(self):
        print('LALA')
    def toto(self):
        print('TOTO')   # 其中由两个方法,并且方法名称和A中方法名称一致
class C(A,B):   #定义类C 继承A,B 两个类
    pass
class D(B,A):   # 定义类D 继承B,A 两个类
    pass

c = C()
d = D()

c.lala()  
d.lala()

c.toto()
d.toto()    # 当继承父类的顺序不同的时候,导致调用的方法是不同的内容,默认调用位置靠前的父类方法

#输出结果:
lala
LALA
toto
TOTO

3、多态

以封装和继承为前提,存在一个函数,需要一个参数,不用对函数进行修改当参数是不同的对象的时候输出的结果不同,

就是指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。这意味着,虽然针对不同对象的具体操作不同,但通过一个公共的类,它们(那些操作)可以通过相同的方式予以调用。

不同的子类对象调用相同的方法,产生不同的执行结果

首先要对数据类型再作一点说明。当我们定义一个class的时候,我们实际上就定义了一种数据类型。我们定义的数据类型和Python自带的数据类型,比如str、list、dict没什么两样 class Animal(object):

    def run(self):
        print('Animal is running...')

class Dog(Animal):
    pass

class Cat(Animal):
    pass

a = list()
b = Animal()
c = Dog()
d = Cat()

print(isinstance(a,list))
print(isinstance(b,Animal))
print(isinstance(c,Dog))
print(isinstance(d,Cat))

#输出结果:
True
True
True
True

同时子类实例化出来的对象,不仅是子类类型,同时还是父类类型。在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。例如Animal是Dog的父类,使用Dog实例化一个实例,该实例既是Dog类型,也是Animal类型。

class Animal(object):
    def run(self):
        print('Animal is running...')

class Dog(Animal):
    pass

b = Animal()
c = Dog()

print(isinstance(b,Animal))
print(isinstance(c,Dog))
print(isinstance(c,Animal))

#输出结果:
True
True
True

首先存在三个类:Animal、Dog、Cat;Dog、Cat 都继承自Animal类,并且都存在run方法。

 class Animal(object):
    def run(self):
        print('Animal is running...')
class Dog(Animal):
    def run(self):
        print('Dog is running')
class Cat(Animal):
    def run(self):
        print('Cat is running')

理解多态的好处,我们还需要再编写一个函数,这个函数接受一个Animal类型的变量:

   def run_twice(animal):
        animal.run()
        animal.run()
当我们传入Animal类的实例时,run_twice()就打印出:
def run_twice(animal):
    animal.run()
    animal.run()

run_twice(Animal())

#输出结果:
Animal is running...
Animal is running...

由于Dog类的实例以及Cat类的实例都可以是Animal数据类型,同样也可以作为参数传入该函数,当传入Dog类的实例以及Cat类的实例时候,函数的输出:

def run_twice(animal):
    animal.run()
    animal.run()

run_twice(Dog())
run_twice(Cat())

#输出结果:
Dog is running
Dog is running

Cat is running
Cat is running

现在可以在定义一个以Animal作为父类的子类,并将其实例作为参数,函数run_twice可以正常的运行。

class Tortoise(Animal):
    def run(self):
        print('Tortoise is running slowly~~~')
run_twice(Tortoise())

#输出结果:
Tortoise is running slowly~~~
Tortoise is running slowly~~~

重点:

新增一个Animal的子类,不必对run_twice()做任何修改,实际上,任何依赖Animal作为参数的函数或者方法都可以不加修改地正常运行,

函数需要一个Animal类型的参数,当我们需要传入Dog、Cat、Tortoise……时,我们只需要接收其Animal类型就可以了,因为Dog、Cat、Tortoise……都是Animal类型,然后,按照Animal类型进行操作即可。函数能够正常的运行。

由于Animal类型有run()方法,因此,传入的任意类型,只要是Animal类或者子类,就会自动调用实际类型的run()方法,当然,每一个实际类型的run方法内容以及操作的数据都不相同,操作的结果也就相同,这就是多态。

对于一个变量,我们只需要知道它是Animal类型,无需确切地知道它的子类型,就可以放心地调用run()方法,而具体调用的run()方法是作用在Animal、Dog、Cat还是Tortoise对象上,由运行时该对象的确切类型决定,

调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:

对扩展开放:允许新增Animal子类
对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。

猜你喜欢

转载自blog.csdn.net/qq_36016375/article/details/92798782