抽象类

抽象类是一个特殊的类,为了在团队开发中能够规范化代码而延伸出来的知识,源于java,这种类它只能被继承而不能被实例化。这也说明了该类中的所有函数以及属性都将用来被继承。

为什么要使用抽象类

  • 规范代码:
    它的子类必须实现抽象类中的所有函数 前面有提到有一点就是为了规范代码,在团队协作中很经常会遇到多个人开发同一个模块的情况,如果因为函数命名问题导致两个子类中的方法不同。为了避免这种问题才出现了抽象类这种概念。

  • 易于维护:
    还有另外个原因是为了减少代码的重复量和易于维护。比方说我们有多个汽车产品,不同的汽车有不同的功能。有的有自动驾驶,有的有全景天窗,但是只要是汽车都会有行驶,左转右转等基本功能,这些基本功能我们就可以放在抽象类中。

from abc import ABCMeta,abstractmethod

class Car(metaclass=ABCMeta):
    @abstractmethod
    def go():pass
    @abstractmethod
    def left():pass
    @abstractmethod
    def right():pass

为了让它的子类必须实现它的函数,在定义抽象类的时候给函数加上@abstractmethod修饰符。现在我们定义一个子类Audi,它有空调以及音乐的功能。对于它的基本功能它定义了run、turnleft、turnright函数。然后实例化

class Audi(Car):
    def air(): pass
    def music(): pass

    def run():pass
    def turnleft():pass
    def turnright():pass
a = Audi()

TypeError: Can't instantiate abstract class Audi with abstract methods go, left, right

可以看到编译器报错了,它的子类必须实现go、left、right函数。某个程序员想随便起名字那是不可能的。

小结:
在上面个例子中,Car是抽象类,类中定义了go、left、right方法而且没有任何逻辑实现。Audi是Car的子类,这三个方法中的逻辑实现由它来完成。

接口类

其实python中没有接口类的概念的,因为它不像java是可以支持多继承的

接口类比较适用于多个类中有比较多的重复功能,但是并没有像上面汽车例子中有共同的功能。比如斧子、铁镐、剑、吊绳。斧子和铁镐有收集材料和战斗的功能,剑只有战斗的功能,吊绳有攀爬的功能。这种情况下我们可以定义四个接口来定义这四个工具的功能(收集功能、战斗功能、攀爬功能)

from abc import ABCMeta,abstractmethod

# 战斗接口类
class Combat(metaclass=ABCMeta):
	@abstractmethod
	def attack(): pass

# 采集接口类
class Collect(metaclass=ABCMeta):
	@abstractmethod
    def wood(): pass
    @abstractmethod
    def stone(): pass

# 攀爬接口类
class Shin(metaclass=ABCMeta):
    @abstractmethod
    def hiking(): pass
    @abstractmethod
    def climbtree(): pass


# 斧子
class Axe(Combat, Collect):
    def attack(): pass
    def wood(): pass
    def stone(): pass

# 铁镐
class Mattock(Combat, Collect):
    def attack(): pass
    def wood(): pass
    def stone(): pass

#剑
class Sword(Combat):
    def attack(): pass

#绳子
class Cord(Shin):
    def hiking(): pass
    def climbtree(): pass


Axe()
Mattock()
Sword()
Cord()

这种情况如果用抽象类的就必然会出现多余的无用接口,其实这样设计是不符合接口隔离原则的。
接口隔离原则:
使用多个专门的接口,而不使用单一的总接口。即客户端不应该依赖那些不需要的接口。

小结:
接口类的作用:
  在java中,能够满足接口隔离原则,且完成多继承的约束
  而在python中,满足接口隔离原则,由于python本身支持多继承,所以就不需要接口的概念了

抽象类和接口类
在python中
  并没有什么不同,都是用来约束子类中的方法的
  只要是抽象类和接口类中被abstractmethod装饰的方法,都需要被子类实现
  需要注意的是,当多个类之间有相同的功能也有不同的功能的时候,应该采用多个接口类来进行分别的约束

在java中
  抽象类和接口截然不同
  抽象类的本质还是一个类,是类就必须遵循单继承的规则,所以一个子类如果被抽象类约束,那么它只能被一个父类控制
  当多个类之间有相同的功能也有不同的功能的时候 java只能用接口来解决问题

鸭子类型

所谓 鸭子类型 就是:如果一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么它就是鸭子。这个概念的名字来源于 James Whitcomb Riley 提出的鸭子测试。

鸭子类型是编程语言中动态类型语言中的一种设计风格,一个对象的特征不是由父类决定,而是通过对象的方法决定的。以迭代器协议举例来说:任何实现了 iternext 方法的对象都可以称为为迭代器,在这里迭代器是什么类型无关重要,而是由是否实现了这两个方法来决定的。

class Foo:
    def __iter__(self):
        pass

    def __next__(self):
        pass

from collections.abc import Iterable
from collections.abc import Iterator

print(isinstance(Foo(), Iterable)) # True
print(isinstance(Foo(), Iterator)) # True

例子中Foo类并没有继承任何类,但是它实现了 iternext 方法,由于动态语言的鸭子类型特性,它被设定为Iterator类型,这种现象就称为鸭子类型。所有动态语言都会有这种情况,如php,ruby.

多态

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

还是用例子来说话:

# 斧子
class Axe(Combat, Collect):
    def attack(self): pass
    def wood(): pass
    def stone(): pass

# 铁镐
class Mattock(Combat, Collect):
    def attack(self): pass
    def wood(): pass
    def stone(): pass

def attck(att_obj):
    att_obj.attack()


attck(Axe())
attck(Mattock())

在该例子中,Axe与Mattock明明是不同类型,但是在attack函数中并没有考虑这一点并且直接使用了它们各自的函数。总结来说就是多态性是指在不考虑实例类型的情况下使用实例。
这里就是扯到这就是“开闭”原则,对扩展开放:允许新增子类;对修改封闭:不需要修改依赖该类型的函数。虽然开闭原则适用于java、C++等静态语言,其实在这里也是一样的概念只不过动态语言中的多态就无须考虑继承的问题。其实多态和鸭子类型其实是一个意思,都是动态类型语言的设计风格。