1. 程式人生 > >面向對象之封裝

面向對象之封裝

問題 返回值 wid 數值 計算周 car 繼承 賦值 Owner

閱讀目錄

  • 一 引子
  • 二 先看如何隱藏
  • 三 封裝不是單純意義的隱藏
  • 四 特性(property)
  • 五 封裝與擴展性

一 引子

從封裝本身的意思去理解,封裝就好像是拿來一個麻袋,把小貓,小狗,小王八,還有alex一起裝進麻袋,然後把麻袋封上口子。照這種邏輯看,封裝=‘隱藏’,這種理解是相當片面的

二 先看如何隱藏

在python中用雙下劃線開頭的方式將屬性隱藏起來(設置成私有的)

技術分享圖片
#其實這僅僅這是一種變形操作
#類中所有雙下劃線開頭的名稱如__x都會自動變形成:_類名__x的形式:

class A:
    __N=0 #類的數據屬性就應該是共享的,但是語法上是可以把類的數據屬性設置成私有的如__N,會變形為_A__N
def __init__(self): self.__X=10 #變形為self._A__X def __foo(self): #變形為_A__foo print(from A) def bar(self): self.__foo() #只有在類內部才可以通過__foo的形式訪問到. #A._A__N是可以訪問到的,即這種操作並不是嚴格意義上的限制外部訪問,僅僅只是一種語法意義上的變形
技術分享圖片

這種自動變形的特點:

1.類中定義的__x只能在內部使用,如self.__x,引用的就是變形的結果

2.這種變形其實正是針對外部的變形

,在外部是無法通過__x這個名字訪問到的。

3.在子類定義的__x不會覆蓋在父類定義的__x,因為子類中變形成了:_子類名__x,而父類中變形成了:_父類名__x,即雙下滑線開頭的屬性在繼承給子類時,子類是無法覆蓋的。

這種變形需要註意的問題是:

1.這種機制也並沒有真正意義上限制我們從外部直接訪問屬性,知道了類名和屬性名就可以拼出名字:_類名__屬性,然後就可以訪問了,如a._A__N

2.變形的過程只在類的定義是發生一次,在定義後的賦值操作,不會變形

技術分享圖片

3.在繼承中,父類如果不想讓子類覆蓋自己的方法,可以將方法定義為私有的

技術分享圖片
#正常情況
>>> class A:
...     
def fa(self): ... print(from A) ... def test(self): ... self.fa() ... >>> class B(A): ... def fa(self): ... print(from B) ... >>> b=B() >>> b.test() from B #把fa定義成私有的,即__fa >>> class A: ... def __fa(self): #在定義時就變形為_A__fa ... print(from A) ... def test(self): ... self.__fa() #只會與自己所在的類為準,即調用_A__fa ... >>> class B(A): ... def __fa(self): ... print(from B) ... >>> b=B() >>> b.test() from A
View Code

封裝不是單純意義的隱藏

1:封裝數據:將數據隱藏起來這不是目的。隱藏起來然後對外提供操作該數據的接口,然後我們可以在接口附加上對該數據操作的限制,以此完成對數據屬性操作的嚴格控制。

技術分享圖片
class Teacher:
    def __init__(self,name,age):
        self.__name=name
        self.__age=age

    def tell_info(self):
        print(姓名:%s,年齡:%s %(self.__name,self.__age))
    def set_info(self,name,age):
        if not isinstance(name,str):
            raise TypeError(姓名必須是字符串類型)
        if not isinstance(age,int):
            raise TypeError(年齡必須是整型)
        self.__name=name
        self.__age=age

t=Teacher(egon,18)
t.tell_info()

t.set_info(egon,19)
t.tell_info()
View Code

2:封裝方法:目的是隔離復雜度

封裝方法舉例:

1. 你的身體沒有一處不體現著封裝的概念:你的身體把膀胱尿道等等這些尿的功能隱藏了起來,然後為你提供一個尿的接口就可以了(接口就是你的。。。,),你總不能把膀胱掛在身體外面,上廁所的時候就跟別人炫耀:hi,man,你瞅我的膀胱,看看我是怎麽尿的。

2. 電視機本身是一個黑盒子,隱藏了所有細節,但是一定會對外提供了一堆按鈕,這些按鈕也正是接口的概念,所以說,封裝並不是單純意義的隱藏!!!

3. 快門就是傻瓜相機為傻瓜們提供的方法,該方法將內部復雜的照相功能都隱藏起來了

提示:在編程語言裏,對外提供的接口(接口可理解為了一個入口),可以是函數,稱為接口函數,這與接口的概念還不一樣,接口代表一組接口函數的集合體。

技術分享圖片
#取款是功能,而這個功能有很多功能組成:插卡、密碼認證、輸入金額、打印賬單、取錢
#對使用者來說,只需要知道取款這個功能即可,其余功能我們都可以隱藏起來,很明顯這麽做
#隔離了復雜度,同時也提升了安全性

class ATM:
    def __card(self):
        print(插卡)
    def __auth(self):
        print(用戶認證)
    def __input(self):
        print(輸入取款金額)
    def __print_bill(self):
        print(打印賬單)
    def __take_money(self):
        print(取款)

    def withdraw(self):
        self.__card()
        self.__auth()
        self.__input()
        self.__print_bill()
        self.__take_money()

a=ATM()
a.withdraw()
隔離復雜度的例子

3: 了解

python並不會真的阻止你訪問私有的屬性,模塊也遵循這種約定,如果模塊名以單下劃線開頭,那麽from module import *時不能被導入,但是你from module import _private_module依然是可以導入的

其實很多時候你去調用一個模塊的功能時會遇到單下劃線開頭的(socket._socket,sys._home,sys._clear_type_cache),這些都是私有的,原則上是供內部調用的,作為外部的你,一意孤行也是可以用的,只不過顯得稍微傻逼一點點

