17. 面向对象的特征

一、面向对象的三大特征

  面向对象的三大特征指的是 封装继承多态

  封装(encapsulation,有时称为数据隐藏)是处理对象的一个重要概念。从形式上看,封装就是将数据和行为组合在一个包中,并对对象的使用者隐藏具体的实现方式。

  继承(inheritance)的基本思想是,可以基于已有的类创建新类。继承以存在的类就是复用(继承)这些类的方法,而且可以增加一些新的属性和方法,使新类能够适应新的情况。

  多态(polynirphic)就是一个类的多种形态。

二、封装性

2.1、什么是封装性

  所谓的封装,就是把客观事物封装成抽象概念的类,并且类可以把自己的数据和方法只向可信的类或对象开放,向没必要开放的类或对象异常信息。封装性就是隐藏内部对象的复杂性,只对外公开简单的接口。便于外部调用,从而提高系统的可扩展性、可维护性。通俗的来说,把该隐藏的隐藏起来,该暴露的暴露出来。

  当我们创建一个类时,我们可以通过“对象.属性”的方式,对对象的属性进行赋值。此时,赋值操作要受到属性的数据类型和存储范围的制约。除此之外,没有其它制约条件。但是,在实际问题中,我们往往需要给属性赋值加入额外的限制条件。这个条件就不能在属性声明时体现,只能通过方法进行限制条件的添加。同时,我们需要避免用户在使用“对象.属性”的方式对属性进行赋值,则需要将属性声明为私有的(private)。此时,针对于属性就体现了封装性。

  使用封装后,确实增加了类的定义的复杂程度,但是它也确保了数据的安全性。我们隐藏了属性名,使调用者无法任意修改对象中的属性。并且我们增加了 getter() 和 setter() 方法,可以很好的控制属性是否为只读的。如果希望属性是只读的,则可以直接去掉 setter() 方法,如果希望属性不能被外部访问,则可以直接去掉 getter() 方法。而且在使用 setter() 方法设置属性时,可以增加数据的验证,确保数据的值是正确的。在使用 getter() 方法获取属性,使用 setter() 方法设置属性时,可以在读取属性和修改属性的同时做一些其它的处理。

2.2、封装性的体现

  我们将类的属性(xxx)隐藏,同时,提供的公共的方法类获取(getter())和设置(setter(self))此属性的值。在 Python 中,我们可以为对象的属性使用双下划线开头 __xxx,双下划线开头的属性,是对象的隐藏属性,隐藏属性值只能在类的内部访问,无法通过对象访问。

class Person:
    def __init__(self,name,age):
        self.__name = name
        self.__age = age

    def get_name(self):
        return self.__name
  
    def set_name(self,name):
        self.__name = name

    def get_age(self):
        return self.__age
  
    def set_age(self,age):
        if age >= 0:
            self.__age = age

    def __show_info(self):
        print(f"name: {self.__name}, age: {self.__age}")

  如果我们直接访问类中的隐藏属性,会报以下错误:

p1 = Person("Sakura",10)
print(p1.__name)
AttributeError: 'Person' object has no attribute '__name'

  此时,我们可以通过公共的 getter() 方法和 setter() 方法调用类的私有属性:

p1 = Person("Sakura",10)
print("name: ", p1.get_name() , ", age: ", p1.get_age())

p1.set_age(12)
print("name: ", p1.get_name() , ", age: ", p1.get_age())

p1.set_age(-10)
print("name: ", p1.get_name() , ", age: ", p1.get_age())

  其实,隐藏属性是假隐藏,只不过是 Python 自动为属性改了一个名字。实际上,Python 将名字修改为 _类型__属性名

p1 = Person("Sakura",10)
p1._Person__age = 12
print(p1._Person__age)
p1._Person__show_info()

使用双下划线开头的属性,实际上依然可以在外部访问。在开发中,我们可以将一些私有属性以下划线开头(实际上是公开的属性),告诉开发人员没有特殊需要不要修改私有属性。

2.3、property装饰器

  property 装饰器,用来将一个 getter() 方法,转换为对象的属性,添加了 property 装饰器以后,我们就可以向调用属性一样使用 getter() 方法。使用 property 装饰的方法必须和属性名是一样的。setter() 方法的装饰器为:@属性名.setter;

