1. 程式人生 > >第26天面向物件程式設計之組合,多型,封裝

第26天面向物件程式設計之組合,多型,封裝

組合

人生三問:

什麼是組合  組合就是將一個物件A定義為另一個物件B的屬性。從而使得B不僅能夠訪問自己的屬性,還會具備A的屬性,就像是把物件A和物件B組合到一塊一樣。
為什麼要用組合  和繼承一樣為了減少類與類之間的程式碼冗餘。  問題來了既然已經有了繼承,為什麼還要有組合呢?主要是為了解決一些沒有父子關係的類之間的一些屬性使用。
怎麼使用組合建立一個類:  class A:    pass  class B:    pass  建立物件,運用組合的特性  a_obj = A()  b_obj = B()  a_obj.b = b_obj  就是把b物件放在了a物件的名稱空間中

組合的使用

# 總結:組合就是將一個物件當作屬性賦值給了另一個物件,使得另一個物件不僅可以使用自己的屬性
#       還可以間接的使用之前那個物件的屬性,從而減少了程式碼的冗餘。
class Foo:
    xxx = 111
    def func1(self):
        print('this is func1')

class Bar:
    yyy = 222
    def func2(self):
        print('this is func2')

# 根據上面的兩個類建立了兩個物件
f_obj = Foo()
b_obj = Bar()
# 使用物件f_obj訪問類裡面的屬性是很容易的
f_obj.func1() print(f_obj.xxx) # 但是現在有一個需求,我發現func2的功能用著很舒服,以及有時候 # 我還會需要使用到Bar裡面的變數,怎麼辦呢?使用組合 f_obj.b = b_obj f_obj.b.func2() print(f_obj.b.yyy)

案例分析:為什麼要用組合,需求:還是建立一個老男孩選課系統

# 老男孩選課系統
class Parent:
    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender 
= gender class Student(Parent): def choose_course(self): print('choose course') class Teacher(Parent): def __init__(self, name, age, gender, level, salary): Parent.__init__(self, name, age, gender) self.level = level self.salary = salary s = Student('egon', 11, 'male') t = Teacher('egon', 11, 'male', 10, 1000) print(t.gender) print(s.gender)
根據需求建立的教師,學生和父類

問題一: 既然是選課,肯定是要有課程的,因此我們需要為每個學生的特徵上面重新新增上一些課程資訊,包括的有課程名,課程時長,課程價格。因此出現了下面的修改的程式碼。

# 老男孩選課系統
class Parent:
    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender

class Student(Parent):
    # 在學生類中新增課程資訊
    def __init__(self, name, age, gender, course_name, course_price, course_period):
        Parent.__init__(self, name, age, gender)
        self.course_name = course_name
        self.course_price = course_price
        self.course_period = course_period
    def choose_course(self):
        print('choose course')

class Teacher(Parent):
    # 在教師類中添加了課程資訊
    def __init__(self, name, age, gender, level, salary, course_name, course_price, course_period):
        Parent.__init__(self, name, age, gender)
        self.level = level
        self.salary = salary
        self.course_name = course_name
        self.course_price = course_price
        self.course_period = course_period

# 建立了三個學生
s1 = Student('egon', 20, 'male', 'python', 1000, '5months')
s2 = Student('alex', 19, 'male', 'python', 1000, '5months')
s3 = Student('hu', 18, 'male', 'python', 1000, '5months')

問題二:課程已經新增到學生和教師的特徵上面了,但是現在觀察建立學生類的時候出現了兩個問題:1. 在學生的__init__函式和教師的__init__函式中出現了程式碼的冗餘 2. 在每次建立物件的時候都要重新輸入課程資訊,可能大量學生或者教師的課程資訊都是一樣的。 3. 每建立一個物件都要儲存一份課程資訊,造成了記憶體的大量浪費。

# 老男孩選課系統
class Parent:    # 將課程資訊都放在父類中,減少了程式碼的冗餘,而且記憶體中也只會在類中儲存一份,減少了記憶體的浪費
    def __init__(self, name, age, gender, course_name, course_price, course_period):
        self.name = name
        self.age = age
        self.gender = gender
        self.course_name = course_name
        self.course_price = course_price
        self.course_period = course_period

class Student(Parent):
    def choose_course(self):
        print('choose course')

class Teacher(Parent):
    def __init__(self, name, age, gender, level, salary, course_name, course_price, course_period):
        Parent.__init__(self, name, age, gender, course_name, course_price, course_period)
        self.level = level
        self.salary = salary

# 建立了三個學生
s1 = Student('egon', 20, 'male', 'python', 1000, '5months')
s2 = Student('alex', 19, 'male', 'python', 1000, '5months')
s3 = Student('hu', 18, 'male', 'python', 1000, '5months')