python要想與其他編程語言一樣,嚴格控制屬性的訪問權限,只能借助內置方法如__getattr__,詳見面向對象進階

四 特性(property)

什麽是特性property

property是一種特殊的屬性,訪問它時會執行一段功能(函數)然後返回值

例一:BMI指數(bmi是計算而來的,但很明顯它聽起來像是一個屬性而非方法,如果我們將其做成一個屬性,更便於理解)

成人的BMI數值: 過輕:低於18.5 正常:18.5-23.9 過重:24-27 肥胖:28-32 非常肥胖, 高於32   體質指數(BMI)=體重(kg)÷身高^2(m)   EX:70kg÷(1.75×1.75)=22.86 技術分享圖片
class People:
    def __init__(self,name,weight,height):
        self.name=name
        self.weight=weight
        self.height=height
    @property
    def bmi(self):
        return self.weight / (self.height**2)

p1=People(egon,75,1.85)
print(p1.bmi)
View Code

例二:圓的周長和面積

技術分享圖片
import math
class Circle:
    def __init__(self,radius): #圓的半徑radius
        self.radius=radius

    @property
    def area(self):
        return math.pi * self.radius**2 #計算面積

    @property
    def perimeter(self):
        return 2*math.pi*self.radius #計算周長

c=Circle(10)
print(c.radius)
print(c.area) #可以向訪問數據屬性一樣去訪問area,會觸發一個函數的執行,動態計算出一個值
print(c.perimeter) #同上
‘‘‘
輸出結果:
314.1592653589793
62.83185307179586
‘‘‘
View Code
#註意:此時的特性arear和perimeter不能被賦值
c.area=3 #為特性area賦值
‘‘‘
拋出異常:
AttributeError: can‘t set attribute
‘‘‘

為什麽要用property

將一個類的函數定義成特性以後,對象再去使用的時候obj.name,根本無法察覺自己的name是執行了一個函數然後計算出來的,這種特性的使用方式遵循了統一訪問的原則

除此之外,看下

技術分享圖片
ps:面向對象的封裝有三種方式:
【public】
這種其實就是不封裝,是對外公開的
【protected】
這種封裝方式對外不公開,但對朋友(friend)或者子類(形象的說法是“兒子”,但我不知道為什麽大家 不說“女兒”,就像“parent”本來是“父母”的意思,但中文都是叫“父類”)公開
【private】
這種封裝對誰都不公開
技術分享圖片

python並沒有在語法上把它們三個內建到自己的class機制中,在C++裏一般會將所有的所有的數據都設置為私有的,然後提供set和get方法(接口)去設置和獲取,在python中通過property方法可以實現

技術分享圖片
class Foo:
    def __init__(self,val):
        self.__NAME=val #將所有的數據屬性都隱藏起來

    @property
    def name(self):
        return self.__NAME #obj.name訪問的是self.__NAME(這也是真實值的存放位置)

    @name.setter
    def name(self,value):
        if not isinstance(value,str):  #在設定值之前進行類型檢查
            raise TypeError(%s must be str %value)
        self.__NAME=value #通過類型檢查後,將值value存放到真實的位置self.__NAME

    @name.deleter
    def name(self):
        raise TypeError(Can not delete)

f=Foo(egon)
print(f.name)
# f.name=10 #拋出異常‘TypeError: 10 must be str‘
del f.name #拋出異常‘TypeError: Can not delete‘
技術分享圖片 技術分享圖片
class Foo:
    def __init__(self,val):
        self.__NAME=val #將所有的數據屬性都隱藏起來

    def getname(self):
        return self.__NAME #obj.name訪問的是self.__NAME(這也是真實值的存放位置)

    def setname(self,value):
        if not isinstance(value,str):  #在設定值之前進行類型檢查
            raise TypeError(%s must be str %value)
        self.__NAME=value #通過類型檢查後,將值value存放到真實的位置self.__NAME

    def delname(self):
        raise TypeError(Can not delete)

    name=property(getname,setname,delname) #不如裝飾器的方式清晰
了解:一種property的古老用法

五 封裝與擴展性

封裝在於明確區分內外,使得類實現者可以修改封裝內的東西而不影響外部調用者的代碼;而外部使用用者只知道一個接口(函數),只要接口(函數)名、參數不變,使用者的代碼永遠無需改變。這就提供一個良好的合作基礎——或者說,只要接口這個基礎約定不變,則代碼改變不足為慮。

技術分享圖片
#類的設計者
class Room:
    def __init__(self,name,owner,width,length,high):
        self.name=name
        self.owner=owner
        self.__width=width
        self.__length=length
        self.__high=high
    def tell_area(self): #對外提供的接口,隱藏了內部的實現細節,此時我們想求的是面積
        return self.__width * self.__length


#使用者
>>> r1=Room(臥室,egon,20,20,20)
>>> r1.tell_area() #使用者調用接口tell_area
400


#類的設計者,輕松的擴展了功能,而類的使用者完全不需要改變自己的代碼
class Room:
    def __init__(self,name,owner,width,length,high):
        self.name=name
        self.owner=owner
        self.__width=width
        self.__length=length
        self.__high=high
    def tell_area(self): #對外提供的接口,隱藏內部實現,此時我們想求的是體積,內部邏輯變了,只需求修該下列一行就可以很簡答的實現,而且外部調用感知不到,仍然使用該方法,但是功能已經變了
        return self.__width * self.__length * self.__high


#對於仍然在使用tell_area接口的人來說,根本無需改動自己的代碼,就可以用上新功能
>>> r1.tell_area()
8000
View Code

面向對象之封裝