抽象类描述了相关的一组类的通用的特征和行为。在客户的应用程序中,抽象类是不能正常实例化的,它的子类叫做具体类,这表明它们才是真正用来在客户应用程序中创建对象的类。
前面我们在Python 基于数组、链表实现的包接口中以不同方法实现了包接口。然后又在Python继承中实现了一个继承ArrayBag类的子类ArraySortedBag类。现在我们需要在ArrayBag类和LinkedBag类之上实现一个抽象类来去除冗余代码。
包类中最显而易见的冗余方法是直接调用其它方法而没有直接访问实例变量的那些方法,这包括isEmpty、__str__、__add__和__eq__等方法。冗余的实例变量有点难以识别。包类使用了两个实例变量,分别是self._items和self._size。每个类中,self._items引用的是数据结构的一个不同的数据类型,相反,self._size在每一个包类中都引用了一个整数值。因此只有self._size是一个冗余的实例变量。
由于__len__方法访问了self._size而不是self._items,因此它也被视为冗余方法。
我们可以将包类中冗余的方法删除,并且将它们放置到一个叫做AbstractBag的新类中,然后,包类会通过继承变为AbstractBag的子类,从而访问这些方法。
注意:通常一个类的方法和变量对其所有的子孙类都是适用的。
(1)引入变量self._size并将其初始化为0
(2)如果需要的话,将源集合中的项添加到self中
因此,我们需要删除初始化变量self._items的行,这是子类的职责。
class AbstractBag(object):
"""An abstract bag implementation."""
# Constructor
def __init__(self, sourceCollection = None):
"""Sets the initial state of self, which includes the contents of sourceCollection, if it's present."""
self._size = 0
if sourceCollection:
for item in sourceCollection:
self.add(item)
ArrayBag的__init__方法修改(LinkedBag的__init__方法修改类似,这里忽略)
这个方法仍然负责将self._items设置为一个新的数组,然后在通过AbstractBag中的__init__方法来初始化包的大小并且在需要的是时候添加源集合的项。
from arrays import Array
from abstractbag import AbstractBag
class ArrayBag(AbstractBag):
"""An array-based bag implementation."""
# Class variable
DEFAULT_CAPACITY = 10
# Constructor
def __init__(self, sourceCollection = None):
"""Sets the initial state of self, which includes the contents of sourceCollection, if it's present."""
self._items = Array(ArrayBag.DEFAULT_CAPACITY)
AbstractBag.__init__(self, sourceCollection)
这里需要注意__init__中编写的两条语句的顺序,在运行超类中的构造方法之前,将self._items初始化为新的数组,以便有地方存储添加到新包中的任何项。
ArrayBag类中的__add__方法
def __add__(self,other):
"""Returns a new bag containing the contents of self and other."""
result = ArrayBag(self)
for item in other:
result.add(item)
return result
如果直接将这个__add__方法复制到AbstractBag类中,然后使用测试函数来测试的话,+运算符使用了AbstractBag中的__add__方法,这会引发一个异常。因为这里__add__方法试图创建一个ArrayBag的一个实例来保存其结果,但是你真正想要的并不是一个特定的实例,而是一个self类型的一个实例,而不用管这个类型是什么。要解决这个问题,我们使用Python中的type函数来获取self的类型。
def __add__(self, other):
"""Returns a new bag containing the contents of self and other."""
result = type(self)(self)
for item in other:
result.add(item)
return result
class AbstractBag(object):
"""An abstract bag implementation."""
# Constructor
def __init__(self, sourceCollection = None):
"""Sets the initial state of self, which includes the
contents of sourceCollection, if it's present."""
self._size = 0
if sourceCollection:
for item in sourceCollection:
self.add(item)
# Accessor methods
def isEmpty(self):
"""Returns True if len(self) == 0, or False otherwise."""
return len(self) == 0
def __len__(self):
"""Returns the number of items in self."""
return self._size
def __str__(self):
"""Returns the string representation of self."""
return "{" + ", ".join(map(str, self)) + "}"
def __add__(self, other):
"""Returns a new bag containing the contents
of self and other."""
result = type(self)(self)
for item in other:
result.add(item)
return result
def __eq__(self, other):
"""Returns True if self equals other,
or False otherwise."""
if self is other: return True
if type(self) != type(other) or \
len(self) != len(other):
return False
for item in self:
if not item in other:
return False
return True
from arrays import Array
from abstractbag import AbstractBag
class ArrayBag(AbstractBag):
"""An array-based bag implementation."""
# Class variable
DEFAULT_CAPACITY = 10
# Constructor
def __init__(self, sourceCollection = None):
"""Sets the initial state of self, which includes the
contents of sourceCollection, if it's present."""
self._items = Array(ArrayBag.DEFAULT_CAPACITY)
AbstractBag.__init__(self, sourceCollection)
# Accessor methods
def __iter__(self):
"""Supports iteration over a view of self."""
cursor = 0
while cursor < len(self):
yield self._items[cursor]
cursor += 1
# Mutator methods
def clear(self):
"""Makes self become empty."""
self._size = 0
self._items = Array(ArrayBag.DEFAULT_CAPACITY)
def add(self, item):
"""Adds item to self."""
# Check array memory here and increase it if necessary
self._items[len(self)] = item
self._size += 1
def remove(self, item):
"""Precondition: item is in self.
Raises: KeyError if item in not in self.
Postcondition: item is removed from self."""
# Check precondition and raise if necessary
if not item in self:
raise KeyError(str(item) + " not in bag")
# Search for the index of the target item
targetIndex = 0
for targetItem in self:
if targetItem == item:
break
targetIndex += 1
# Shift items to the left of target up by one position
for i in range(targetIndex, len(self) - 1):
self._items[i] = self._items[i + 1]
# Decrement logical size
self._size -= 1
# Check array memory here and decrease it if necessary
如果你查看AbstractBag类的代码,你会发现,几乎所有的方法(包括__init__方法)都运行其他的方法或函数,或者直接访问变量self._size,它们都不会提及包类。除了__str__方法(它使用花括号创建了一个字符串)和__eq__方法(它并不比较给定位置的成对的项)。更好的建议是:将这些方法和数据放到一个更加通用的类中,这样的类叫做AbstractCollection。
AbstractCollection类负责引入和初始化self._size变量,所有集合类都使用这个变量。AbstractCollection的__init__方法也可以将源集合中的项加入到self。这个类还包含了所有集合可用的最通用的方法:isEmpty、__len__和__add__,这里的“最通用”是指它们的实现不需要由子类来修改。
最后AbstractCollection还包含了__str__和__eq__方法的默认实现,它们在AbstractBag中的当前形式适合于无序的集合,但是大多数集合类很可能是线性的而不是无序的。因此,这两个方法在AbstractBag中保持不变,但是在AbstractCollection中提供了新的实现。新的__str__方法用方括号来分割字符串,新的__eq__方法比较给定位置成对的项,AbstractCollection的子类仍然能够自由定制__str__和__eq__以满足自己的需要。
创建AbstractCollection类和AbstractBag类的步骤:
(1)从之前实现的AbstractBag类中复制代码,然后将该类命名为AbstractCollection类
(2)通过删除父类中对__init__的调用,修改__init__方法
(3)修改__str__和__eq__方法以提供合理的默认行为
(4)从AbstractBag类中删除isEmpty、__len__和__add__方法
class AbstractCollection(object):
"""An abstract collection implementation."""
# Constructor
def __init__(self, sourceCollection = None):
"""Sets the initial state of self, which includes the
contents of sourceCollection, if it's present."""
self._size = 0
if sourceCollection:
for item in sourceCollection:
self.add(item)
# Accessor methods
def isEmpty(self):
"""Returns True if len(self) == 0, or False otherwise."""
return len(self) == 0
def __len__(self):
"""Returns the number of items in self."""
return self._size
def __str__(self):
"""Returns the string representation of self."""
return "[" + ", ".join(map(str, self)) + "]"
def __add__(self, other):
"""Returns a new bag containing the contents
of self and other."""
result = type(self)(self)
for item in other:
result.add(item)
return result
def __eq__(self, other):
"""Returns True if self equals other,
or False otherwise."""
if self is other: return True
if type(self) != type(other) or \
len(self) != len(other):
return False
otherIter = iter(other)
for item in self:
if item != next(otherIter):
return False
return True
from abstractcollection import AbstractCollection
class AbstractBag(AbstractCollection):
"""An abstract bag implementation."""
# Constructor
def __init__(self, sourceCollection = None):
"""Sets the initial state of self, which includes the
contents of sourceCollection, if it's present."""
self._size = 0
AbstractCollection.__init__(self,sourceCollection)
def __str__(self):
"""Returns the string representation of self."""
return "{" + ", ".join(map(str, self)) + "}"
def __eq__(self, other):
"""Returns True if self equals other,
or False otherwise."""
if self is other: return True
if type(self) != type(other) or len(self) != len(other):
return False
for item in self:
if not item in other:
return False
return True
抽象类充当其它一组类所共有的数据和方法的一个库。如果这些其它的类不是抽象类,它们就叫做具体类。
抽象类是不能实例化的。