class Person:
    def __init__(self,name):
        self.__name = name

    @property
    def name(self):
        print("get_name(self)方法执行了")
        return self.__name
  
    @name.setter
    def name(self,name):
        print("set_name(self,name)方法执行了")
        self.__name = name
  
    @name.deleter
    def name(self):
        print("del_name(self,name)方法执行了")

p = Person("Sakura")
print(p.name,end="\n\n")

p.name = "Mikoto"
print(p.name,end="\n\n")

del p.name

  在一些较老版本的 Python 解释器中,我们可以通过如下的方法实现。

class Person:
    def __init__(self,name):
        self.__name = name

    def get_name(self):
        print("get_name(self)方法执行了")
        return self.__name
  
    def set_name(self,name):
        print("set_name(self,name)方法执行了")
        self.__name = name
  
    def del_name(self):
        print("del_name(self,name)方法执行了")

    name = property(get_name, set_name, del_name)

p = Person("Sakura")
print(p.name,end="\n\n")

p.name = "Mikoto"
print(p.name,end="\n\n")

del p.name

三、继承性

3.1、什么是继承性

  继承,其基本思想就是基于某个父类的扩展,制定出一个新的子类,子类可以继承父类原有的属性和方法,也可以增加原来父类所不具备的属性和方法,或者直接重写父类中的某些方法。

  通过继承可以直接让子类获取父类的方法和属性,避免编写重复性的代码,并且也符合 OCP 原则。所以,我们经常需要通过继承来对一个类进行扩展。

3.2、继承性的应用

  使用继承后,我们可以减少代码的冗余,提高了代码的复用性,并且使用继承后,便于功能的拓展。继承的出现让类与类之间产生了 is-a 的关系。

  在 Python 中,继承性的格式如下:

class A(父类):
    pass

  其中,A 类代表 子类(派生类、subclass),B 类代表 父类(基类、superclass)。一旦 子类 A 继承 父类 B 之后,子类 A 中就获取 父类 B 中声明的所有的结构:属性、方法(包括特殊方法);子类继承父类以后,还可以声明自己特有的属性和方法,实现功能的拓展;

class Animal:
    def run(self):
        print("动物在跑")

    def sleep(self):
        print("动物在睡觉")

class Dog(Animal):
    def bark(self):
        print("汪汪汪")

dog = Dog()

dog.run()
dog.sleep()
dog.bark()
print()

# isinstance()检查一个对象是否是一个类的实例
# 如果这个类是这个对象的父类,也会返回true
print(isinstance(dog,Dog))
print(isinstance(dog,Animal))
print()

# issubclass()检查一个类是或否是另一个类的子类
print(issubclass(Dog,Animal))

如果在创建类时,省略了父类,则默认父类为 object,它是所有类的父类,所有类都继承 object;

3.3、方法重写

  如果在子类中有和父类同名的方法,则通过子类实例调用方法时,会调用子类的方法而不是父类的方法,这个特点我们成为 方法重写(覆盖,override)。

  当我们调用一个对象的方法时,它会优先去当前对象中寻找是否具有该方法,如果有则直接调用,如果没有则取当前对象的父类中寻找,如果父类中有则直接调用父类中的方法,如果没有则取父类中的父类中寻找,以此类推,直到找到 object,如果依然没有找到则报错。

class Animal:
    def run(self):
        print("动物在跑")

    def sleep(self):
        print("动物在睡觉")

class Dog(Animal):
    def run(self):
        print("狗在跑")

    def bark(self):
        print("汪汪汪")

dog = Dog()

dog.run()
dog.sleep()
dog.bark()

3.4、super()方法的使用

  父类中所有结构:属性 和 方法(包括特殊方法)都会被子类继承。如果我们希望直接调用父类中的结构,我们可以使用 super() 方法获取当前类的父类,并且通过 super() 调用父类方法时,不需要传递 self。

class Animal:
    def __init__(self,name):
        self.__name = name

    @property
    def name(self):
        return self.__name
  
    @name.setter
    def name(self,name):
        self.__name = name

class Dog(Animal):
    def __init__(self,name,age):
        # 直接调用父类中的__init__(self)来初始化父类中定义的属性
        super().__init__(name)
        self.__age = age

    @property
    def age(self):
        return self.__age
  
    @age.setter
    def age(self,age):
        self.__age = age

dog = Dog("旺财",3)
print("name: ", dog.name, ", age: ", dog.age)

