2019年1月13日 星期日

python - 封裝

封裝

【封裝】
            隱藏對象的屬性和實現細節,僅對外提供公共訪問方式。

【好處】
1. 將變化隔離
2. 便於使用
3. 提高復用性
4. 提高安全性

【封裝原則】
       1. 將不需要對外提供的內容都隱藏起來
       2. 把屬性都隱藏,提供公共方法對其訪問。

私有變量和私有方法

在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 定義成私有的,即__faclass A:
    def __fa(self): #在定義時就變形為_A__fa         print('from A')
    def test(self):
        self.__fa() 
        #只會與自己所在的類為準,        #即調用_A__faclass B(A):
    def __fa(self):
        print('from B')

b = B()
b.test()
from  A

封裝與擴展性

封裝在於明確區分內外,使得類實現者可以修改封裝內的東西而不影響外部調用者的代碼;
而外部使用用者只知道一個接口(函數), 只要接口 (函數)名 ,參數不變,使用者的代碼永遠
無須改變。 這就提供一個良好的合作基礎 -- 或者說,只要接口這個基類約定不變,則代碼改變不足為慮。
#類的設計者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
#類的設計者,輕鬆的擴展了功能,而類的使用者完全不需要改變自己的代碼class Room:
    def __init__(self,name,owner,width,lenght,high):
        self.name = name
        self.owner = owner
        self.__width = width
        self.__length = lenght
        self.__high = high
    def tell_area(self):
        #對外提供的接口,隱藏內部實現, 此時我們想求的是體積        #內部邏輯變了,只需求修改下列一行就可以很簡單的實現        #而且外部調用感知不到,仍然使用該方法,但是功能已經改變了        return  self.__width*self.__length*self.__high

#對於仍然在使用 tell_area 接口的人來說,根本無須改動自己的代碼r1.tell_area()

property屬性

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

.舉例一:
#例一: BMI 指數 (bmi 是計算而來的,# 但很明顯它聽起來像是一個屬性而非方法# 如果我們將其作成一個屬性,更便於理解)
#成人的BMI數值#過輕 低於 18.5# 正常: 18.5 - 23.9# 過重: 28 - 32# 非常胖高於  32# 算式:  體質數值(BMI) = 體重(kg) / 身高^ 2 (m)# EX : 70kg /(1.75*1.75) = 22.86

.View code
class people :
    def __init__(self,name,weight,heigh):
        self.name = name
        self.weight =  weight
        self.heigh = heigh
    @property    def bmi(self):
        return  self.weight / (self.heigh**2)

p1 = people('egon',75,1.85)
print(p1.bmi)
執行結果:
21.913805697589478
.例二: 圓的周長與面積
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) #同上
執行結果:
10
314.1592653589793
62.83185307179586

#注意: 此時的特性 area 和 perimeter 不能被賦值
c.area = 3 #為特效area 賦值
'''
拋出異常
AttribError : cat't set attribute
"""

#___為甚麼要用property  :
將一個類的函數定義成特性以後,對象再去使用的時候obj.name是執行了一個函數然後計算出來的,這種特性的使用方式 遵循了統一訪問的原則

除此之外,看下:
ps :  面向對象的封裝有三種方式:

public】 
這種其實就是不封裝,完全對外公開的
protected
這種封裝方式對外不公開,但對朋友(friend) 或者子類公開
【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):
        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'
執行結果:
egon


.一個靜態屬性property 本質就是實現了 get , set ,delete 三種方法
範例一:
class Foo:
    @property    def AAA(self):
        print('get的時候運行我啊')

    @AAA.setter    def AAA (self,value):
        print('set的時候運行我啊')

    @AAA.deleter    def AAA(self):
        print('delete的時候運行我啊')
# 只有在屬性AAA定義 property 後才能定義 AAA.setter ,AAA.deleterf1 = Foo()
f1.AAA
f1.AAA = 'aaa'del f1.AAA
執行結果:
et的時候運行我啊
set的時候運行我啊
delete的時候運行我啊


範例二:
class  Foo:
    def get_AAA(self):
        print('get的時候運行我')

    def set_AAA(self,value):
        print('set的時候運行我')

    def delete_AAA(self):
        print('delete的時候運行我')

    AAA = property(get_AAA,set_AAA,delete_AAA) #內置propert 三個參數get,set,delete__對應
f1 = Foo()
f1.AAA
f1.AAA = 'aaa'del  f1.AAA
執行結果:
get的時候運行我
set的時候運行我
delete的時候運行我

#怎麼用?
class Goods:
    def __init__(self):
        #原價        self.original_price = 100        #折扣        self.discount = 0.8
    @property    def price(self):
        #實際價格 = 原價 * 折扣        new_price = self.original_price * self.discount
        return  new_price

    @price.setter    def price(self,value):
        self.original_price = value

    @price.deleter    def price(self):
        del  self.original_price

obj = Goods()
obj.price        #獲取商品價格obj.price = 200  #修改商品原價print(obj.price)
del  obj.price   #刪除商品原價

執行結果:
160.0

classmethod

class Classmethod_Demo():
    role = 'dog'

    @classmethod    def func(cls):
        print(cls.role)

Classmethod_Demo.func()

執行結果:
dog


staticmethod

class staticmethod_Demo():
    role = 'dog'
    @staticmethod    def func ():
        print("當普通方法用")

staticmethod_Demo.func()
執行結果:
當普通方法用











.廣義上面向對象的封裝:  代碼保護,面向對像的思想本身就是一種。
.只讓自己的對象能調用自己類中的方法

.廣義上的封裝 -- 面向對向的三大特性之一
.屬性 和  方法都藏起來 不讓你看見

class Person:
    __key = 123 #私有靜態屬性    def __init__(self,name,passwd):
        self.name = name
        self.__passwd = passwd #私有屬性
    def __get_pwd(self):    #私有方法        return  self.__passwd
        # 只要在類的內部使用私有屬性,        # 就會自動帶上_類名
    def login(self):   #正常的方法調用私有的方法        self.__get_pwd()


alex = Person("alex","alex3714")
print(alex.__Person__passwd) #_類名__屬性名print(alex.get_pwd())

#所有的私有 都是在變量的左邊加上雙下畫線        #對象的私有屬性        #類中的私有方法        #類中的靜態私有屬性#所有的私有的 都不能再累的外部使用



































沒有留言:

張貼留言