python基礎(二十三):面向對象編程實戰演練(選課系統)、封裝

一、面向對象編程實戰演練

1、學校類(關聯班級)

# 一、學校---# 校區創建完畢後,可以爲每個校區創建班級
# 定義學校類
class School:
    school_name = 'OLDBOY'

    def __init__(self, nickname, addr):
        self.nickname = nickname
        self.addr = addr
        self.classes = []
    #  爲【學校】關聯【班級】
    def related_class(self, class_obj):
        # self.classes.append(班級名字)
        # self.classes.append(class_name)
        self.classes.append(class_obj)

    #  函數功能:打印每個校區的每個班級的課程信息
    def tell_class(self):  # 改
        # 打印的校區的名字
        print(self.nickname.center(60,'='))   #  格式化打印效果:==========================校區名稱===========================
        # 打印班級開設的課程信息
        for class_obj in self.classes:
            class_obj.tell_course()

# 實例化學校類
school_obj1=School('老男孩魔都校區','上海')
school_obj2=School('老男孩帝都校區','北京')

2、班級類(關聯課程)

# 二、班級---#   班級創建完畢後,可以爲每個班級創建課程
# 定義班級類
class Class:
    def __init__(self, name):
        self.name = name
        self.course = None

    #  爲【班級】關聯【課程】
    def related_course(self, course_obj):
        # self.course = course_name
        self.course = course_obj

    def tell_course(self):
        print('%s' % self.name,end=" ") # 打印班級信息
        self.course.tell_info() # 打印課程的詳細信息

    # def related_student(self,student_obj):
    #     self.student = student_obj

    def tell_class(self):
        print('%s'%self.name)
        self.tell_course()

# 實例化班級
# 1、創建班級
class_obj1 = Class('脫產14期')
class_obj2 = Class('脫產15期')
class_obj3 = Class('脫產29期')

# 4、爲學校開設班級(爲【學校】關聯【班級】)
# 上海校區開了:脫產14期,上海校區開了脫產15期
school_obj1.related_class(class_obj1)
school_obj1.related_class(class_obj2)

# 北京校區開了:脫產29期
school_obj2.related_class(class_obj3)

3、課程類

# 三、課程類
# 定義課程類
class Course:
    def __init__(self,name,period,price):
        self.name=name
        self.period=period
        self.price=price

    def tell_info(self):
        print('<課程名:%s 週期:%s 價錢:%s>' %(self.name,self.period,self.price))

# 實例化課程類
# 1、創建課程
course_obj1=Course('python全棧開發','6mons',20000)
course_obj2=Course('linux運維','5mons',18000)


# 3、爲【班級】關聯【課程對象】
class_obj1.related_course(course_obj1)
class_obj2.related_course(course_obj2)
class_obj3.related_course(course_obj1)


# 【查詢一】
# 查看每個校區的每個班級的課程信息(先是爲【學校】關聯【班級】,再爲【班級】關聯【課程】)
# tell_class(tell_course())--- tell_course(tell_info())---tell_info(課程信息)
# school_obj1.tell_class()
# school_obj2.tell_class()

# 輸出結果:
# ==========================老男孩魔都校區===========================
# 脫產14期 <課程名:python全棧開發 週期:6mons 價錢:20000>
# 脫產15期 <課程名:linux運維 週期:5mons 價錢:18000>
# ==========================老男孩帝都校區===========================
# 脫產29期 <課程名:python全棧開發 週期:6mons 價錢:20000>

4、學生類(關聯學校、班級)

# 四、學生類--#   學生創建完畢後,學生可以選擇班級
# 定義學生類
class Student:
    def __init__(self,s_school,s_name,s_age,s_number,s_gender):
        self.stu_school = s_school
        self.stu_name = s_name
        self.stu_age = s_age
        self.stu_number = s_number
        self.stu_gender = s_gender
    # def choose_class(self,class_obj):   # 學生創建完畢後,學生可以選擇班級
    #     self.classes= class_obj

    # 爲【學生】關聯【學校】
    def related_school(self,school_obj):
        self.stu_school= school_obj

    # 爲【學生】關聯【班級】
    def related_class(self,class_obj):
        self.stu_class= class_obj

    def tell_stu_message(self):
        print(('學生 %s 2020年檔案表' %(self.stu_name)).center(60, '='))
        print('<學校:%s 姓名:%s 年齡:%s 學號:%s 性別:%s>' %(self.stu_school,self.stu_name,self.stu_age,self.stu_number,self.stu_gender))
        # self.stu_class.tell_class()
        self.stu_class.tell_course()

 # 實例化學生類
# 1、創建學生
stu_obj1 = Student('老男孩魔都校區','CC',18,2020001,'female')
stu_obj2 = Student('老男孩帝都校區','MILI',19,2020007,'male')
stu_obj3 = Student('老男孩魔都校區','MELA',20,2020009,'male')


# 2、爲【學生】關聯【班級】
stu_obj1.related_class(class_obj1)
stu_obj2.related_class(class_obj2)
stu_obj3.related_class(class_obj3)

# 【查詢二】查詢每個學生的學校,班級,課程信息
stu_obj1.tell_stu_message()
stu_obj2.tell_stu_message()
stu_obj3.tell_stu_message()

# 輸出結果:
# =======================學生 CC 2020年檔案表=======================
# <學校:老男孩魔都校區 姓名:CC 年齡:18 學號:2020001 性別:female>
# 脫產14期 <課程名:python全棧開發 週期:6mons 價錢:20000>
# ======================學生 MILI 2020年檔案表======================
# <學校:老男孩帝都校區 姓名:MILI 年齡:19 學號:2020007 性別:male>
# 脫產15期 <課程名:linux運維 週期:5mons 價錢:18000>
# ======================學生 MELA 2020年檔案表======================
# <學校:老男孩魔都校區 姓名:MELA 年齡:20 學號:2020009 性別:male>
# 脫產29期 <課程名:python全棧開發 週期:6mons 價錢:20000>

