編程范式
編程是 程序 員 用特定的語法+數據結構+算法組成的代碼來告訴計算機如何執行任務的過程 , 一個程序是程序員為了得到一個任務結果而編寫的一組指令的集合,正所謂條條大路通羅馬,實現一個任務的方式有很多種不同的方式, 對這些不同的編程方式的特點進行歸納總結得出來的編程方式類別,即為編程范式。 不同的編程范式本質上代表對各種類型的任務采取的不同的解決問題的思路, 大多數語言只支持一種編程范式,當然也有些語言可以同時支持多種編程范式。 兩種最重要的編程范式分別是面向過程編程和面向對象編程。
面向過程編程(Procedural Programming) Procedural programming uses a list of instructions to tell the computer what to do step-by-step. 面向過程編程依賴 - 你猜到了- procedures,一個procedure包含一組要被進行計算的步驟, 面向過程又被稱為top-down languages, 就是程序從上到下一步步執行,一步步從上到下,從頭到尾的解決問題 。基本設計思路就是程序一開始是要著手解決一個大的問題,然后把一個大問題分解成很多個小問題或子過程,這些子過程再執行的過程再繼續分解直到小問題足夠簡單到可以在一個小步驟范圍內解決。
舉個典型的面向過程的例子, 數據庫備份, 分三步,連接數據庫,備份數據庫,測試備份文件可用性。
代碼如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | def db_conn(): ???? print ( "connecting db..." ) def db_backup(dbname): ???? print ( "導出數據庫..." ,dbname) ???? print ( "將備份文件打包,移至相應目錄..." ) def db_backup_test(): ???? print ( "將備份文件導入測試庫,看導入是否成功" ) def main(): ???? db_conn() ???? db_backup( 'my_db' ) ???? db_backup_test() if __name__ = = '__main__' : ???? main() |
這樣做的問題也是顯而易見的,就是如果你要對程序進行修改,對你修改的那部分有依賴的各個部分你都也要跟著修改, 舉個例子,如果程序開頭你設置了一個變量值 為1 , 但如果其它子過程依賴這個值 為1的變量才能正常運行,那如果你改了這個變量,那這個子過程你也要修改,假如又有一個其它子程序依賴這個子過程 , 那就會發生一連串的影響,隨著程序越來越大, 這種編程方式的維護難度會越來越高。 所以我們一般認為, 如果你只是寫一些簡單的腳本,去做一些一次性任務,用面向過程的方式是極好的,但如果你要處理的任務是復雜的,且需要不斷迭代和維護 的, 那還是用面向對象最方便了。
面向對象編程
OOP編程是利用“類”和“對象”來創建各種模型來實現對真實世界的描述,使用面向對象編程的原因一方面是因為它可以使程序的維護和擴展變得更簡單,并且可以大大提高程序開發效率 ,另外,基于面向對象的程序可以使它人更加容易理解你的代碼邏輯,從而使團隊開發變得更從容。
面向對象的幾個核心特性如下
Class 類 一個類即是對一類擁有相同屬性的對象的抽象、藍圖、原型。在類中定義了這些對象的都具備的屬性(variables(data))、共同的方法
Object 對象 一個對象即是一個類的實例化后實例,一個類必須經過實例化后方可在程序中調用,一個類可以實例化多個對象,每個對象亦可以有不同的屬性,就像人類是指所有人,每個人是指具體的對象,人與人之前有共性,亦有不同
Encapsulation 封裝 在類中對數據的賦值、內部調用對外部用戶是透明的,這使類變成了一個膠囊或容器,里面包含著類的數據和方法
Inheritance 繼承 一個類可以派生出子類,在這個父類里定義的屬性、方法自動被子類繼承
Polymorphism 多態 多態是面向對象的重要特性,簡單點說:“一個接口,多種實現”,指一個基類中派生出了不同的子類,且每個子類在繼承了同樣的方法名的同時又對父類的方法做了不同的實現,這就是同一種事物表現出的多種形態。 編程其實就是一個將具體世界進行抽象化的過程,多態就是抽象化的一種體現,把一系列具體事物的共同點抽象出來, 再通過這個抽象的事物, 與不同的具體事物進行對話。 對不同類的對象發出相同的消息將會有不同的行為。比如,你的老板讓所有員工在九點鐘開始工作, 他只要在九點鐘的時候說:“開始工作”即可,而不需要對銷售人員說:“開始銷售工作”,對技術人員說:“開始技術工作”, 因為“員工”是一個抽象的事物, 只要是員工就可以開始工作,他知道這一點就行了。至于每個員工,當然會各司其職,做各自的工作。 多態允許將子類的對象當作父類的對象使用,某父類型的引用指向其子類型的對象,調用的方法是該子類型的方法。這里引用和調用方法的代碼編譯前就已經決定了,而引用所指向的對象可以在運行期間動態綁定
面向對象編程(Object-Oriented Programming?)介紹
- 寫重復代碼是非常不好的低級行為
- 你寫的代碼需要經常變更?
1 2 3 4 5 6 7 8 9 10 11 | #role 1 name = 'Alex' role = 'terrorist' weapon = 'AK47' life_value = 100 #rolw 2 name2 = 'Jack' role2 = 'police' weapon2 = 'B22' life_value2 = 100 |
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 | #role 1 name = 'Alex' role = 'terrorist' weapon = 'AK47' life_value = 100 money = 10000 #rolw 2 name2 = 'Jack' role2 = 'police' weapon2 = 'B22' life_value2 = 100 money2 = 10000 #role 3 name3 = 'Rain' role3 = 'terrorist' weapon3 = 'C33' life_value3 = 100 money3 = 10000 #rolw 4 name4 = 'Eric' role4 = 'police' weapon4 = 'B51' life_value4 = 100 money4 = 10000 |
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 | roles = { ???? 1 :{ 'name' : 'Alex' , ??????? 'role' : 'terrorist' , ??????? 'weapon' : 'AK47' , ??????? 'life_value' : 100 , ??????? 'money' : 15000 , ??????? }, ???? 2 :{ 'name' : 'Jack' , ??????? 'role' : 'police' , ??????? 'weapon' : 'B22' , ??????? 'life_value' : 100 , ???????? 'money' : 15000 , ??????? }, ???? 3 :{ 'name' : 'Rain' , ??????? 'role' : 'terrorist' , ??????? 'weapon' : 'C33' , ??????? 'life_value' : 100 , ??????? 'money' : 15000 , ??????? }, ???? 4 :{ 'name' : 'Eirc' , ??????? 'role' : 'police' , ??????? 'weapon' : 'B51' , ??????? 'life_value' : 100 , ??????? 'money' : 15000 , ??????? }, } print (roles[ 1 ]) #Alex print (roles[ 2 ]) #Jack |
- 被打中后就會掉血的功能
- 開槍功能
- 換子彈
- 買槍
- 跑、走、跳、下蹲等動作
- 保護人質(僅適用于警察)
- 不能殺同伴
- 。。。
1 2 3 4 5 6 7 8 9 10 11 | def shot(by_who): ???? #開了槍后要減子彈數 ???? pass def got_shot(who): ???? #中槍后要減血 ???? who[‘life_value’] - = 10 ???? pass def buy_gun(who,gun_name): ???? #檢查錢夠不夠,買了槍后要扣錢 ???? pass ... |
- 每個角色定義的屬性名稱是一樣的,但這種命名規則是我們自己約定的,從程序上來講,并沒有進行屬性合法性檢測,也就是說role 1定義的代表武器的屬性是weapon, role 2 ,3,4也是一樣的,不過如果我在新增一個角色時不小心把weapon 寫成了wepon , 這個程序本身是檢測 不到的
- terrorist 和police這2個角色有些功能是不同的,比如police是不能殺人質的,但是terrorist可能,隨著這個游戲開發的更復雜,我們會發現這2個角色后續有更多的不同之處, 但現在的這種寫法,我們是沒辦法 把這2個角色適用的功能區分開來的,也就是說,每個角色都可以直接調用任意功能,沒有任何限制。
- 我們在上面定義了got_shot()后要減血,也就是說減血這個動作是應該通過被擊中這個事件來引起的,我們調用get_shot(),got_shot()這個函數再調用每個角色里的life_value變量來減血。 但其實我不通過got_shot(),直接調用角色roles[role_id][‘life_value’] 減血也可以呀,但是如果這樣調用的話,那可以就是簡單粗暴啦,因為減血之前其它還應該判斷此角色是否穿了防彈衣等,如果穿了的話,傷害值肯定要減少,got_shot()函數里就做了這樣的檢測,你這里直接繞過的話,程序就亂了。 因此這里應該設計 成除了通過got_shot(),其它的方式是沒有辦法給角色減血的,不過在上面的程序設計里,是沒有辦法實現的。?
- 現在需要給所有角色添加一個可以穿防彈衣的功能,那很顯然你得在每個角色里放一個屬性來存儲此角色是否穿 了防彈衣,那就要更改每個角色的代碼,給添加一個新屬性,這樣太low了,不符合代碼可復用的原則
class Dog(object):nationality = 'jp'def __init__(self,name): #傳參 構造方法,初始化方法self.NAME = nameself.__heart = 'normal' #私有屬性def sayhi(self): #類的方法(類的功能)print('hello,my name is ',self.NAME)def __del__(self):print('haha goodbye') d =Dog('lwq') #lwq是d的成員屬性 #先去實例化 #Dog(d,'lwq') #實例化后產生的對象,就是實例。self其實就是實例本身 d2 = Dog('hsc') d.sayhi() #執行 #d.sayhi(d) #d2.sayhi() #print(d._Dog__heart) #強制調用 #print(d._heart) #不能打印,打印出錯 print(d.nationality) d.nationality = 'CN' #自己修改公有屬性,相當于自己在內存上創建了一個空間,就不會再調用公有的屬性。 print(d2.nationality) #調用共有的屬性,除非自己修改后,才是找自己的
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class Role( object ): ???? def __init__( self ,name,role,weapon,life_value = 100 ,money = 15000 ): ???????? self .name = name ???????? self .role = role ???????? self .weapon = weapon ???????? self .life_value = life_value ???????? self .money = money ???? def shot( self ): ???????? print ( "shooting..." ) ???? def got_shot( self ): ???????? print ( "ah...,I got shot..." ) ???? def buy_gun( self ,gun_name): ???????? print ( "just bought %s" % gun_name) r1 = Role( 'Alex' , 'police' ,'AK47’) #生成一個角色 r2 = Role( 'Jack' , 'terrorist' ,'B22’)? #生成一個角色 |
- 代碼量少了近一半
- 角色和它所具有的功能可以一目了然看出來
1 2 3 4 5 6 7 8 9 | class Dog( object ): ???? print ( "hello,I am a dog!" ) d = Dog() #實例化這個類, #此時的d就是類Dog的實例化對象 #實例化,其實就是以Dog類為模版,在內存里開辟一塊空間,存上數據,賦值成一個變量名 |
上面的代碼其實有問題,想給狗起名字傳不進去。
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Dog( object ): ???? def __init__( self ,name,dog_type): ???????? self .name = name ???????? self . type = dog_type ???? def sayhi( self ): ???????? print ( "hello,I am a dog, my name is " , self .name) d = Dog( 'LiChuang' , "京巴" ) d.sayhi() |
為什么有__init__? 為什么有self? 此時的你一臉蒙逼,相信不畫個圖,你的智商是理解不了的!
畫圖之前, 你先注釋掉這兩句
1 2 3 4 | # d = Dog('LiChuang', "京巴") # d.sayhi() print (Dog) |
沒實例直接打印Dog輸出如下
1 | < class '__main__.Dog' > |
這代表什么?代表 即使不實例化,這個Dog類本身也是已經存在內存里的對不對, yes, cool,那實例化時,會產生什么化學反應呢?
?根據上圖我們得知,其實self,就是實例本身!你實例化時python會自動把這個實例本身通過self參數傳進去。
你說好吧,假裝懂了, 但下面這段代碼你又不明白了, 為何sayhi(self),要寫個self呢?
1 2 3 4 5 6 7 | class Role( object ): #定義一個類, class是定義類的語法,Role是類名,(object)是新式類的寫法,必須這樣寫,以后再講為什么 ???? def __init__( self ,name,role,weapon,life_value = 100 ,money = 15000 ): #初始化函數,在生成一個角色時要初始化的一些屬性就填寫在這里 ???????? self .name = name #__init__中的第一個參數self,和這里的self都 是什么意思? 看下面解釋 ???????? self .role = role ???????? self .weapon = weapon ???????? self .life_value = life_value ???????? self .money = money |
1 2 | r1 = Role( 'Alex' , 'police' ,'AK47’) #生成一個角色 , 會自動把參數傳給Role下面的__init__(...)方法 r2 = Role( 'Jack' , 'terrorist' ,'B22’)? #生成一個角色 |
我們看到,上面的創建角色時,我們并沒有給__init__傳值,程序也沒未報錯,是因為,類在調用它自己的__init__(…)時自己幫你給self參數賦值了,?
1 2 | r1 = Role( 'Alex' , 'police' , 'AK47’) #此時self 相當于 r1 ,? Role(r1,' Alex ',' police ',' AK47’) r2 = Role( 'Jack' , 'terrorist' , 'B22’)#此時self 相當于 r2, Role(r2,' Jack ',' terrorist ',' B22’) |
- 在內存中開辟一塊空間指向r1這個變量名
- 調用Role這個類并執行其中的__init__(…)方法,相當于Role.__init__(r1,'Alex','police',’AK47’),這么做是為什么呢? 是為了把'Alex','police',’AK47’這3個值跟剛開辟的r1關聯起來,是為了把'Alex','police',’AK47’這3個值跟剛開辟的r1關聯起來,是為了把'Alex','police',’AK47’這3個值跟剛開辟的r1關聯起來,重要的事情說3次, 因為關聯起來后,你就可以直接r1.name, r1.weapon 這樣來調用啦。所以,為實現這種關聯,在調用__init__方法時,就必須把r1這個變量也傳進去,否則__init__不知道要把那3個參數跟誰關聯呀。
- 明白了么哥?所以這個__init__(…)方法里的,self.name = name , self.role = role 等等的意思就是要把這幾個值 存到r1的內存空間里。
1 2 | def buy_gun( self ,gun_name): ???? print (“ % s has just bought % s” % ( self .name,gun_name) ) |
1 2 | r1 = Role( 'Alex' , 'police' , 'AK47' ) r1.buy_gun( "B21”) #python 會自動幫你轉成 Role.buy_gun(r1,”B21" ) |
- 上面的這個r1?=?Role('Alex','police','AK47’)動作,叫做類的“實例化”, 就是把一個虛擬的抽象的類,通過這個動作,變成了一個具體的對象了, 這個對象就叫做實例
- 剛才定義的這個類體現了面向對象的第一個基本特性,封裝,其實就是使用構造方法將內容封裝到某個具體對象中,然后通過對象直接或者self間接獲取被封裝的內容
面向對象的特性:
封裝
封裝最好理解了。封裝是面向對象的特征之一,是對象和類概念的主要特性。
封裝,也就是把客觀事物封裝成抽象的類,并且類可以把自己的數據和方法只讓可信的類或者對象操作,對不可信的進行信息隱藏。
繼承
面向對象編程 (OOP) 語言的一個主要功能就是“繼承”。繼承是指這樣一種能力:它可以使用現有類的所有功能,并在無需重新編寫原來的類的情況下對這些功能進行擴展。
通過繼承創建的新類稱為“子類”或“派生類”。
被繼承的類稱為“基類”、“父類”或“超類”。
繼承的過程,就是從一般到特殊的過程。
要實現繼承,可以通過“繼承”(Inheritance)和“組合”(Composition)來實現。
在某些 OOP 語言中,一個子類可以繼承多個基類。但是一般情況下,一個子類只能有一個基類,要實現多重繼承,可以通過多級繼承來實現。
繼承概念的實現方式主要有2類:實現繼承、接口繼承。
OO開發范式大致為:劃分對象→抽象類→將類組織成為層次化結構(繼承和合成) →用類與實例進行設計和實現幾個階段。
class SchoolMember(object):member = 0def __init__(self,name,age,sex):self.name = nameself.age = ageself.sex = sexself.enroll()def enroll(self):print('just enrolled a new school member [%s]'%self.name)SchoolMember.member +=1def tell(self):print('----%s------'%self.name)for k,v in self.__dict__.items():print('\t',k,v)def __del__(self):print('開除了[%s]....'%self.name)SchoolMember.member -=1 class School(object):"""學校類"""def open_branch(self,addr):print('openning a newbranch in ',addr) class Teacher(SchoolMember,School): #這里的School是多繼承"""講師類"""def __init__(self,name,age,sex,salary,course):#SchoolMember.__init__(self,name,age,sex) #經典類寫法super(Teacher,self).__init__(name,age,sex) #新式類寫法self.salary = salaryself.course = coursedef teaching(self):print('Teacher [%s] is teaching [%s]'%self.name,self.course) class Student(SchoolMember):def __init__(self,name,age,sex,course,tuition):SchoolMember.__init__(self,name,age,sex)self.course = courseself.tuition = tuitionself.amount = 0def pay_tuition(self,amount):print('student [%s] has just paied [%s]'%(self.name,self.amount))self.amount +=amount t1 = Teacher('wusir',28,'M',3000,'python') s1 = Student('lwq',12,'M','python',11000) s2 = Student('hsc',11,'M','python',11000) t1.tell() s1.tell() del t1
?
關聯
class F1:def __init__(self,n):self.N = nprint('F1') class F2:def __init__(self,arg1):self.a = arg1print('F2') class F3:def __init__(self,arg2):self.b = arg2print('F3') o1 = F1('alex') o2 = F2(o1) o3 = F3(o2)#####輸出alex o3.b.a.N
繼承
class F1:def __init__(self):print('F1')def a1(self):print('F1a1')def a2(self):print('F1a2') class F2(F1):def __init__(self):print('F2')def a1(self):self.a2()print('F2a1')def a2(self):print('F2a2') class F3(F2):def __init__(self):print('F3')def a2(self):print('F3a2') obj = F3() obj.a1()
?
?
class SchoolMember(object):member = 0def __init__(self,name,age,sex):self.name = nameself.age = ageself.sex = sexself.enroll()def enroll(self):print('just enrolled a new school member [%s]'%self.name)SchoolMember.member +=1def tell(self):print('----%s------'%self.name)for k,v in self.__dict__.items():print('\t',k,v)def __del__(self):print('開除了[%s]....'%self.name)SchoolMember.member -=1 class School(object):"""學校類"""def open_branch(self,addr):print('openning a newbranch in ',addr) class Teacher(SchoolMember,School): #這里的School是多繼承"""講師類"""def __init__(self,name,age,sex,salary,course):#SchoolMember.__init__(self,name,age,sex) #經典類寫法super(Teacher,self).__init__(name,age,sex) #新式類寫法self.salary = salaryself.course = coursedef teaching(self):print('Teacher [%s] is teaching [%s]'%self.name,self.course) class Student(SchoolMember):def __init__(self,name,age,sex,course,tuition):SchoolMember.__init__(self,name,age,sex)self.course = courseself.tuition = tuitionself.amount = 0def pay_tuition(self,amount):print('student [%s] has just paied [%s]'%(self.name,self.amount))self.amount +=amount t1 = Teacher('wusir',28,'M',3000,'python') s1 = Student('lwq',12,'M','python',11000) s2 = Student('hsc',11,'M','python',11000) t1.tell() s1.tell() del t1
小結
面向對象:(更容易擴展,提高代碼使用效率,使代碼更強,更清晰,更適合復雜項目的開發) 類 對象
封裝: 1.防止數據被隨意修改 2.使外部程序不需要關注對象內部的構造,只需要通過此對象對外提供的接口進行直接訪問即可。
繼承: 通過父類---〉子類的方式以最小代碼量的方式實現不同角色的共同點和不同點同時存在。 多態: 接口的繼承。
類的基本定義: 類==》模版 類----〉實例化----〉實例 __init__ 構造函數 self.name = name 屬性(成員變量)或者字段 def sayhi() 方法或者動態屬性
封裝: 成員屬性:==》實例變量
公有屬性:在類里直接定義的屬性,既是公有屬性,存在類的內存里,所有實例共享。
私有屬性:__private_attr_name = value #兩個下劃線加名字 對外不提供只讀訪問接口 def get_heart(self): return self._heart 強制訪問: r1._role__heart 類前加一個下劃線,屬性前加兩個下劃線。
del 析構函數(銷毀實例時運行) 關閉連接。
繼承
靜態方法;沒有__init__(可以有任意個參數),直接通過類調用方法,保存在類中,調用者是類, class F1: @staticmethod def a1(): print('alex') F1.a1()