問題三:按照之前的繼承問題基本上已經把大部分的資訊都給解決了,但是依然存在以下幾個問題:1. 每建立一個物件都要求我們輸入相應的課程資訊,很麻煩,不過這還是小事,重點在第二個問題。2. 我們現在的課程選課系統中只有學生和教師這兩個類,但是如果我們加入一個管理員類呢,管理員類可是不需要課程資訊的,因此,我們還是不能把課程資訊放在父類中的,因為課程資訊對於老男孩裡面的每一個物件而言不是必須的,有的人是沒有必要選課程資訊的。 這樣就和我們之前的有點矛盾了,我們是為了減少程式碼的冗餘以及減少記憶體空間才去使用這個繼承的,但是目前因為需求的問題我們又不能用繼承。 此時我們應該能夠想到用組合了吧。首先建立一個課程類,然後組合到學生類和教師類中。

# 老男孩選課系統
class Parent:
    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender
class Student(Parent):
    def choose_course(self):
        print('choose course')
class Teacher(Parent):
    # 在教師類中添加了課程資訊
    def __init__(self, name, age, gender, level, salary):
        Parent.__init__(self, name, age, gender)
        self.level = level
        self.salary = salary
        
class Course:
    def __init__(self, course_name, course_price, course_period):
        self.course_name = course_name
        self.course_price = course_price
        self.course_period = course_period
        
    def tell_info(self):
        print('課程:<%s> 週期:{%s} 價格: [%s]' % (self.course_name, self.course_period, self.course_price))
# 建立課程物件
python = Course('python', 1000, '5months')
# 建立學生物件,然後組合課程物件
s1 = Student('egon', 20, 'male')
s1.course = python
s2 = Student('alex', 18, 'male')
s2.course = python

總結: 

能夠減少類與類之間的程式碼冗餘的方法區別  繼承主要是抽取子類中相同的特徵和技能來減少程式碼冗餘
  組合主要是抽取子類中部分相同的特徵來重新建立一個類來減少程式碼冗餘

封裝

人生三問:

什麼是封裝  裝就是將一堆的屬性裝到一個容器中,也就是說的類。  封就是隱藏內部的屬性,這種隱藏是對外不對內的,只提供一個介面給我們使用。
為什麼要用封裝
  資料屬性的封裝  將資料屬性封裝起來,類外部的使用就無法直接操作該資料,需要類內部開一個介面給使用者,類的設計者可以在介面之上附加任意邏輯,從而控制使用者對資料屬性的操作。  功能屬性的封裝:隔離複雜度怎麼使用封裝  只需要在屬性前面加上__開頭,該屬性就會被隱藏起來,該隱藏具備的特點  1. 只是一種語法意義上的變形,即__開頭的屬性會在測試語法時發生變形  2. 這種隱藏式對外不對內的,因為類內部檢測語法的時候是檢測所有的程式碼  3. 這種變形只是在類的定義階段變形一次,之後新增的__開頭的屬性不會發生改變  4. 如果父類不想讓子類覆蓋自己的屬性,可以在屬性前面加上__開頭

封裝的特點:

1. 只是語法上的一種變形而已

class Foo:
    xx = 11
    def func1(self):
        print('Foo Func1')

    def func2(self):
        print('Foo Func2')

# 列印當前Foo中的名稱空間
print(Foo.__dict__)
print(Foo.xx)
print(Foo.func1)
print(Foo.func2)
# 結果:
# {'__module__': '__main__', 'xx': 11, 'func1': <function Foo.func1 at 0x007E4F60>, 'func2': <function Foo.func2 at 0x007E4930>, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None}
# 11
# <function Foo.func1 at 0x007E4F60>
# <function Foo.func2 at 0x007E4930>
沒有__開頭的屬性
class Foo:
    __xx = 11
    def __func1(self):
        print('Foo Func1')

    def func2(self):
        print('Foo Func2')

# 列印當前Foo中的名稱空間
print(Foo.__dict__)
# 我們會發現加上__開頭之後的屬性我們訪問不到了,為什麼會出現這種情況呢?
# 並不是說python直接把內部的變數給刪除了,只是說python在定義類的時候發現有__開頭的變數會自動幫我們改一個名字存在了__dict__中
# 如果我們堅持要去訪問還是可以通過_Foo變數名去訪問的,如下面
print(Foo._Foo__xx)
print(Foo._Foo__func1)
print(Foo.func2)
# 結果:
# {'__module__': '__main__', '_Foo__xx': 11, '_Foo__func1': <function Foo.__func1 at 0x02D14F60>, 'func2': <function Foo.func2 at 0x02D14930>, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None}
# 11
# <function Foo.__func1 at 0x02D14F60>
# <function Foo.func2 at 0x02D14930>

2. 隱藏是對外不對內

class Foo:
    __xx = 11
    def __func1(self):
        print('Foo Func1')

    def func2(self):
        # 隱藏是對外不對內,因為變形是在類定義階段就已經完成的了
        # 也就是說在定義階段也是變成了self._Foo__xx
        print(self.__xx)   # 此時的相當於是print(self._Foo__xx)
        print('Foo Func2')
# 建立一個物件並且去呼叫方法func2函式
obj = Foo()
obj.func2()
# 結果:
# 11
# Foo Func2

3. 之後新增的__開頭的變數並不會對其進行隱藏

class Foo:
    __xx = 11
    def __func1(self):
        print('Foo Func1')

    def func2(self):
        print('Foo Func2')

