編程分爲面向過程和麪向對象,首先咱們要了解什麼是面向對象。python
面向過程就是咱們以前學的內容,主要是函數式,其核心是過程,過程即解決問題的步驟,面向過程的設計就比如精心設計好一條流水線,考慮周全何時處理什麼東西。linux
優勢是:極大的下降了寫程序的複雜度,只須要順着要執行的步驟,堆疊代碼便可。git
缺點是:一套流水線或者流程就是用來解決一個問題,代碼牽一髮而動全身。程序員
應用場景:一旦完成基本不多改變的場景,著名的例子有Linux內核,git,以及Apache HTTP Server等。算法
面向對象的程序設計的核心是對象(上帝式思惟),對象就是指由咱們程序員創造出來的,因此能夠吧本身理解爲上帝,對象的一切屬性都須要咱們給予,編程
優勢是:解決了程序的擴展性。對某一個對象單獨修改,會馬上反映到整個體系中,如對遊戲中一我的物參數的特徵和技能修改都很容易。以使程序的維護和擴展變得更簡單,而且能夠大大提升程序開發效率 ,另外,基於面向對象的程序可使它人更加容易理解你的代碼邏輯,從而使團隊開發變得更從容。安全
缺點:可控性差,沒法向面向過程的程序設計流水線式的能夠很精準的預測問題的處理流程與結果,面向對象的程序一旦開始就由對象之間的交互解決問題,即使是上帝也沒法預測最終結果。因而咱們常常看到一個遊戲人某一參數的修改極有可能致使bug出現,一刀砍死3我的,這個遊戲就失去平衡。微信
應用場景:需求常常變化的軟件,通常需求的變化都集中在用戶層,互聯網應用,企業內部軟件,遊戲等都是面向對象的程序設計大顯身手的好地方。網絡
面向對象編程其實就是對 「類」 和 「對象」 的使用。app
類:具備相同特徵的一類事物(人類、狗類、武器類等等)
對象/實例:具體的某一個事物(愛摳鼻的如花,愛日天的泰迪,擁有炫酷皮膚的英雄級武器等等)
實例化:類——>對象的過程(這在生活中表現的不明顯,咱們在後面再慢慢解釋)
注:在python中,用變量表示特徵,用函數表示技能,於是具備相同特徵和技能的一類事物就是‘類’,對象是則是這一類事物中具體的一個。
注:使用函數式編程和麪向對象編程方式來執行一個「方法」的方式爲——面向對象:【建立對象】【經過對象執行方法】 函數編程:【執行函數】
難點:待弄懂面向對象後再來看這裏
class MyType(type): """ 自定義的元類metaclass,用於建立類的類,對於此類而言,普通的類只是它實例化後獲得的對象 若是沒有自定義這個類,就會用內建的元類 """ def __init__(self,*args,**kwargs): super(SingletonType,self).__init__(*args,**kwargs) def __call__(cls, *args, **kwargs): obj = cls.__new__(cls,*args, **kwargs) cls.__init__(obj,*args, **kwargs) # Foo.__init__(obj) return obj class Foo(metaclass=SingletonType): """ 本身定義的普通的類,是type類的對象 """ def __init__(self,name): self.name = name def __new__(cls, *args, **kwargs): return object.__new__(cls, *args, **kwargs) obj = Foo('name')#由類實例化生成的對象 """ 原理剖析: 1.內存首先加載元類,而後加載到Foo類的時候就至關於生成了元類的對象,因此會執行元類 中的__init__方法 2.內存加載到obj,obj是由類Foo實例化來的,而Foo實例化的過程就是Foo加括號,obj被實例化的 時候回執行Foo類的__init__,可是在執行以前還需執行3個方法。往下看…… 3.當Foo加括號時,就會執行元類中的__call__方法(對象加括號當即執行該對象的類的__call__) 4.而元類的__call__方法首先執行了其對象的__new__方法即Foo類的__new__,最後纔再執行Foo類 中的__init__方法 5.最後獲得obj對象。 後話:通常狀況下,咱們平時使用的時候就只關注obj實例化時執行其類的__init__方法就行,可是在高階 開發時必定要注意其原理 """
面向對象的三大特性:封裝、繼承、多態
class 類名: def __init__(self,參數1,參數2): self.對象的屬性1 = 參數1 self.對象的屬性2 = 參數2 def 方法名(self):pass def 方法名2(self):pass 對象名 = 類名(1,2) #對象就是實例,表明一個具體的東西 #類名() : 類名+括號就是實例化一個類,至關於調用了__init__方法 #括號裏傳參數,參數不須要傳self,其餘與init中的形參一一對應 #結果返回一個對象 對象名.對象的屬性1 #查看對象的屬性,直接用 對象名.屬性名 便可 對象名.方法名() #調用類中的方法,直接用 對象名.方法名() 便可
建立一個類就會建立一個類的名稱空間,用來存儲類中定義的全部名字,這些名字稱爲類的屬性
而類有兩種屬性:靜態屬性和動態屬性
關於面向對象的一系列知識,咱們經過一串代碼來說解,只要擼懂了如下代碼,有關面向對象的知識就已經入門了(前方高能):
#傳說中的有這麼一我的,他生平嫉犬如仇,誓要與惡犬大戰一場,人狗大戰一觸即發…… class Person:#定義一我的類 role='chinese'#此類中的人通通是中國人,這種定義的屬性都稱爲靜態屬性 money=200#也是靜態屬性,如下由函數定義的稱爲動態屬性 def __init__(self,name,life_value,aggressivity):#使以後創造的每個對象都有本身的名字、生命值、攻擊力 self.name=name#使對象的名字具體化,這樣每創造一個對象時就能夠給予一個不一樣的名字 self.life_value=life_value#同上,生命值 self.aggressivity=aggressivity#同上,攻擊力 def attack(self,enemy):#定義人的攻擊方式和enemy爲攻擊對象,具體傳參後就能夠進行互動 print(self.name, '痛扁', enemy.name) enemy.life_value-=self.aggressivity#人攻擊敵人後,敵人的生命值會根據人的攻擊力而減小 class Dog:#定義一個狗類 role='dog'#狗的屬性 def __init__(self,name,breed,life_value,aggressivity):#使以後創造的每個對象都有本身的名字、品種、生命值、攻擊力 self.name = name self.breed = breed self.life_value = life_value self.aggressivity = aggressivity def bite(self,enemy):#定義人的攻擊方式和enemy爲攻擊對象,具體傳參後就能夠進行互動 print(self.name,'瘋狂撕咬',enemy.name) enemy.life_value -= self.aggressivity#狗攻擊敵人後,敵人的生命值會根據人的攻擊力而減小 class Weapon:#定義了一個武器, def __init__(self,name,price,aggressivity,life_value,attack_force):#使以後創造的每個對象都有本身的名字、價格、攻擊力、生命、大招 self.name=name self.price=price self.aggressivity=aggressivity self.life_value=life_value self.attack_force=attack_force def update(self,person):#定義裝備了該武器後的人的各項屬性加成 person.money-=self.price#錢會減小 person.aggressivity+=self.aggressivity#攻擊力疊加 person.life_value+=self.life_value#生命值疊加 def kill(self,person,obj):#定義人使用該武器時又擁有的大招,攻擊敵人 print(person.name,'使用',self.name,'狂砍',obj.name) obj.life_value-=self.attack_force#敵人的生命減小了大招的傷害值 #如下是測試交互內容: #對象是關於類而實際存在的一個例子,即實例 #實例化過程,類名加括號後會自動觸發__init__函數的運行,能夠用它來爲每一個實例定製本身的特徵 egg=Person('egon',10000,50)#這裏咱們將類變成了對象,就是實例化過程,默認傳參 al=Person('alex',250,5)#能夠看出al相比egg而言徹底是個弱雞 sword=Weapon('sword_soul',50,50,1000,500)#實例化了一把名叫sword_soul的劍,賦予其相應屬性 ted=Dog('Tyler','teddy',20000,100)#實例化一隻名爲Tyler的泰迪狗,賦予其必要的屬性 print(Person.role)#查看人的role屬性,chinese print(egg.life_value)#對象名.屬性名的方式就能夠查看其相應的屬性,這裏查看的是egg的生命值10000 print(egg.aggressivity)#50 print(al.name,al.life_value)#alex 250 egg.attack(al)#egg攻擊了al print(al.life_value)#查看被攻擊後al的生命值變化,結果爲掉了200血 #egg準備買一把劍裝個逼 if egg.money>sword.price:#判斷egg的錢是否是夠買那把劍 sword.update(egg)#egg買了劍之後就擁有了劍給他的實行加成 egg.weapon=sword#egg裝備上劍 print(egg.money,egg.life_value,egg.aggressivity)#查看擁有劍之後egg的屬性,發現有了相應變化,150 11000 100 egg.attack(ted)#egg對着狗反手就是一劍,此時的狗爲ted print(ted.name,'剩餘血量',ted.life_value)#查看ted被egg刺了一劍以後的血量,Tyler 剩餘血量 19900 egg.weapon.kill(egg,ted)#egg心一狠,蓄力使用大招再次對ted刺出一劍 '''這在面向對象中是一種稱之爲組合的用法 組合指的是,在一個類中以另一個類的對象做爲數據屬性,稱爲類的組合 這裏egg組合了一個武器的對象,能夠直接egg.weapon來使用組合類中的全部方法''' print(ted.name,'剩餘血量',ted.life_value)#查看ted被egg大招擊中後的血量,Tyler 剩餘血量 19400
一:咱們定義的類的屬性到底存到哪裏了?有兩種方式查看 dir(類名):查出的是一個名字列表 類名.__dict__:查出的是一個字典,key爲屬性名,value爲屬性值 二:特殊的類屬性 類名.__name__# 類的名字(字符串) 類名.__doc__# 類的文檔字符串 類名.__base__# 類的第一個父類(在講繼承時會講) 類名.__bases__# 類全部父類構成的元組(在講繼承時會講) 類名.__dict__# 類的字典屬性 類名.__module__# 類定義所在的模塊 類名.__class__# 實例對應的類(僅新式類中)
人狗大戰的代碼最後引入了一個叫作「組合」的知識,下面咱們就要來了解面向對象的兩種重要的重用方式,一個是「組合」,一個是「繼承」。
組合指的是,在一個類中以另一個類的對象做爲數據屬性,稱爲類的組合
好比咱們建立了一個圓形的類,而後咱們又要創造一個圓環的類,忽然發現圓環中不少的代碼能夠套用圓形的類中的代碼,因而咱們就可使用「組合」的方式使咱們的代碼更精簡,美觀易懂高端大氣上檔次。
from math import pi#由於圓要用到π,這裏能夠用數學模塊導入π到程序中 class Circle: ''' 定義了一個圓形類; 提供計算面積(area)和周長(perimeter)的方法 ''' def __init__(self,radius): self.radius = radius#半徑 def area(self):#面積 return pi * self.radius * *2 def perimeter(self):#周長 return 2 * pi *self.radius circle = Circle(10) #實例化一個半徑爲10的圓 area1 = circle.area() #計算圓面積 per1 = circle.perimeter() #計算圓周長 print(area1,per1) #打印圓面積和周長 class Ring: ''' 定義了一個圓環類 提供圓環的面積和周長的方法 ''' def __init__(self,radius_outside,radius_inside): self.outsid_circle = Circle(radius_outside)#「組合」 self.inside_circle = Circle(radius_inside)#「組合」 def area(self): return self.outsid_circle.area() - self.inside_circle.area() def perimeter(self): return self.outsid_circle.perimeter() + self.inside_circle.perimeter() ring = Ring(10,5) #實例化一個外半徑是10,內半徑是5的環形 print(ring.perimeter()) #計算環形的周長 print(ring.area()) #計算環形的面積
用組合的方式創建了類與組合的類之間的關係,它是一種‘有’的關係
下面針對「組合」再舉一個小栗子:好比教授有生日,教授教python課程
class BirthDate:#定義生日 def __init__(self,year,month,day): self.year=year self.month=month self.day=day class Couse:#定義職業 def __init__(self,name,salary,period):#名字、工資、工齡 self.name=name self.price=price self.period=period class Teacher:#定義一個老師 def __init__(self,name,sex,birth,course): self.name=name self.sex=sex self.birth=birth self.course=course def teach(self): print('teaching') p1=Teacher('egon','male', BirthDate('1995','1','27'), Couse('python','28000','4 months') ) print(p1.birth.year,p1.birth.month,p1.birth.day) print(p1.course.name,p1.course.price,p1.course.period) ''' 運行結果: 1995 1 27 python 28000 4 months '''
繼承:繼承是一種建立新類的方式,在python中,新建的類能夠繼承一個或多個父類,父類又可稱爲基類或超類,新建的類稱爲派生類或子類
注:python中類的繼承分爲單繼承和多繼承而Java語言C語言只支持單繼承
對於面向對象的繼承來講,繼承就是將多個類共有的方法提取到父類中,子類僅需繼承父類而沒必要一一實現每一個方法。
class ParentClass1: #定義父類 pass class ParentClass2: #定義父類 pass class SubClass1(ParentClass1): #單繼承,基類是ParentClass1,派生類是SubClass1 pass class SubClass2(ParentClass1,ParentClass2): #python支持多繼承,用逗號分隔開多個繼承的類 pass
查看繼承:
>>> SubClass1.__bases__ #__base__只查看從左到右繼承的第一個父類,__bases__則是查看全部繼承的父類 (<class '__main__.ParentClass1'>,) >>> SubClass2.__bases__ (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
注:若是沒有指定基類,python3中的類會默認繼承object類,object是全部python類的基類,它提供了一些常見方法(如__str__)的實現。
下面小插一嘴,在python2中若是沒有指定基類,那麼這就是一個經典類,而在python3中就會默認繼承object類,這個咱們稱之爲新式類。
#python2中 class A: pass class B(A): pass #A和B都是經典類 #而在python3中,A會默認繼承object類,因此A和B都是新式類
經典類和新式類的多繼承方法的尋找方法是不一樣的
經典類——深度優先
新式類——廣度優先
以代碼進行說明:(鑽石繼承)
class D: def bar(self): print 'D.bar' class C(D): def bar(self): print 'C.bar' class B(D): def bar(self): print 'B.bar' class A(B, C): def bar(self): print 'A.bar' a = A() #執行bar()方法時,經典類:首先去A類中查找,若是A類中沒有,則繼續去B類中找,若是B類中麼有,則繼續去D類中找,若是D類中麼有,則繼續去C類中找,若是仍是未找到,則報錯 # 因此,查找順序:A --> B --> D --> C # 在上述查找bar方法的過程當中,一旦找到,則尋找過程當即中斷,便不會再繼續找了 a.bar() class D(object): def bar(self): print 'D.bar' class C(D): def bar(self): print 'C.bar' class B(D): def bar(self): print 'B.bar' class A(B, C): def bar(self): print 'A.bar' #執行bar()方法時,新式類:首先去A類中查找,若是A類中沒有,則繼續去B類中找,若是B類中麼有,則繼續去C類中找,若是C類中麼有,則繼續去D類中找,若是仍是未找到,則報錯 # 因此,查找順序:A --> B --> C --> D # 在上述查找bar方法的過程當中,一旦找到,則尋找過程當即中斷,便不會再繼續找了 a=A()
a.bar()
注:python3中有一個mro方法能夠幫你分析出繼承順序,即a.mro或者a.__mro__
繼承與抽象之間是有關係的,抽象即抽取相似或者說比較像的部分,而繼承就是抽象的反方向。
抽象最主要的做用是劃分類別(能夠隔離關注點,下降複雜度),抽象只是分析和設計的過程當中,一個動做或者說一種技巧,經過抽象就能夠獲得類
繼承是基於抽象的結果,經過編程語言去實現它,咱們實際中確定是先在腦海中或者草稿中經歷抽象這個過程,而後才能經過繼承的方式去表達出抽象的結構。
在開發程序的過程當中,若是咱們定義了一個類A,而後又想新創建另一個類B,可是類B的大部份內容與類A的相同時,咱們不可能從頭開始寫一個類B,這就用到了類的繼承的概念。經過繼承的方式新建類B,讓B繼承A,B會‘遺傳’A的全部屬性(數據屬性和函數屬性),實現代碼重用。
繼承時子類也能夠添加本身新的屬性或者在本身這裏從新定義這些屬性(不會影響到父類),這就叫派生。須要注意的是,一旦從新定義了本身的屬性且與父類重名,那麼調用新增的屬性時,就以本身爲準了。
關於繼承和派生,這裏舉個栗子:
class Animal: ''' 人和狗都是動物,因此創造一個Animal基類 ''' def __init__(self, name, aggressivity, life_value): self.name = name # 人和狗都有本身的暱稱; self.aggressivity = aggressivity # 人和狗都有本身的攻擊力; self.life_value = life_value # 人和狗都有本身的生命值; def eat(self): print('%s is eating'%self.name) class Dog(Animal): ''' 狗類,繼承Animal類 ''' def __init__(self,name,breed,aggressivity,life_value): super().__init__(name, aggressivity, life_value) #執行父類Animal的init方法 self.breed = breed #派生出了新的屬性 def bite(self, people): ''' 派生出了新的技能:狗有咬人的技能 :param people: ''' people.life_value -= self.aggressivity def eat(self): # Animal.eat(self) #super().eat() print('from Dog') class Person(Animal): ''' 人類,繼承Animal ''' def __init__(self,name,aggressivity, life_value,money): #Animal.__init__(self, name, aggressivity, life_value) #super(Person, self).__init__(name, aggressivity, life_value) super().__init__(name,aggressivity, life_value) #執行父類的init方法 self.money = money #派生出了新的屬性 def attack(self, dog): ''' 派生出了新的技能:人有攻擊的技能 :param dog: ''' dog.life_value -= self.aggressivity def eat(self): #super().eat() Animal.eat(self) print('from Person') egg = Person('egon',10,1000,600) ha2 = Dog('二愣子','哈士奇',10,1000) print(egg.name) print(ha2.name) egg.eat()
附:在python3中,子類執行父類的方法也能夠直接用super方法,上面代碼中有說明
注:像ha2.life_value之類的屬性引用,會先從實例中找life_value而後去類中找,而後再去父類中找...直到最頂級的父類
接口類:聲明某個子類兼容於某基類,定義一個接口類Interface,接口類中定義了一些接口名(就是函數名)且並未實現接口的功能,子類繼承接口類,而且實現接口中的功能。
普通的繼承覺得會使子類與父類耦合,因此每每不推薦。接口繼承的意義很是重大,接口繼承實質上是要求「作出一個良好的抽象,這個抽象規定了一個兼容接口,使得外部調用者無需關心具體細節,可一視同仁的處理實現了特定接口的全部對象」——這在程序設計上,叫作歸一化。
歸一化使得高層的外部使用者能夠不加區分的處理全部接口兼容的對象集合——就好象linux的泛文件概念同樣,全部東西均可以當文件處理,沒必要關心它是內存、磁盤、網絡仍是屏幕(固然,對底層設計者,固然也能夠區分出「字符設備」和「塊設備」,而後作出針對性的設計:細緻到什麼程度,視需求而定)。
依賴倒置原則:高層模塊不該該依賴低層模塊,兩者都應該依賴其抽象;抽象不該該應該依賴細節;細節應該依賴抽象。換言之,要針對接口編程,而不是針對實現編程。
爲什麼要用接口:接口提取了一羣類共同的函數,能夠把接口當作一個函數的集合。而後讓子類去實現接口中的函數。這麼作的意義在於歸一化,什麼叫歸一化,就是隻要是基於同一個接口實現的類,那麼全部的這些類產生的對象在使用時,從用法上來講都同樣。
歸一化,讓使用者無需關心對象的類是什麼,只須要知道這些對象都具有某些功能就能夠了,這極大地下降了使用者的使用難度。好比:咱們定義一個動物接口,接口裏定義了有跑、吃、呼吸等接口函數,這樣老鼠的類去實現了該接口,松鼠的類也去實現了該接口,由兩者分別產生一隻老鼠和一隻松鼠送到你面前,即使是你分別不到底哪隻是什麼鼠你確定知道他倆都會跑,都會吃,都能呼吸。再好比:咱們有一個汽車接口,裏面定義了汽車全部的功能,而後由本田汽車的類,奧迪汽車的類,大衆汽車的類,他們都實現了汽車接口,這樣就好辦了,你們只須要學會了怎麼開汽車,那麼不管是本田,仍是奧迪,仍是大衆咱們都會開了,開的時候根本無需關心我開的是哪一類車,操做手法(函數調用)都同樣。
#這裏咱們用支付軟件舉例進行說明 class Alipay: ''' 支付寶支付 ''' def pay(self,money): print('支付寶支付了%s元'%money) class Applepay: ''' apple pay支付 ''' def pay(self,money): print('apple pay支付了%s元'%money) class Wechatpay: def fuqian(self,money): ''' 實現了pay的功能,可是名字不同 ''' print('微信支付了%s元'%money) def pay(payment,money): ''' 支付函數,整體負責支付 對應支付的對象和要支付的金額 ''' payment.pay(money) p = Wechatpay() pay(p,200) #執行會報錯 #咱們設置一個接口,就能夠手動讓他報出異常 class Payment: def pay(self): raise NotImplementedError class Wechatpay(Payment): def fuqian(self,money): print('微信支付了%s元'%money) p = Wechatpay() #這裏不報錯 pay(p,200) #這裏報錯了 #使用abc模塊來實現接口 from abc import ABCMeta,abstractmethod class Payment(metaclass=ABCMeta): @abstractmethod def pay(self,money): pass class Wechatpay(Payment): def fuqian(self,money): print('微信支付了%s元'%money) p = Wechatpay() #不調就報錯了
抽象類:抽象類是一個特殊的類,他只能被繼承,不能被實例化
若是說類是從一堆對象中抽取相同的內容而來的,那麼抽象類就是從一堆類中抽取相同的內容而來的,內容包括數據屬性和函數屬性。
好比咱們有香蕉的類,有蘋果的類,有桃子的類,從這些類抽取相同的內容就是水果這個抽象的類,你吃水果時,要麼是吃一個具體的香蕉,要麼是吃一個具體的桃子。。。。。。你永遠沒法吃到一個叫作水果的東西。
從設計角度去看,若是類是從現實對象抽象而來的,那麼抽象類就是基於類抽象而來的。
從實現角度來看,抽象類與普通類的不一樣之處在於:抽象類中只能有抽象方法(沒有實現功能),該類不能被實例化,只能被繼承,且子類必須實現抽象方法。這一點與接口有點相似,但實際上是不一樣的
抽象類的實現:
#一切皆文件 import abc #利用abc模塊實現抽象類 class All_file(metaclass=abc.ABCMeta): all_type='file' @abc.abstractmethod #定義抽象方法,無需實現功能 def read(self): '子類必須定義讀功能' pass @abc.abstractmethod #定義抽象方法,無需實現功能 def write(self): '子類必須定義寫功能' pass # class Txt(All_file): # pass # # t1=Txt() #報錯,子類沒有定義抽象方法 class Txt(All_file): #子類繼承抽象類,可是必須定義read和write方法 def read(self): print('文本數據的讀取方法') def write(self): print('文本數據的讀取方法') class Sata(All_file): #子類繼承抽象類,可是必須定義read和write方法 def read(self): print('硬盤數據的讀取方法') def write(self): print('硬盤數據的讀取方法') class Process(All_file): #子類繼承抽象類,可是必須定義read和write方法 def read(self): print('進程數據的讀取方法') def write(self): print('進程數據的讀取方法') wenbenwenjian=Txt() yingpanwenjian=Sata() jinchengwenjian=Process() #這樣你們都是被歸一化了,也就是一切皆文件的思想 wenbenwenjian.read() yingpanwenjian.write() jinchengwenjian.read() print(wenbenwenjian.all_type) print(yingpanwenjian.all_type) print(jinchengwenjian.all_type)
抽象類的本質仍是類,指的是一組類的類似性,包括數據屬性(如all_type)和函數屬性(如read、write),而接口只強調函數屬性的類似性。抽象類是一個介於類和接口直接的一個概念,同時具有類和接口的部分特性,能夠用來實現歸一化設計
注:在python中,並無接口類這種東西,即使不經過專門的模塊定義接口,咱們也應該有一些基本的概念。
在繼承抽象類的過程當中,咱們應該儘可能避免多繼承;
而在繼承接口的時候,咱們反而鼓勵你來多繼承接口
接口隔離原則:使用多個專門的接口,而不使用單一的總接口。即客戶端不該該依賴那些不須要的接口。
多態指的就是多個形態。其實多態的概念很明確,使用方法卻很模糊,咱們基本一直都在使用多態
多態性:多態性是指在不考慮實例類型的狀況下使用實例
鴨子類型
Python崇尚鴨子類型,即‘若是看起來像、叫聲像並且走起路來像鴨子,那麼它就是鴨子。python程序員一般根據這種行爲來編寫程序。例如,若是想編寫現有對象的自定義版本,能夠繼承該對象。也能夠建立一個外觀和行爲像,但與它無任何關係的全新對象,後者一般用於保存程序組件的鬆耦合度。
封裝的含義是隱藏對象的屬性和具體細節,對外僅提供訪問功能。
封裝原則:1. 將不須要對外提供的內容都隱藏起來;2. 把屬性都隱藏,提供公共方法對其訪問。
在python中用開頭加兩個下劃線的方式將屬性私有起來
如某一個參數咱們比較隱私,不想讓別人看到和修改,但在最後的結果中會使用到這個參數,咱們就能夠將其隱藏起來,只在須要結果時調用,外部的人只能經過特定的接口看到結果但不能看到參數。這樣的好處是咱們在必要的時候修改了參數但由於接口不變因此用戶依然能夠查看結果,不管結果變不變,用戶都看不到原來的參數是否變化。(將0+1=1換成1*1=1,假如參數隱藏,用戶並不知道你換了參數)
封裝在於明確區份內外,使得類實現者能夠修改封裝內的東西而不影響外部調用者的代碼;外部使用者只知道一個接口(函數),只要接口(函數)名、參數不變,使用者的代碼永遠無需改變。這就提供一個良好的合做基礎——雙方商定好後定義者給使用者提供接口,若需求改變,定義者修改代碼,但只要接口這個基礎約定不變,使用者就無需改變。
注意:封裝毫不是等於「把不想讓別人看到、之後可能修改的東西用private隱藏起來」
真正的封裝是,通過深刻的思考,作出良好的抽象,給出「完整且最小」的接口,並使得內部細節能夠對外透明(對外透明的意思是,外部調用者能夠順利的獲得本身想要的任何功能,徹底意識不到內部細節的存在)
封裝的好處:
1. 將變化隔離;
2. 便於使用;
3. 提升複用性;
4. 提升安全性
舉個栗子:
#假設香蕉打折 class Goods: __discount = 0.8 #類的私有屬性,外部沒法被修改 def __init__(self,name,price): self.name = name self.price = price def goods_price(self): return self.price * Goods.__discount banana = Goods('banana',2)#實例化一個香蕉,2元一斤 print(banana.goods_price())輸出打折後的價錢 print(Goods.__dict__) print(Goods._Goods__discount) #若想查看私有屬性,必須從類開始調,方式爲_類名__屬性名。直接Goods.__discount是沒法查看的,因此外部人員若不知道類名就沒法查看
1.類中定義的__discount只能在內部使用
2.這種變形其實正是針對外部的變形,在外部是沒法經過__discount這個名字訪問到的。
3.在子類定義的__discount不會覆蓋在父類定義的__discount,由於子類中變造成了:_子類名__屬性名,而父類中變造成了:_父類名__屬性名,即雙下滑線開頭的屬性在繼承給子類時,子類是沒法覆蓋的。因此,若是一個類不想讓本身的子類覆蓋本身的屬性,就能夠定義爲私有屬性。
#普通狀況下定義的一個類 class Parent: def __init__(self): self.func() def func(self): print('Parent func') class Son(Parent): def func(self): print('Son func') s=Son()#Son func #由於Son類中本身有func方法,因此直接用本身的 #定義了私有屬性的類 class Parent: def __init__(self): self.__func()#這裏的self.__func()實際上是_Parent.__func() def __func(self):#這裏的self.__func()實際上是_Parent.__func() print('Parent func') class Son(Parent): def __func(self):#這裏的self.__func()實際上是_Son.__func() print('Son func') s=Son()#Parent func #這裏s調用Son()後調用__init__(self)方法,Son類中沒有因此去Parent類中尋找,接下來調用 __func(self)方法 #由於這裏的 __func(self)已變形爲_Parent.__func(),因此輸出爲Parent func,這裏就避免了父類的屬性被子類覆蓋的問題
property特性:property是一種特殊的屬性。類中的方法能夠經過@property假裝成屬性,由於屬性不能被修改
舉個栗子:
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) #同上,若是沒有被裝飾的話就得以print(c.perimeter())的形式 ''' 輸出結果: 314.1592653589793 62.83185307179586 '''
爲何要使用property呢:將一個類的函數定義成特性之後,對象再去使用的時候obj.name,根本沒法察覺本身的name是執行了一個函數而後計算出來的,這種特性的使用方式遵循了統一訪問的原則
注:面向對象的封裝有三種方式:
【public】:這種其實就是不封裝,是對外公開的
【protected】:這種封裝方式對外不公開,但對朋友(friend)或者子類公開
【private】:這種封裝對誰都不公開
可是!!!python並無在語法上把它們三個內建到本身的class機制中,哈哈哈是否是以爲被套路了。
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.deleter f1=Foo() f1.AAA f1.AAA='aaa' del f1.AAA
實際應用中的小栗子
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() print(obj.price) # 獲取商品價格80 obj.price = 200 # 修改商品原價 print(obj.price)# 獲取修改後商品價格160 del obj.price # 刪除商品原價
關於面相對象的後話:
軟件的開發其實有一整套規範,咱們所學的只是其中的一小部分,一個完整的開發過程,須要明確每一個階段的任務,在保證一個階段正確的前提下再進行下一個階段的工做,稱之爲軟件工程。
面向對象的軟件工程包括下面幾個部分:
1.面向對象分析(object oriented analysis ,OOA)
軟件工程中的系統分析階段,要求分析員和用戶結合在一塊兒,對用戶的需求作出精確的分析和明確的表述,從大的方面解析軟件系統應該作什麼,而不是怎麼去作。面向對象的分析要按照面向對象的概念和方法,在對任務的分析中,從客觀存在的事物和事物之間的關係,概括出有關的對象(對象的‘特徵’和‘技能’)以及對象之間的聯繫,並將具備相同屬性和行爲的對象用一個類class來標識。
創建一個能反映這是工做狀況的需求模型,此時的模型是粗略的。
2 面向對象設計(object oriented design,OOD)
根據第一階段造成的需求模型,對每一部分分別進行具體的設計。
首先是類的設計,類的設計可能包含多個層次(利用繼承與派生機制)。而後以這些類爲基礎提出程序設計的思路和方法,包括對算法的設計。
在設計階段並不牽涉任何一門具體的計算機語言,而是用一種更通用的描述工具(如僞代碼或流程圖)來描述
3 面向對象編程(object oriented programming,OOP)
根據面向對象設計的結果,選擇一種計算機語言把它寫成程序,能夠是python
4 面向對象測試(object oriented test,OOT)
在寫好程序後交給用戶使用前,必須對程序進行嚴格的測試,測試的目的是發現程序中的錯誤並修正它。
面向對的測試是用面向對象的方法進行測試,以類做爲測試的基本單元。
5 面向對象維護(object oriendted soft maintenance,OOSM)
正如對任何產品都須要進行售後服務和維護同樣,軟件在使用時也會出現一些問題,或者軟件商想改進軟件的性能,這就須要修改程序。
因爲使用了面向對象的方法開發程序,使用程序的維護比較容易。
由於對象的封裝性,修改一個對象對其餘的對象影響很小,利用面向對象的方法維護程序,大大提升了軟件維護的效率,可擴展性高。
注:開發一個軟件,確定是頻繁的開會討論計劃,而後才能制定合理的規劃,最後才動手寫代碼