月嫂云商城網(wǎng)站建設百度廣告聯(lián)盟下載
OOP
【面向?qū)ο蟪绦蛟O計】(OOP)與【面向過程程序設計】在思維方式上存在著很大的差別?!久嫦蜻^程程序設計】中,算法是第一位的,數(shù)據(jù)結構是第二位的,這就明確地表述了程序員的工作方式。首先要確定如何操作數(shù)據(jù),然后再決定如何組織數(shù)據(jù),以便于數(shù)據(jù)操作。而【面向?qū)ο蟪绦蛟O計】卻調(diào)換了這個次序,【面向?qū)ο蟪绦蛟O計】將數(shù)據(jù)放在第一位,然后再考慮操作數(shù)據(jù)的算法。
對于一些規(guī)模較小的問題,將問題分解為過程的開發(fā)方式比較理想。而面向?qū)ο蟾舆m用于解決規(guī)模較大的問題。
面向?qū)ο蟪绦蛟O計是一種編程范式或編程風格。面向?qū)ο蟮某绦蚴怯深惡蛯ο蠼M成的(以類和對象作為組織代碼的基本單元),并將封裝、抽象、繼承、多態(tài)這四個特性,作為程序設計和實現(xiàn)的基礎。
面向?qū)ο蟪绦蛟O計語言是【支持類和對象的語法機制。并有現(xiàn)成的語法機制,能方便地實現(xiàn) OOP 的四大特性(封裝、抽象、繼承、多態(tài))】的編程語言。
OOP 的四大特性
對于 OOP 的四大特性,我們需要知道每一個特性的如下知識:
- xxx 特性的含義
- 為了實現(xiàn) xxx 特性,需要程序設計語言提供一定的語法機制來支持。對于這四大特性,盡管大部分面向?qū)ο蟪绦蛟O計語言都提供了相應的語法機制來支持,但不同的編程語言實現(xiàn)這四大特性的語法機制可能會有所不同。
- xxx 特性存在的意義、好處
封裝
封裝(encapsulation)也被稱為數(shù)據(jù)隱藏、數(shù)據(jù)訪問保護。從形式上看,封裝就是將數(shù)據(jù)和行為組合在一起中,并對對象的使用者隱藏數(shù)據(jù)的實現(xiàn)方式。
對象中的數(shù)據(jù)被稱為實例域(instance field),操作數(shù)據(jù)的過程被稱為方法(method)。對于每個特定的類實例(對象)都有一組特定的實例域值。這些值的集合就是這個對象的當前狀態(tài)(state)。
實現(xiàn)封裝的關鍵在于絕對不能讓類中的方法直接地訪問其他類的實例域。程序僅通過對象的方法與對象數(shù)據(jù)進行交互。封裝給對象賦予了 “黑盒” 特征,這是提高重用性和可靠性的關鍵。這意味著一個類可以全面地改變存儲數(shù)據(jù)的方式,只要仍舊使用同樣的方法操作數(shù)據(jù),其他對象就不會知道或介意所發(fā)生的變化。
為了實現(xiàn)封裝這個特性,需要程序設計語言提供一定的語法機制來支持。這個語法機制就是訪問權限控制(訪問修飾符:public、protected、private、default)。
在 Java 中,封裝就意味著所有的實例域都帶有 private 訪問修飾符(私有的實例域),并提供帶有 public 訪問修飾符的域訪問器方法和域更改器方法(公共的操作方法)。
如果實例域帶有 public 訪問修飾符,這就破壞了封裝性。因為 public 實例域允許程序中的任何方法對其進行讀取和修改。
如果域訪問器方法、域更改器方法直接返回了一個可變對象的引用,這就破壞了封裝性。在 Employee 類中就違反了這個設計原則,其中的 getHireDay() 方法返回了一個 Date 類對象。Date 類有一個更改器方法 setTime(),可以使用 setTime() 這個方法設置毫秒數(shù)。Date 對象是可變的,這一點就破壞了封裝性。對 d 調(diào)用更改器方法就可以自動地改變這個雇員對象的私有狀態(tài)。
如果域訪問器方法、域更改器方法需要返回一個可變對象的引用,應該首先對對象進行克隆(clone)。
對象 clone 指的是:存放在另一個位置上的對象副本。
class Employee {private Date hireDay;public Date getHireDay() {return hireDay; // Bad}// ...
}Employee harry = . .
Date d = harry.getHireDay();
double tenYearsInMilliSeconds = 10 * 365.25 * 24 * 60 * 60 * 1000;
d.setTime(d.getTime() - (long) tenYearsInMilliSeconds);
// let's give Harry ten years of added seniority// 修改后的代碼
class Employee {public Date getHireDay() {return (Date) hireDay.clone(); // Ok}// ...
}
封裝存在的意義、封裝的好處:程序僅通過對象的方法與對象數(shù)據(jù)進行交互
- 保護對象數(shù)據(jù)不被隨意修改。
- 可以改變類的內(nèi)部實現(xiàn),除了該類的方法之外,不會影響其他的代碼。
- 更改器方法可以執(zhí)行錯誤檢查,而直接對實例域進行賦值將不會進行這些處理。例如,setSalary 方法可以檢查薪水是否小于 0。
抽象
封裝主要講的是如何隱藏數(shù)據(jù)、數(shù)據(jù)訪問保護,而抽象講的是如何隱藏方法的具體實現(xiàn),讓方法的調(diào)用者只需要關心方法提供了哪些功能,并不需要知道這些功能是如何實現(xiàn)的。
我們可以借助程序設計語言提供的接口類(比如 Java 中的 interface 關鍵字語法)或者抽象類(比如 Java 中的 abstract 關鍵字語法)這兩種語法機制,來實現(xiàn)抽象這一特性。
實際上,抽象這個特性是非常容易實現(xiàn)的,并不需要非得依靠接口類或者抽象類這些語法機制來支持。換句話說,并不是說一定要為實現(xiàn)類抽象出接口類,才叫作抽象。即便不編寫接口類,單純的實現(xiàn)類本身就滿足抽象特性。
之所以這么說,那是因為類的方法是通過程序設計語言中的 “函數(shù)” 這一語法機制實現(xiàn)的。通過函數(shù)包裹具體的實現(xiàn)邏輯,這本身就是一種抽象。調(diào)用者在調(diào)用函數(shù)的時候,并不需要去研究函數(shù)內(nèi)部的實現(xiàn)邏輯,只需要通過函數(shù)的命名、注釋或者文檔,了解該函數(shù)提供了什么功能,就可以直接調(diào)用了。比如,我們在使用 C 語言的 malloc() 函數(shù)的時候,并不需要了解它的底層代碼是怎么實現(xiàn)的。
抽象存在的意義、抽象的好處:
- 一方面,抽象提高了代碼的可擴展性、可維護性,修改實現(xiàn)不需要改變定義,減少了代碼的改動范圍;
- 另一方面,抽象是處理復雜系統(tǒng)的有效手段,抽象能有效地過濾掉不必要關注的信息。
繼承
繼承(inheritance)即 “is-a” 關系,是一種用于表示特殊與一般關系的。
例如,RushOrder 類由 Order 類繼承而來。在具有特殊性的 RushOrder 類中包含了一些用于優(yōu)先處理的特殊方法,以及一個計算運費的不同方法;而其他的方法,如添加商品、生成賬單等都是從 Order 類繼承來的。
利用繼承,人們可以基于已存在的類構造一個新類。繼承已存在的類就是復用(繼承)這些類的方法和域。在此基礎上,還可以添加一些新的方法和域,以滿足新的需求。
從繼承關系上來講,繼承可以分為單繼承和多繼承。有些程序設計語言只支持單繼承,不支持多重繼承,比如 Java、PHP、C#、Ruby 等,而有些程序設計語言既支持單繼承,也支持多繼承,比如 C++、Python、Perl 等。
- 單繼承表示一個子類只能繼承一個父類;
- 多繼承表示一個子類可以繼承多個父類。
為了實現(xiàn)繼承這個特性,需要程序設計語言提供一定的語法機制來支持。比如 Java 使用 extends 關鍵字來實現(xiàn)繼承,C++ 使用冒號來實現(xiàn)繼承(class B : public A),Python 使用 parentheses() 來實現(xiàn)繼承,Ruby 使用 < 來實現(xiàn)繼承。
繼承存在的意義、繼承的好處:繼承的一個最大好處就是代碼復用。假如兩個類有一些相同的屬性和方法,我們就可以將這些相同的部分,抽取到基類中,讓兩個子類繼承基類。這樣,兩個子類就可以重用基類中的代碼,避免代碼重復寫多遍。
不過,代碼復用這個好處也并不是繼承所獨有的,我們也可以通過其他的方式來解決代碼復用的問題,比如利用組合關系。
過度的使用繼承,繼承的層次過深、過復雜,就會導致代碼的可讀性、可維護性變差。
- 可讀性變差的原因:為了了解一個類的功能,我們不僅需要查看這個類的代碼,還需要按照繼承關系一層一層地往上查看“父類、父類的父類……”的代碼。
- 可維護性變差的原因:子類和父類高度耦合,修改父類的代碼,會直接影響到子類。
多態(tài)
一個對象變量可以指向多種實際類型的現(xiàn)象被稱為多態(tài)(polymorphism)。在運行時自動地選擇調(diào)用哪個方法的現(xiàn)象被稱為動態(tài)綁定(dynamic binding)。
為了實現(xiàn)多態(tài)這個特性,需要程序設計語言提供一定的語法機制來支持。
- 第一個語法機制是:程序設計語言要支持繼承;
- 第二個語法機制是:程序設計語言要支持父類的對象變量可以引用子類對象;
- 第三個語法機制是:程序設計語言要支持方法的重寫(override)。
在 Java 程序設計語言中,對象變量是多態(tài)的。一個父類的對象變量既可以引用一個父類的對象,也可以引用一個子類的對象。
一個 Employee 變量既可以引用一個 Employee 類的對象,也可以引用一個 Employee 類的任何一個子類的對象(例如, Manager、Executive、 Secretary 等)。
對于多態(tài)特性的實現(xiàn)方式,除了利用 “繼承加方法重寫” 這種實現(xiàn)方式之外,還有其他兩種比較常見的的實現(xiàn)方式,一種是利用接口類語法,另一種是利用 duck-typing 語法。不過,并不是每種程序設計語言都支持接口類或者 duck-typing 這兩種語法機制,比如 C++ 就不支持接口類語法,而 duck-typing 只有一些動態(tài)語言才支持,比如 Python、JavaScript 等。
- 接口類語法:一個對象變量(接口類)可以指向多種實際類型(實現(xiàn)類)
- duck-typing 語法:duck-typing 可以這樣表述:“如果看起來像鴨子,叫起來像鴨子,那么它一定是鴨子”。
多態(tài)存在的意義、多態(tài)的好處:
- 多態(tài)的好處是,我們可以在一個比較穩(wěn)定的、抽象的層面上編程,而不被更加具體的、易變的實現(xiàn)細節(jié)干擾。
- 多態(tài)也是很多設計模式、設計原則、編程技巧的代碼實現(xiàn)基礎,比如策略模式、基于接口而非實現(xiàn)編程、依賴倒置原則、里式替換原則、利用多態(tài)去掉冗長的 if-else 語句等。
參考資料
《Java核心技術卷一:基礎知識》(第10版)
04 | 理論一:當談論面向?qū)ο蟮臅r候,我們到底在談論什么?-極客時間 (geekbang.org)
05 | 理論二:封裝、抽象、繼承、多態(tài)分別可以解決哪些編程問題? (geekbang.org)