二、封裝

1、引入

面向對象編程有三大特性:封裝、繼承、多態,其中最重要的一個特性就是封裝。封裝指的就是把數據與功能都整合到一起,聽起來是不是很熟悉,沒錯,我們之前所說的”整合“二字其實就是封裝的通俗說法。除此之外,針對封裝到對象或者類中的屬性,我們還可以嚴格控制對它們的訪問,分兩步實現:隱藏與開放接口

2、隱藏屬性

Python的Class機制採用雙下劃線開頭的方式將屬性隱藏起來(設置成私有的),但其實這僅僅只是一種變形操作,類中所有雙下滑線開頭的屬性都會在類定義階段、檢測語法時自動變成“_類名__屬性名”的形式:

class Foo:
    __N=0 # 變形爲_Foo__N

    def __init__(self): # 定義函數時,會檢測函數語法,所以__開頭的屬性也會變形
        self.__x=10 # 變形爲self._Foo__x

    def __f1(self): # 變形爲_Foo__f1
        print('__f1 run')

    def f2(self):  # 定義函數時,會檢測函數語法,所以__開頭的屬性也會變形
        self.__f1() #變形爲self._Foo__f1()
        
# -----------此線以上都是Foo類的定義__開頭都會變成_類名__屬性名----------------

print(Foo.__N) # 報錯AttributeError:類Foo沒有屬性__N
print(Foo._Foo__N) # 正確

obj = Foo()
obj.__x = 2
print(obj._Foo__x) # 對象obj沒有屬性_Foo__x,注意只在類定義階段才變形成_Foo__x,上面obj.__x = 2,這已經是執行階段了,因此不會變形,你只是新增了一個__x數據屬性而已
print(obj.__x) # 正確,返回結果:2

# 注意:
1. 函數屬性和數據屬性的隱藏方法一樣,隱藏後的外部調用方法也一樣
2. 隱藏對外不對內,對外部需要'_類名__屬性名'這樣訪問,內部'__屬性名'這樣即可
3.2點這是爲什麼呢?,因爲對內的都是在類定義階段,裏面的'__屬性名'都會轉變成'_類名__屬性名',因此訪問無礙,外面都是執行階段,不會變形因此無法訪問到。

3、爲什麼要隱藏屬性呢?

定義屬性就是爲了使用,所以隱藏並不是目的

(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('lili',18)
>>> t.set_info(‘LiLi','19') # 年齡不爲整型,拋出異常
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 11, in set_info
TypeError: 年齡必須是整型
>>> t.set_info('LiLi',19) # 名字爲字符串類型,年齡爲整形,可以正常設置
>>> t.tell_info() # 查看老師的信息
姓名:LiLi,年齡:19
(2)爲什麼隱藏函數屬性?

目的的是爲了隔離複雜度,例如ATM程序的取款功能,該功能有很多其他功能組成,比如插卡、身份認證、輸入金額、打印小票、取錢等,而對使用者來說,只需要開發取款這個功能接口即可,其餘功能我們都可以隱藏起來

>>> 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()
...
>>> obj=ATM()
>>> obj.withdraw()
# 注意隱藏了的函數屬性,obj,就不會再提示出來,只會提示withdraw這個函數,因此操作簡單明瞭,如果提示一大堆,看着頭都暈

總結隱藏屬性與開放修改、訪問數據屬性的接口,本質就是爲了明確地區分內外,隱藏數據屬性,是爲了保護數據,隱藏函數是爲了屏蔽複雜的細節。類內部可以修改封裝內的東西而不影響外部調用者的代碼;而類外部只需拿到一個接口,只要接口名、參數不變,則無論設計者如何改變內部實現代碼,使用者均無需改變代碼。這就提供一個良好的合作基礎,只要接口這個基礎約定不變,則代碼的修改不足爲慮。

4、property

身高或體重是不斷變化的,因而每次想查看BMI值都需要通過計算才能得到,但很明顯BMI聽起來更像是一個特徵而非功能,爲此Python專門提供了一個裝飾器property,可以將類中的函數“僞裝成”對象的數據屬性,對象在訪問該特殊屬性時會觸發功能的執行,然後將返回值作爲本次訪問的結果,例如

>>> 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)
...
>>> obj=People('lili',75,1.85)
>>> obj.bmi #觸發方法bmi的執行,將obj自動傳給self,執行後返回值作爲本次引用的結果
21.913805697589478

使用property有效地保證了屬性訪問的一致性。另外property還提供設置和刪除屬性的功能,如下

>>> class Foo:
...     def __init__(self,val):
...         self.__NAME=val #將屬性隱藏起來
...     @property
...     def name(self):
...         return 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 PermissionError('Can not delete')
...
>>> f=Foo('lili')
>>> f.name
lili
>>> f.name='LiLi' #觸發name.setter裝飾器對應的函數name(f,’Egon')
>>> f.name=123 #觸發name.setter對應的的函數name(f,123),拋出異常TypeError
>>> del f.name #觸發name.deleter對應的函數name(f),拋出異常PermissionError

注意:我們都知道隱藏數據屬性,是爲了提供接口,更規範的訪問,不讓用戶肆無忌憚的對屬性操作,但是提供函數供用戶使用,使其對屬性進行修改、查看、刪除,這樣不符合用戶的使用習慣,因此,我們需要用上面的方法,使其符合用戶的使用習慣,且限制規範了用戶對屬性的操作

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章