接口、实现、多态

转载须注明出处:https://www.jianshu.com/u/5e6f798c903a

参考:《数据结构(Python 语言描述)》 - 第5章 接口、实现和多态

目录.jpg

在 C# 等静态语言中,我们会将"接口类型"简称为接口。但本文中提及的"接口"并非特指接口类型,而是以软件资源用户的视角,将软件资源为用户提供的各种操作视为接口。由于用户只关心如何使用方法,但并不关心其具体实现(这是类编写者的工作),所以接口仅含方法名、参数以及文档注释。就 Python 而言,接口可理解为"类为用户提供的一组方法签名及其文档注释"

使用 help 函数获取模块、数据类型、类、方法或函数的相关信息时,便会访问与该软件资源的接口相关的文档。比如通过 help() 获取 data types (或 classes) 的帮助文档时,可以看到一个 "Methods defined" 列表,其中包含了相关的接口。

class Cls:
    """测试类"""

    def func_1(self, argm: object) -> object:
        """方法 1"""
        return True

    def func_2(self, argm):
        """方法 2"""
        pass


help(Cls)

输出:

class Cls(builtins.object)
 |  测试类
 |  
 |  Methods defined here:
 |  
 |  func_1(self, argm:object) -> object
 |      方法 1
 |  
 |  func_2(self, argm)
 |      方法 2
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)

一个设计良好的软件资源,其特征之一便是可以清晰的区分接口和实现。当用户使用一个软件资源时,只需要关心其接口。理想情况下,这些软件资源如何实现的细节,也就是底层的算法代码和数据结构,都隐藏或封装在一个抽象的边界中。这个边界将接口和实现隔开,从而:

  • 允许用户即插即用的方式,快速地将软件资源整合起来;
  • 让用户有机会在相同软件资源的不同实现中做出选;
  • 并允许具体实现者对软件资源的实现做出修改,而不影响其用户的代码。

多态(polymorphism)是指一个软件资源的多种实现,但这些实现都遵从相同的接口或方法。比如在两个不同的实现中都使用了相同的运算符符号或方法名,这便是多态。多态方法的示例是 strlen ;多态运算符的示例是 +==

1. 接口类型

一些编程语言会提供接口(interface)类型,例如 C#。在接口类型中,我们会指定一组函数成员,但不会实现它们。因此,接口本身不会执行任何操作,它作用是为实现接口的类提供一套有关函数成员的蓝图。在创建类的过程中,必须依照接口提供的蓝图,逐一实现接口中的函数成员。

Tips:本小结中提及的"接口"表示接口类型,如 IIfc1 接口。

# 本例展示了在C#中声明和使用接口的过程
interface IIfc1{  // 声明接口
    void PrintOut(string s); // 不必实现函数体
}

class MyClass : IIfc1{ // 声明类,该类需要实现IIfc1接口
    public void PrintOut(string s){ // 实现接口
        Console.WriteLine("Calling through: {0}", s);
    }
}
class Program{
    static void Main(){
        MyClass mc = new MyClass(); // 创建类实例
        mc.PrintOut("object"); // 调用方法
    }
}

2. 伪接口类

Python 中并没有接口类型,如果我们需要事先为类提供一个蓝图,以说明类中所包含的操作,可创建一个伪接口类。伪接口类不会出现在继承链中,作用仅在于为类提供蓝图,用于说明类中所包含的操作,并确保操作和实现之间的一致性。

伪接口类通常以 "Interface" 为后缀,其中包含一组未实现的方法,所有方法都以 passreturn 结尾并包含文档字符串。pass 语句用于没有返回值的修改器方法;return 语句用于访问器方法,并返回一个简单的默认值,如 False0None 。最后,还需要为所有方法添加文档字符串,以说明该方法所做的事情。

由于在之后的内容中,我们会为一种为名"包(bag)"的无需集合(collection)类型开发接口。所以,此处以包集合为例,创建一个伪接口类:

"""
File: baginterface.py
Author: Ken Lambert
"""

class BagInterface(object):
    """Interface for all bag types."""

    # Constructor 构造器
    def __init__(self, sourceCollection = None):
        """Sets the initial state of self, which includes the
        contents of sourceCollection, if it's present."""
        pass

    # Accessor methods 访问器方法
    def isEmpty(self):
        """Returns True if len(self) == 0, or False otherwise."""
        return True
    
    def __len__(self):
        """Returns the number of items in self."""
        return 0

    def __str__(self):
        """Returns the string representation of self."""
        return ""

    def __iter__(self):
        """Supports iteration over a view of self."""
        return None

    def __add__(self, other):
        """Returns a new bag containing the contents
        of self and other."""
        return None

    def __eq__(self, other):
        """Returns True if self equals other,
        or False otherwise."""
        return False

    # Mutator methods
    def clear(self):
        """Makes self become empty."""
        pass

    def add(self, item):
        """Adds item to self."""
        pass

    def remove(self, item):
        """Precondition: item is in self.
        Raises: KeyError if item in not in self.
        Postcondition: item is removed from self."""
        pass

在设计一个软件资源的伪接口类时,首先考虑要完成哪些操作,并依次给出文档字符串;然后,依据文档字符串的描述来给出恰当的函数签名。

3. 开发基于 BagInterface 的实现

下面来为包集合开发两种基于 BagInterface 的实现。

在集合类的设计者获知了集合的伪接口类之后,类自身的设计和实现包含两个步骤:

  1. 选择一个恰当的数据结构来包含集合中的项,并确定用于表示集合状态的其它数据,将这些数据赋给 __init__ 方法中的实例变量(也有可能使用类变量)。
  2. 完成接口中指定方法的代码

