美文网首页程序员
接口、实现、多态

接口、实现、多态

作者: import_hello | 来源:发表于2018-08-08 23:36 被阅读0次

转载须注明出处: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--

相关文章

  • jvm结构&运行机制&多态实现

    浅析Java虚拟机结构与机制 浅谈多态机制的意义及实现 多态:编译时多态(重载)、运行时多态(继承父类、实现接口)...

  • 接口、实现、多态

    转载须注明出处:https://www.jianshu.com/u/5e6f798c903a 参考:《数据结构(P...

  • 28. 多态

    28. 多态 Go 通过[接口]来实现多态。我们已经讨论过,在 Go 语言中,我们是隐式地实现接口。一个类型如果定...

  • 6.JDBC

    利用多态特性调用接口,然后具体的实现都是通过各个数据库接口实现。

  • python多态

    多态定义 在面向对象编程中, 多个类的相同接口差异化实现. 多态作用 通过在多个类中实现通用接口, 使得该类具有接...

  • 第四天

    一、面向对象编程 1、特性 没有封装、继承、多态 封装通过方法实现 继承通过匿名字段实现 多态通过接口实现 2、继...

  • 【面向对象】C# 为什么用接口实例化一个实现该接口的类?

    这是多态的体现。 首先接口不能实例化的。 实现接口的类 实例 = new 实现接口的类()// 这样用不好吗? /...

  • 面向对象程序设计(OOP)

    多态: 多态的本质: 父类引用指向子类的对象 或者 接口类型的引用可以只想实现接口的类的实例 多态是一种运行时期的...

  • 多态

    1.什么是多态?在面向对象语言中,接口的多种不同的实现方式即为多态。引用Charlie Calverts对多态的描...

  • Java基础7-异常;jar包

    昨日内容回顾 多态:多种状态,多态的前提条件是继承或者是实现 面向接口编程降低耦合度接口是最低标准,接口可以多重继...

网友评论

    本文标题:接口、实现、多态

    本文链接:https://www.haomeiwen.com/subject/yjmfbftx.html