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

接口、实现、多态

作者: 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--
    

    相关文章

      网友评论

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

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