面向對象【九】三大特性之封裝

python面向對象三大特性之一封裝

 

1、什麼是封裝

  在程序設計中,封裝(Encapsulation)是對具體對象的一種抽象,即將某些部分隱藏起來,在程序外部看不到,其html

含義是其餘程序沒法調用。python

  要了解封裝,離不開「私有化」,就是將類或者是函數中的某些屬性限制在某個區域以內,外部沒法調用。編程

  2、爲何要封裝

  封裝數據的主要緣由是:保護隱私(把不想別人知道的東西封裝起來)編程語言

  封裝方法的主要緣由是:隔離複雜度(好比:電視機,咱們看見的就是一個黑匣子,其實裏面有不少電器元件,對於函數

用戶來講,咱們不須要清楚裏面都有些元件,電視機把那些電器元件封裝在黑匣子裏,提供給用戶的只是幾個按鈕接口,post

經過按鈕就能實現對電視機的操做。)url

  提示:在編程語言裏,對外提供的接口(接口可理解爲了一個入口),就是函數,稱爲接口函數,這與接口的概念還spa

不同,接口表明一組接口函數的集合體。設計

  3、封裝分爲兩個層面

  封裝其實分爲兩個層面,但不管哪一種層面的封裝,都要對外界提供好訪問你內部隱藏內容的接口(接口能夠理解爲入code

口,有了這個入口,使用者無需且不可以直接訪問到內部隱藏的細節,只能走接口,而且咱們能夠在接口的實現上附加更

多的處理邏輯,從而嚴格控制使用者的訪問)

  第一個層面的封裝(什麼都不用作):建立類和對象會分別建立兩者的名稱空間,咱們只能用類名.或者obj.的方式去

訪問裏面的名字,這自己就是一種封裝。

1
2
3
4
5
print (m1.brand)  #實例化對象(m1.)
print (motor_vehicle.tag)  #類名(motor_vehicle.)
- - - - - - - - - - - - - 輸出結果 - - - - - - - - - - - - - -
春風
fuel oil

  注意:對於這一層面的封裝(隱藏),類名.和實例名.就是訪問隱藏屬性的接口

  第二個層面的封裝:類中把某些屬性和方法隱藏起來(或者說定義成私有的),只在類的內部使用、外部沒法訪問,或

者留下少許接口(函數)供外部訪問。

  Python中私有化的方法也比較簡單,即在準備私有化的屬性(包括方法、數據)名字前面加兩個下劃線便可。

  類中全部雙下劃線開頭的名稱如__x都會自動變造成:_類名__x的形式:

1
2
3
4
5
6
7
8
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的形式訪問到.  

  這種自動變形的特色:

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

    二、這種變形其實正是針對外部的變形,在外部是沒法經過__x這個名字訪問到的。

    三、在子類定義的__x不會覆蓋在父類定義的__x,由於子類中變造成了:_子類名__x,而父類中變造成了:_父

類名__x,即雙下滑線開頭的屬性在繼承給子類時,子類是沒法覆蓋的。

  注意:對於這一層面的封裝(隱藏),咱們須要在類中定義一個函數(接口函數)在它內部訪問被隱藏的屬性,而後

外部就可使用了

  這種變形須要注意的問題是:

  一、這種機制也並無真正意義上限制咱們從外部直接訪問屬性,知道了類名和屬性名就能夠拼出名字:_類名__屬

性,而後就能夠訪問了,如a._A__N

1
2
3
4
5
6
7
8
=  A()
print (a._A__N)
print (a._A__X)
print (A._A__N)
- - - - - - - - 輸出結果 - - - - - - - -
0
10
0

  二、變形的過程只在類的定義是發生一次,在定義後的賦值操做,不會變形

