Python設計模式 - 基礎 - 類與類之間的六種關系
在程序中需要把世間萬物抽象成相應的類,現實世界中物與物之間的關系和程序中類與類之間的關系相對應,因為世間萬物是普遍聯系的,所以程序中類與類之間也不是孤立的。在系統分析和框架設計中,根據面向對象機制的三大特性:封裝、繼承、多態,歸納和擴展出類與類之間六種不同的關系:
- 依賴關系Dependency: 在局部變量,方法的形參,或者對靜態方法的調用中實現
- 關聯關系Association: 在類的成員變量中實現,可以雙向也可以單向
- 聚合關系Aggregation: 強關聯,整體與部分的關系,但是可分離
- 組合關系Composition: 更強關聯,整體與部分的關系,不可分離
- 繼承關系Generalization:類與類,或接口與接口之間的父子關系
- 實現關系Implementation: 接口或抽象類定義好的操作集合,由實現類完成接口或抽象類的具體操作
其實從更寬泛的角度來說,類與類之間的關系其實只有三種:繼承、實現、關聯(依賴是一種弱關聯,聚合和組合是關聯中的特例)。繼承和實現是類與類之間的縱向關系,比如A類繼承了B類,C類實現了D接口;而關聯是類與類之間的橫向關系,類與類之間的關聯關系從弱到強依次為:依賴 < 關聯 < 聚合 < 組合。
縱向的繼承和實現關系很容易理解,橫向的關聯關系相對復雜些,尤其是對生命周期的不同處理,關聯、聚合、組合只能結合上下文語義才好判斷。下面就重點介紹下這些容易相互混淆的關聯關系。
依賴關系(Dependency)
概念說明
可以簡單地理解,依賴就是一個類A使用到了另一個類B,僅僅是“使用到”,類B本身並不屬於類A,或者說類A並不擁有類B。依賴關系是偶然性的、臨時性的,但是因為類B本身被類A引用到,所以B類的變化也會影響到類A。比較常見的場景就是人乘飛機從一地到另一地,飛機和人之間並沒有所屬關系,只是一種臨時性的出行工具,此時人和飛機之間就是依賴關系。
代碼實現
依賴關系在代碼上體現為三種形式:
- 類B作為類A方法的形參
- 類B作為類A方法的局部變量
- 類A調用類B的靜態方法
# 類B作為類A方法的形參 class Person(object): def flying(self, plane): plane.fly() class Plane(object): def fly(self): print ("The plane is flying.") >>> person = Person() >>> plane = Plane() >>> person.flying(plane) The plane is flying. >>>
# 類B作為類A方法的局部變量 class Person(object): def flying(self): self.plane = Plane() plane.fly() class Plane(object): def fly(self): print ("The plane is flying") >>> person = Person() >>> person.flying() The plane is flying. >>>
# 類A調用類B的靜態方法 class Person(object): def flying(self): Plane.fly() class Plane(object): @staticmethod def fly(): print ("The plane is flying.") >>> person = Person() >>> person.flying() The plane is flying. >>>
UML類圖
UML中使用帶箭頭的虛線表示,箭頭指向表示調用關系,從類A指向類B
關聯關系(Association)
概念說明
關聯關系一般長期性的、擁有性的關系,而且雙方的關系一般是平等的,如學校與學生之間、老師與學生之間。被關聯類B以類的屬性形式出現在關聯類A中,關聯可以是單向的,也可以是雙向的。
依賴關系與關聯關系的區別有動靜之分,依賴關系的偶然性和臨時性說明了動態性,關聯關系的長期性、擁有性靜態地展示了對被關聯類的引用。
代碼實現
關聯關系在代碼上體現為二種形式:
- 單向關聯:單向擁有關系,只有一個類知道另一個類的屬性和方法
- 雙向關聯:雙向擁有關系,雙方都知道對方的屬性和方法
- 自身關聯:自己關聯自己,這種情況比較少但也有用到,如鏈表
# 單向關聯
class Student(object): def __init__(self): self.title = "Seno"
def study(self): print ("Studying...") class School(object): def __init__(self): self.address = "ABC Street" self.student = Student() def act(self): print (self.student.title) self.student.study() >>> school = School() >>> school.act() Seno Studying... >>>
# 雙向關聯
class Student(object): def __init__(self): self.school = School() class School(object): def __init__(self): self.student = Student()
# 自身關聯 class Node(object): def __init__(self): self.next = Node()
UML類圖
UML中使用直線箭頭表示,箭頭指向為被關聯的類,從類A指向類B
單向關聯
雙向關聯
自我關聯
聚合關系(Aggregation)
概念說明
聚合關系是也是關聯關系的特例。普通關聯關系的兩個類一般處於同一平等層次上,而聚合關系的兩個類處於不同的層次,是整體與部分的關系。聚合關系中的整體和部分是可以分離的,生命周期也是相互獨立的,如公司與員工之間。
代碼實現
聚合關系在代碼上體現為:類A由類B聚合而成,類A包含有類B的全局對象,但類B的對象可以不在類A創建的時刻創建。
class School(object): def __init__(self): self.__students = [] def add_student(self, student): self.__students.append(student) class Student(object): pass >>> student = Student() >>> school = School() >>> school.add_student(student) >>>
UML類圖
UML中使用空心菱形+實線箭頭表示,空心菱形邊指向類A(整體),實現箭頭邊指向部分類B(部分)
組合關系(Composition)
概念說明
組合關系也是關聯關系的特例,屬於強聚合,本身也表示整體與部分的關系,但是組合關系中的整體和部分是不可分離的,整體生命周期的結束時也是部分的生命周期到頭時。如人和大腦。
聚合和組合其實都是關聯的特例,都是整體與部分的關系。它們的區別在於整體和部分是否可分離,聚合的兩個對象之間是可分離的,且具有各自的生命周期,而組合的兩個對象往往表現為一種同命相連的關系。
代碼實現
組合關系在代碼上體現為在整體類的構造方法中直接實例化成員類,因為類組合關系中整體與部分是共生的。
class Person(object): def __init__(self): print ("Person Iinitialization Start") self.__brain = Brain() print ("Person Iinitialization End") def run(self): print ("Running...") class Brain(object): def __init__(self): print ("Brain Initialization Start") print ("Brain Initialization End") >>> person = Person() Person Iinitialization Start Brain Initialization Start Brain Initialization End Person Iinitialization End >>> person.run() Running... >>>
UML類圖
UML中使用實心菱形+實線箭頭表示,實心菱形邊指向類A(整體),實線箭頭邊指向類B(部分)
繼承關系(Generalization)
概念說明
繼承指的是子類繼承父類、或子接口繼承父接口的功能並增加自己新功能的過程,是兩個類之間耦合度最大的關系之一。父類稱為基類或超類,子類也稱為派生類。子類可以繼承自抽象類或普通類。
代碼實現
繼承關系在代碼上體現為二種形式:
- 子類繼承自抽象類或普通類
- 子接口繼承自父接口:適用於Java
# 子類繼承自抽象類,必須實現父類中@abstractmethod修飾的抽象方法
from abc import ABCMeta, abstractmethod
class Animal(metaclass=ABCMeta): def __init__(self): self.name = "Animal" @abstractmethod def run(self): print ("Animal is running.") def play(self): print ("Animal is playing.") class Dog(Animal): def __init__(self): self.name = "Dog" def run(self): print ("Dog is running.") def __bark(self): print ("Dog is barking.") class Cat(Animal): def __init__(self): self.name = "Cat" def run(self): print ("Animal is running.") def play(self): print ("Cat is running.") def __jump(self): print ("Cat is jumping.") >>> dog = Dog() >>> cat = Cat() >>> dog.run() Dog is running. >>> dog.play() Animal is playing.>>> cat.run() Animal is running. >>> cat.play() Cat is running.>>>
# 子類繼承自普通類,子類方法重寫父類同名非私有方法
class Animal(object): def __init__(self): self.name = "Animal" def run(self): print ("Animal is running.") def play(self): print ("Animal is playing.") class Dog(Animal): def __init__(self): self.name = "Dog" def run(self): print ("Dog is running.") def __bark(self): print ("Dog is barking.") class Cat(Animal): def __init__(self): self.name = "Cat" def play(self): print ("Cat is running.") def __jump(self): print ("Cat is jumping.") >>> dog = Dog() >>> cat = Cat() >>> dog.run() Dog is running. >>> dog.play() Animal is playing.>>> cat.run() Animal is running. >>> cat.play() Cat is running.>>>
UML類圖
UML中使用實線+空心箭頭表示,箭頭由子類指向父類、或子接口指向父接口
實現關系(Implementation)
概念說明
實現關系是指一個類實現一個或多個接口功能的過程,這裏的接口更多的是一種契約或規範。實現是兩個類之間或類與接口之間耦合度最大的關系之一,在這種關系中,類實現了接口或接口類中所聲明的操作。
代碼實現
實現關系在代碼上體現為二種形式:
- 類具體實現接口中所聲明的操作:如Java中支持原生interface,可以直接implement
- 類具體實現接口類中所聲明的操作:如python中無原生interface,這裏的接口類更多的是邏輯上的契約或規範
class Car(object): def engine(self): raise NotImplementedError class Benz(Car): def engine(self): print ("Benz is running.") class BMW(Car): def engine(self): print ("BMW is running.") >>> benz = Benz() >>> bmw = BMW() >>> benz.engine() Benz is running. >>> bmw.engine() BMW is running. >>>
UML類圖
UML中使用虛線+空心箭頭表示,箭頭由實現類指向接口
Python設計模式 - 基礎 - 類與類之間的六種關系