3.5、多重继承

  在 Python 中是支持多重继承的,也就是我们可以为一个类同时指定多个父类。我们可以在类名的 () 后面添加多个类,来实现多重继承。多重继承会使子类同时拥有多个父类,并且或获取所有父类中的方法。如果多个父类中有同名的方法,则会先在第一个父类中寻找,然后找第二个,然后找第三个,...,前面父类的方法会覆盖后面父类的方法。

class A:
    def test(self):
        print("AAA")

class B:
    def test(self):
        print("BBB")

class C(A,B):
    pass

# 类名.__bases__这个属性可以获取当前类的所有父类
print(C.__bases__)
# 我们可以通过类.mro()查看属性的查找顺序
print(C.mro())

c = C()
c.test()

super() 在调用父类的时候,它需要计算出当前到底调用哪个父类,在 Python 中实现这个功能的算法叫 C3 算法;

  在 Python 中子类可以同时继承多个父类的属性,这样可以最大限度地重用代码,但是使用多继承会导致扩展性变差,并且有可能会导致棱形问题(钻石问题)。棱形问题(钻石问题)指的是一个子类继承的多个父类汇集到一个非 object 类的身上。
棱形问题

class A:
    def test(self):
        print("from A")

class B(A):
    def test(self):
        print("from B")

class C(A):
    def test(self):
        print("from C")

class D(B):
    def test(self):
        print("from D")

class E(C):
    def test(self):
        print("from E")

class F(A):
    def test(self):
        print("from F")

class G(D,E,F):
    pass

# 类名.__bases__这个属性可以获取当前类的所有父类
print(G.__bases__)
# 我们可以通过类.mro()查看属性的查找顺序
print(G.mro())
obj = G()
obj.test()

  如果真的涉及到一个子类不可避免使用多个父类的属性,应该使用 Mixins 机制。Mixin 类表示某一功能,而不是某个物品,Python 对于Mixin 类的命名方式一般以 Mixin,able,ible 为后缀。我们可以将相关的功能放在一个 Mixin 类中,如果有多个不同功能,那可以写多个 Mixin 类,一个类可以继承多个 Mixin 类,但应该只继承一个表示其归属含义的父类,以确保遵循继承的 "is-a" 原则。Mixin 类不应该依赖于子类的实现,子类即便没有继承这个 Mixin 类,也可以照常工作,只是缺少了某个功能而已。

class Vehicle:
    pass

class FlyerMixin:
    def fly(self):
        pass

class CivilAircraft(FlyerMixin,Vehicle):
    pass

class Helicopter(FlyerMixin,Vehicle):
    pass

class Car(Vehicle):
    pass

四、多态性

  多态性指的是一个事物的多种形态;同样的行为(函数),传入不同的对象,得到不同的状态;多态性指的是可以在不考虑对象具体类型的情况下而直接使用对象。

  它的格式如下:

class A:
    def __init__(self,name):
        self.__name = name

    @property
    def name(self):
        return self.__name
  
    @name.setter
    def name(self,name):
        self.__name = name
  
class B:
    def __init__(self,name):
        self.__name = name

    @property
    def name(self):
        return self.__name
  
    @name.setter
    def name(self,name):
        self.__name = name

a = A("Sakura")
b = B("Mikoto")

# 对于say_hello()这个函数来说,只要对象中含有name属性,它就可以作为参数传递
# 这个函数并不会考虑对象的类型,只要有name属性即可
def say_hello(obj):
    print("你好,%s" %obj.name)

say_hello(a)
say_hello(b)
import abc

# 使用模块abc统一所有子类的标准
class Animal(metaclass=abc.ABCMeta):

    @abc.abstractmethod
    def speak(self):
        pass

class Dog(Animal):
    def speak(self):
        print("汪汪汪!")

class Cat(Animal):
    def speak(self):
        print("喵喵喵!")

def make_noise(animal):
    animal.speak()

#animal = Animal()       # 不能实例化抽象类自己

dog = Dog()
cat = Cat()

make_noise(dog)
make_noise(cat)

看上去调用相同的方法,但实际上需要这个看这个对象是父类还是子类创建的对象,如果是父类创建的对象,一定调用父类中定义的方法,如果是子类创建的对象,那么就要看子类是否重写了父类的方法,如果子类要是重写了父类的方法,那么会调用子类的方法,如果子类没有重写父类方法,那么会调用父类的方法。

posted @ 2024-10-16 20:00  星光映梦  阅读(4)  评论(0编辑  收藏  举报