obj = Foo()
# 變形只是在類的定義階段進行的,之後進行新增的__變數是不會進行變形的
# 因此直接通過變數名是可以直接進行訪問的
obj.__yy = 22
print(obj.__yy)
# 結果
# 22

4. 父類可以通過__開頭的屬性覆蓋子類的屬性

# 預設子類的屬性會覆蓋掉父類的屬性
class Bar:
    def func1(self):
        print('Bar Func1')

    def func2(self):
        print('Bar Func2')
        self.func1()

class Foo(Bar):
    def func1(self):
        print('Foo Func1')

# 根據屬性查詢順序  物件-->類-->父類
# 其實就是子類的屬性會覆蓋掉父類的屬性
obj = Foo()
obj.func2()
# 結果
# Bar Func2   因為物件和類中都沒有func2,所以去父類中查詢,找到之後執行func2.列印效果
# Foo Func1   但是之後還會執行self.func1()但是此時還是先從物件中找,物件中沒有,所以到類Foo中找,有,所以列印結果
預設子類的屬性會覆蓋掉父類的屬性

如果不想讓他覆蓋父類的屬性,我們需要通過__開頭的屬性

# 預設子類的屬性會覆蓋掉父類的屬性
class Bar:
    def __func1(self):   # _Bar_func1()
        print('Bar Func1')

    def func2(self):
        print('Bar Func2')
        self.__func1() # 相當於self._Bar__func1()

class Foo(Bar):
    def func1(self):
        print('Foo Func1')

# 根據屬性查詢順序  物件-->類-->父類
obj = Foo()
obj.func2()
# 結果
# Bar Func2   因為物件和類中都沒有func2,所以去父類中查詢,找到之後執行func2.列印效果
# Bar Func1   此時相當於執行相當於self._Bar__func1(),物件中沒有_Bar__func1方法,類中也沒有,所以只有父類中有

資料封裝的作用

# 封裝的作用
# 對於資料的封裝主要就是為了能夠控制使用者對屬性的操作
class Student:
    def __init__(self, name, age, gender):
        self.__name = name
        self.age = age
        self.gender = gender

    def show_name(self):
        # 之前檢視的時候在外部直接通過name就可以檢視
        # 現在封裝之後,我就可以對其進行格式上的展示
        if '__name' not in self.__dict__:
            print('當前沒有名字!')
            return
        print('名字:<%s>' % self.__name)

    def modify_name(self, new_name):
        # 封裝之後,就不允許你隨便的更改屬性
        # 當你更改的屬性不是str型別的時候,是不行的
        # 從而限制了當前使用者對屬性的操作
        if type(new_name) is not str:
            print('hi名字只能是str型別的')
            return
        self.__name = new_name
        print('名字修改成功')

    def clear_name(self):
        del self.__name
        print('名稱已經刪除')

stu1 = Student('egon', 11, 'male')
stu1.show_name()
stu1.modify_name('EGON')
stu1.show_name()
stu1.clear_name()
stu1.show_name()

函式封裝的作用

函式封裝的作用
  主要是為了降低複雜度,例如:我現在要開機電腦,如果我告訴你要第一步插電,第二步操作bios。。。。等等的時候,肯定就要瘋掉了,因此我告訴你只需要按一下開機鍵就可以開機了,這個就是封裝的過程,就是告訴使用者對於一系列複雜的操作我已經幫你做好了一個介面,你以後要用這個功能的時候你只需要記住這個介面就ok了。

多型

什麼是多型  多型就是多種形態的意思。
為什麼要用多型  為了統一標準
怎麼使用多型

多型的使用:

# 一個動物類,動物都會叫, 因此在animal類中定義了spark方法
# 但是我們都知道不同的動物都有不同的叫聲對吧,這個就是多型的一種表現
class Animal:
    def spark(self):
        pass

class People(Animal):
    def spark(self):
        print('People spark')

class Pig(Animal):
    def spark(self):
        print('哼哼哼')

class Dog(Animal):
    def spark(self):
        print('汪汪汪')

p = People()
pig = Pig()
dog = Dog()
# 當我們例項化物件之後,我們就不必在去關注他是哪一種型別的物件,
# 因為在每一個子類中都會有spark這個方法去使用
p.spark()
pig.spark()
dog.spark()

強制多型的方法

# 強制多型的意思就是在父類中一旦定義了強制多型方法,在子類中就必須有這樣的一個方法
# 沒有將會報錯
import abc
class Animal(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def spark(self):
        pass

class People(Animal):
    # 如果將下面的spark方法給註釋掉,將會報錯
    # def spark(self):
        # print('People spark')
    pass
class Pig(Animal):
    def spark(self):
        print('哼哼哼')

class Dog(Animal):
    def spark(self):
        print('汪汪汪')

結論:python本身是崇尚一種高度自由思想,因此這種強制多型的方法並不是特別的完美。在日常工作過程中,我們更應該一一種約定俗稱的標準來規劃我們程式碼,而不應該以這種強制的形式去規定我們程式碼。

在python運用到多型思想:

例如:__len__方法,我們沒有必要去考慮具體的型別是什麼,都可以使用此方法。