1
2
3
4
5
6
7
=  A()  #實例化對象a
print (a.__dict__)  #打印變形的內容
a.__Y  =  20  #新增Y的值,此時加__不會變形
print (a.__dict__)  #打印變形的內容
- - - - - - - - - 輸出結果 - - - - - - - - - -
{ '_A__X' 10 }
{ '_A__X' 10 '__Y' 20 #發現後面的Y並無變形

  三、在繼承中,父類若是不想讓子類覆蓋本身的方法,能夠將方法定義爲私有的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class  A:  #這是正常狀況
     def  fa( self ):
         print ( "from A" )
     def  test( self ):
         self .fa()
 
class  B(A):
     def  fa( self ):
         print ( "from B" )
 
=  B()
b.test()
- - - - - - - - 輸出結果 - - - - - - - - - -
from  B

  看一下把fa被定義成私有的狀況:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class  A:  #把fa定義成私有的,即__fa
     def  __fa( self ):  #在定義時就變形爲_A__fa
         print ( "from A" )
     def  test( self ):
         self .__fa()  #只會與本身所在的類爲準,即調用_A__fa
 
class  B(A):
     def  __fa( self ):  #b調用的是test,跟這個不要緊
         print ( "from B" )
 
=  B()
b.test()
- - - - - - - 輸出結果 - - - - - - - - -
from  A

  4、特性(property)

  一、什麼是特性property

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

  注意:被property裝飾的屬性會優先於對象的屬性被使用,而被propery裝飾的屬性,分紅三種:property、被裝飾

的函數名.setter、被裝飾的函數名.deleter(都是以裝飾器的形式)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class  room:  #定義一個房間的類
     def  __init__( self ,length,width,high):
         self .length  =  length  #房間的長
         self .width  =  width  #房間的寬
         self .high  =  high  #房間的高
     @property
     def  area( self ):  #求房間的平方的功能
         return  self .length  *  self .width  #房間的面積就是:長x寬
     @property
     def  perimeter( self ):  #求房間的周長的功能
         return  2  *  ( self .length  +  self .width)  #公式爲:(長 + 寬)x 2
     @property
     def  volume( self ):  #求房間的體積的功能
         return  self .length  *  self .width  *  self .high  #公式爲:長 x 寬 x 高
 
r1  =  room( 2 , 3 , 4 #實例化一個對象r1
print ( "r1.area:" ,r1.area)  #能夠像訪問數據屬性同樣去訪問area,會觸發一個函數的執行,動態計算出一個值
print ( "r1.perimeter:" ,r1.perimeter)  #同上,就不用像調用綁定方法同樣,還得加括號,才能運行
print ( "r1.volume:" ,r1.volume)  #同上,就像是把運算過程封裝到一個函數內部,咱們無論過程,只要有結果就行
- - - - - - - - - - - - 輸出結果 - - - - - - - - - - - - - - -
r1.area:  6
r1.perimeter:  10
r1.volume:  24

   注意:此時的特性arear、perimeter和volume不能被賦值。

1
2
3
4
5
6
7
8
9
r1.area  =  8  #爲特性area賦值
r1.perimeter  =  14  #爲特性perimeter賦值
r1.volume  =  24  #爲特性volume賦值
'''
拋出異常:
     r1.area = 8 #第一個就拋異常了,後面的也同樣
AttributeError: can't set attribute
 
'''

二、爲何要用property

  將一個類的函數定義成特性之後,對象再去使用的時候obj.name,根本沒法察覺本身的name是執行了一個函數而後

計算出來的,這種特性的使用方式遵循了統一訪問的原則。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class  people:  #定義一我的的類
     def  __init__( self ,name,sex):
         self .name  =  name
         self .sex  =  sex  #p1.sex = "male",遇到property,優先用property
 
     @property  #查看sex的值
     def  sex( self ):
         return  self .__sex  #返回正真存值的地方
 
     @sex .setter  #修改sex的值
     def  sex( self ,value):
         if  not  isinstance (value, str ):  #在設定值以前進行類型檢查
             raise  TypeError( "性別必須是字符串類型" #不是str類型時,主動拋出異常
         self .__sex  =  value  #類型正確的時候,直接修改__sex的值,這是值正真存放的地方
             #這裏sex前加"__",對sex變形,隱藏。
 
     @sex .deleter  #刪除sex
     def  sex( self ):
         del  self .__sex
 
p1  =  people( "egon" , "male" #實例化對象p1
print (p1.sex)  #查看p1的sex,此時要注意self.sex的優先級
p1.sex  =  "female"  #修改sex的值
print (p1.sex)  #查看修改後p1的sex
print (p1.__dict__)  #查看p1的名稱空間,此時裏面有sex
del  p1.sex  #刪除p1的sex
print (p1.__dict__)  #查看p1的名稱空間,此時發現裏面已經沒有sex了
- - - - - - - - - - - - - - - - - - - 輸出結果 - - - - - - - - - - - - - - - - - - - -
male
female
{ 'name' 'egon' '_people__sex' 'female' }
{ 'name' 'egon' }

  python並無在語法上把它們三個內建到本身的class機制中,在C++裏通常會將全部的全部的數據都設置爲私有的

,而後提供set和get方法(接口)去設置和獲取,在python中經過property方法能夠實現。

  5、封裝與擴展性

  封裝在於明確區份內外,使得類實現者能夠修改封裝內的東西而不影響外部調用者的代碼;而外部使用用者只知道一

個接口(函數),只要接口(函數)名、參數不變,使用者的代碼永遠無需改變。這就提供一個良好的合做基礎——或者說

,只要接口這個基礎約定不變,則代碼改變不足爲慮。 

1
2
3
4
5
6
7
8
9
10
11
12
#類的設計者
class  room:  #定義一個房間的類
     def  __init__( self ,name,owner,length,width,high):
         self .name  =  name
         self .owner  =  owner
         self .__length  =  length  #房間的長
         self .__width  =  width  #房間的寬
         self .__high  =  high  #房間的高
     @property
     def  area( self ):  #求房間的平方的功能
         return  self .__length  *  self .__width  #對外提供的接口,隱藏了內部的實現細節,\
                                             # 此時咱們想求的是房間的面積就是:長x寬

  實例化對象經過接口,調用相關屬性獲得想要的值:

1
2
3
4
5
#類的使用者
r1  =  room( "客廳" , "michael" , 20 , 30 , 9 #實例化一個對象r1
print (r1.area)  #經過接口使用(area),使用者獲得了客廳的面積
- - - - - - - - - - - - - 輸出結果 - - - - - - - - - - - - - -
600  #獲得了客廳的面積

  擴展原有的代碼,使功能增長:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#類的設計者,輕鬆的擴展了功能,而類的使用者徹底不須要改變本身的代碼
class  room:  #定義一個房間的類
     def  __init__( self ,name,owner,length,width,high):
         self .name  =  name  #房間名
         self .owner  =  owner  #房子的主人
         self .__length  =  length  #房間的長
         self .__width  =  width  #房間的寬
         self .__high  =  high  #房間的高
     @property
     def  area( self ):  #對外提供的接口,隱藏內部實現
         return  self .__length  *  self .__width,\
                self .__length  *  self .__width  *  self .__high  #此時咱們增長了求體積,
         # 內部邏輯變了,只需增長這行代碼就能簡單實現,並且外部調用感知不到,仍然使
         # 用該方法,可是功能已經增長了

  對於類的使用者,仍然在調用area接口的人來講,根本無需改動本身的代碼,就能夠用上新功能:

1
2
3
4
5
#類的使用者
r1  =  room( "客廳" , "michael" , 20 , 30 , 9 #實例化一個對象r1
print (r1.area)  #經過接口使用(area),使用者獲得了客廳的面積
- - - - - - - - - - - - - - 輸出結果 - - - - - - - - - - - - - - -
( 600 5400 #獲得了新增的功能的值
相關文章
相關標籤/搜索