注意:伪接口类的作用仅是为实现提供蓝图,不必也不需要继承伪接口类。

3.1 基于数组的实现

本节将基于 BagInterface 开发一个基于数组的实现,这里使用名为 Array 的数组类,代码如下:

"""
File: arrays.py

An Array is a restricted list whose clients can use
only [], len, iter, and str.

To instantiate, use

<variable> = array(<capacity>, <optional fill value>)

The fill value is None by default.
"""

class Array(object):
    """Represents an array."""

    def __init__(self, capacity, fillValue = None):
        """Capacity is the static size of the array.
        fillValue is placed at each position."""
        self._items = list()
        for count in range(capacity):
            self._items.append(fillValue)

    def __len__(self):
        """-> The capacity of the array."""
        return len(self._items)

    def __str__(self):
        """-> The string representation of the array."""
        return str(self._items)

    def __iter__(self):
        """Supports iteration over a view of an array."""
        return iter(self._items)

    def __getitem__(self, index):
        """Subscript operator for access at index."""
        return self._items[index]

    def __setitem__(self, index, newItem):
        """Subscript operator for replacement at index."""
        self._items[index] = newItem

a. 构造器方法

from arrays import Array

class ArrayBag(object):# 不必继承BagInterface
    """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)
        self._size = 0 # _size表示逻辑尺寸,len(Array)表示物理尺寸
        if sourceCollection:
            for item in sourceCollection:
                self.add(item)
    # --snip--

b. 无需使用数组变量的方法

当需要访问(或修改)实例变量时,应尽可能通过相应的方法来完成操作,将对变量的引用保持最小化。比如当我们需要查看包实例的逻辑尺寸时,应该运行 len(),而不是直接使用实例变量 _size

这样做的优点是,例如某个方法没有使用数组变量,那么即使将数组变为其它数据结构,也无需对该方法做出修改,可继续使用。

class ArrayBag(object):
    # --snip--
    # 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 __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
    # --snip--

c. 需要使用数组变量的方法

如果将数组变为其它数据结构,则会影响到使用数组变量的方法。因为,数据结构的改变可能会导致操作方式的变化。

class ArrayBag(object):
    # --snip--
    # Accessor methods
    def __iter__(self):
        """Supports iteration over a view of self."""
        cursor = 0
        while cursor < len(self):
            yield self._items[cursor] # 使用数组变量
            cursor += 1

    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
    
    # Mutator methods
    def clear(self):
        """Makes self become empty."""
        self._items = Array(ArrayBag.DEFAULT_CAPACITY) # 使用数组变量
        self._size = 0 

    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
        # 附加练习:如果删除了多个项,可能会造成内存的浪费。
        # 编写代码,当数组的装载因子达到某个阈值时,自动调整数组的大小

3.2 基于链表的实现

本节将基于 BagInterface 开发一个基于链表的实现,这里使用名为 Node 的链表类,代码如下:

"""
File: node.py
Author: Ken Lambert
"""

class Node(object):
    """Represents a singly linked node."""

    def __init__(self, data, next = None):
        self.data = data
        self.next = next

a. 构造器方法

"""
File: linkedbag.py
Author: Ken Lambert
"""

from node import Node

class LinkedBag(object):
    """A link-based 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._items = None # 外部头链接,初始值None表明链表为空
        self._size = 0 # 逻辑尺寸
        if sourceCollection:
            for item in sourceCollection:
                self.add(item)
    # --snip--

b. 可拷贝的方法

如果某个方法没有访问数组变量,它也就不必访问链表变量。所以那些 ArrayBag 类中没有访问数组变量的方法,可直接拷贝到 LinkedBag 中。这是之前的策略——当需要访问(或修改)实例变量时,应尽可能通过相应的方法来完成操作,将对变量的引用保持最小化——带来的回报。也是编写方法的重要一环:尽可能尝试着将数据结构隐藏到对象的方法调用之中,以使得通过调用相应的方法便可完成对数据结构的操作(always try to hide the implementing data structures behind a wall of method calls on the object being implemented.)。

以下方法没有访问数组变量,可直接拷贝到 LinkedBag 中:

class LinkedBag(object):
    # --snip--
    # 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 __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
    # --snip--

c. 需要使用链表变量的方法

class LinkedBag(object):
    # --snip--
    # Accessor methods
    def __iter__(self):
        """Supports iteration over a view of self."""
        cursor = self._items
        while not cursor is None:
            yield cursor.data
            cursor = cursor.next
            
    def __add__(self, other):
        """Returns a new bag containing the contents
        of self and other."""
        result = LinkedBag(self) # 改动
        for item in other:
            result.add(item)
        return result
    
    # Mutator methods
    def clear(self):
        """Makes self become empty."""
        # Exercise
        self._items = None  # 外部头链接,初始值None表明链表为空
        self._size = 0  # 逻辑尺寸

    def add(self, item):
        """Adds item to self's head."""
        # 在列表头部添加新项,从而利用了常数访问时间的优点
        self._items = Node(item, self._items)
        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 node containing the target item
        # probe will point to the target node, and trailer
        # will point to the one before it, if it exists
        probe = self._items
        trailer = None
        for targetItem in self:
            if targetItem == item:
                break
            trailer = probe
            probe = probe.next
        # Unhook the node to be deleted, either the first one or one
        # thereafter
        if probe == self._items:
            self._items = self._items.next
        else:
            trailer.next = probe.next
        # Decrement logical size
        self._size -= 1
    # --snip--
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容