【軟件構造】第三章第四節 面向對象編程OOP

 第三章第四節 面向對象編程OOP

本節講學習ADT的具體實現技術:OOPjava

Outline

  • OOP的基本概念
    • 對象
    • 接口
    • 抽象類
  • OOP的不一樣特徵
    • 封裝
    • 繼承與重寫(override)
    • 多態與重載(overload)
    • 重寫與重載的區別
    • 泛型
  • 設計好的類

Notes

## OOP的基本概念

【對象】編程

  • 對象是類的一個實例,有狀態和行爲。
  • 例如,一條狗是一個對象,它的狀態有:顏色、名字、品種;行爲有:搖尾巴、叫、吃等。
  • 概念:一個對象是一堆狀態和行爲的集合。
    • 狀態是包含在對象中的數據,在Java中,它們是對象的fields。
    • 行爲是對象支持的操做,在Java中,它們稱爲methods。

【類】數組

  • 類是一個模板,它描述一類對象的行爲和狀態。
  • 每一個對象都有一個類
  • 類定義了屬性類型(type)和行爲實現(implementation)
  • 簡單地說,類的方法是它的應用程序編程接口(API)。
  • 類成員變量(class variable)又叫靜態變量;類方法(class method)又叫靜態方法:
  • 實例變量(instance variable)和實例方法(instance method)是不用static形容的實例和方法;
  • 兩者有如下的區別:
    • 類方法是屬於整個類,而不屬於某個對象。 
    • 類方法只能訪問類成員變量(方法),不能訪問實例變量(方法),而實例方法能夠訪問類成員變量(方法)和實例變量(方法)。 
    • 類方法的調用能夠經過類名.類方法和對象.類方法,而實例方法只能經過對象.實例方法訪問。 
    • 類方法不能被覆蓋,實例方法能夠被覆蓋。
    • 當類的字節碼文件被加載到內存時,類的實例方法不會被分配入口地址 當該類建立對象後,類中的實例方法才分配入口地址, 從而實例方法能夠被類建立的任何對象調用執行。
    • 類方法在該類被加載到內存時,就分配了相應的入口地址。 從而類方法不只能夠被類建立的任何對象調用執行,也能夠直接經過類名調用。 類方法的入口地址直到程序退出時才被取消。
  • 注意:
    • 當咱們建立第一個對象時,類中的實例方法就分配了入口地址,當再建立對象時,再也不分配入口地址。
    • 也就是說,方法的入口地址被全部的對象共享,當全部的對象都不存在時,方法的入口地址才被取消。
  • 總結:
    • 類變量和類方法與類相關聯,而且每一個類都會出現一次。 使用它們不須要建立對象。
    • 實例方法和變量會在每一個類的實例中出現一次。
  • 舉例:

 

【接口】安全

  • 概念:接口在JAVA編程語言中是一個抽象類型,用於設計和表達ADT的語言機制,其是抽象方法的集合,接口一般以interface來聲明。
  • 一個類經過繼承接口的方式,從而來繼承接口的抽象方法。
  • 接口並非類,編寫接口的方式和類很類似,可是它們屬於不一樣的概念。類描述對象的屬性和方法。接口則包含類要實現的方法。
  • 一個接口能夠擴展其餘接口,一個類能夠實現多個接口;一個接口也能夠有多重實現
  • 除非實現接口的類是抽象類,不然該類要定義接口中的全部方法。
  • 接口沒法被實例化,可是能夠被實現。一個實現接口的類,必須實現接口內所描述的全部方法,不然就必須聲明爲抽象類。另外,在 Java 中,接口類型可用來聲明一個變量,他們能夠成爲一個空指針,或是被綁定在一個以此接口實現的對象。

 一個接口的實例:編程語言

/** MyString represents an immutable sequence of characters. */
public interface MyString { 

    // We'll skip this creator operation for now
    // /** @param b a boolean value
    //  *  @return string representation of b, either "true" or "false" */
    // public static MyString valueOf(boolean b) { ... }

    /** @return number of characters in this string */
    public int length();

    /** @param i character position (requires 0 <= i < string length)
     *  @return character at position i */
    public char charAt(int i);

    /** Get the substring between start (inclusive) and end (exclusive).
     *  @param start starting index
     *  @param end ending index.  Requires 0 <= start <= end <= string length.
     *  @return string consisting of charAt(start)...charAt(end-1) */
    public MyString substring(int start, int end);
}

一種實現:ide

 1 public class FastMyString implements MyString {
 2 
 3     private char[] a;
 4     private int start;
 5     private int end;
 6 
 7     /** Create a string representation of b, either "true" or "false".
 8      *  @param b a boolean value */
 9     public FastMyString(boolean b) {
10         a = b ? new char[] { 't', 'r', 'u', 'e' } 
11               : new char[] { 'f', 'a', 'l', 's', 'e' };
12         start = 0;
13         end = a.length;
14     }
15 
16     // private constructor, used internally by producer operations.
17     private FastMyString(char[] a, int start, int end) {
18         this.a = a;
19         this.start = start;
20         this.end = end;
21     }
22 
23     @Override public int length() { return end - start; }
24 
25     @Override public char charAt(int i) { return a[start + i]; }
26 
27     @Override public MyString substring(int start, int end) {
28         return new FastMyString(this.a, this.start + start, this.end + end);
29     }
30 }

客戶端如何使用此ADT?這是一個例子:函數

MyString s = new FastMyString(true);
System.out.println("The first character is: " + s.charAt(0));

  但其中有問題,這麼實現接口打破了抽象邊界,接口定義中沒有包含constructor,也沒法保證全部實現類中都包含了一樣名字的constructor。 故而,客戶端須要知道該接口的某個具體實現類的名字。由於Java中的接口不能包含構造函數,因此它們必須直接調用其中一個具體類的構造函數。該構造函數的規範不會出如今接口的任何地方,因此沒有任何靜態的保證,即不一樣的實現甚至會提供相同的構造函數。性能

  在Java 8中,咱們能夠用valueof的靜態工廠方法 代替構造器。學習

public interface MyString { 
    /** @param b a boolean value
     *  @return string representation of b, either "true" or "false" */
    public static MyString valueOf(boolean b) {
        return new FastMyString(true);
    }
    // ...

  此時,客戶端使用ADT就不會破壞抽象邊界:ui

MyString s = MyString.valueOf(true);
System.out.println("The first character is: " + s.charAt(0));

總結:接口的好處

  • Safe from bugs 
    ADT是由其操做定義的,接口就是這樣作的。 
    當客戶端使用接口類型時,靜態檢查確保他們只使用由接口定義的方法。 
    若是實現類公開其餘方法,或者更糟糕的是,具備可見的表示,客戶端不會意外地看到或依賴它們。 
    當咱們有一個數據類型的多個實現時,接口提供方法簽名的靜態檢查。

  • Easy to understand 
    客戶和維護人員確切知道在哪裏查找ADT的規約。 
    因爲接口不包含實例字段或實例方法的實現,所以更容易將實現的細節保留在規範以外。

  • Ready for change 
    經過添加實現接口的類,咱們能夠輕鬆地添加新類型的實現。 
    若是咱們避免使用靜態工廠方法的構造函數,客戶端將只能看到該接口。 
    這意味着咱們能夠切換客戶端正在使用的實現類,而無需更改其代碼。

【抽象類】

  • 抽象類除了不能實例化對象以外,類的其它功能依然存在,成員變量、成員方法和構造方法的訪問方式和普通類同樣。
  • 因爲抽象類不能實例化對象,因此抽象類必須被繼承,才能被使用。
  • 父類包含了子類集合的常見的方法,可是因爲父類自己是抽象的,因此不能使用這些方法。
  • 在Java中抽象類表示的是一種繼承關係,一個類只能繼承一個抽象類,而一個類卻能夠實現多個接口。
  • 若是一個類包含抽象方法,那麼該類必須是抽象類。
  • 任何子類必須重寫父類的抽象方法,或者聲明自身爲抽象類。
  • 構造方法,類方法(用static修飾的方法)不能聲明爲抽象方法。

 

## OOP的不一樣特徵

 【封裝】

  • 封裝(英語:Encapsulation)是指一種將抽象性函式接口的實現細節部份包裝、隱藏起來的方法。
  • 設計良好的代碼隱藏了全部的實現細節
    • 乾淨地將API與實施分開
    • 模塊只能經過API進行通訊
    • 對彼此的內在運做不了解
  • 信息封裝的好處
    • 將構成系統的類分開,減小耦合
    • 加快系統開發速度
    • 減輕了維護的負擔
    • 啓用有效的性能調整
    • 增長軟件複用
  • 信息隱藏接口
    • 使用接口類型聲明變量
    • 客戶端僅使用接口中定義的方法
    • 客戶端代碼沒法直接訪問屬性
  • 實現封裝的方法
  1. 修改屬性的可見性來限制對屬性的訪問(通常限制爲private),例如
    public class Person {
        private String name;
        private int age;
    }
  2. 對每一個值屬性提供對外的公共方法訪問,也就是建立一對賦取值方法,用於對私有屬性的訪問,例如:
     1 public class Person{
     2     private String name;
     3     private int age;
     4  5     public int getAge(){
     6       return age;
     7     }
     8  9     public String getName(){
    10       return name;
    11     }
    12 13     public void setAge(int age){
    14       this.age = age;
    15     }
    16 17     public void setName(String name){
    18       this.name = name;
    19     }
    20 }

    採用 this 關鍵字是爲了解決實例變量(private String name)和局部變量(setName(String name)中的name變量)之間發生的同名的衝突。

【繼承與重寫】

  • 繼承概念:繼承就是子類繼承父類的特徵和行爲,使得子類對象(實例)具備父類的實例域和方法,或子類從父類繼承方法,使得子類具備父類相同的行爲。 
  • 重寫概念:重寫是子類對父類的容許訪問的方法的實現過程進行從新編寫, 返回值和形參都不能改變。即外殼不變,核心重寫!
  • 重寫的好處在於子類能夠根據須要,定義特定於本身的行爲。 也就是說子類可以根據須要實現父類的方法。
  • 實際執行時調用那種方法,在運行時決定
  • 重寫方法不能拋出新的檢查異常或者比被重寫方法申明更加寬泛的異常。
  • 子類只能添加新方法,沒法重寫超類中的方法。
  • 當子類包含一個覆蓋超類方法的方法時,它也可使用關鍵字super調用超類方法。例子以下:
     1 class Animal{
     2    public void move(){
     3       System.out.println("動物能夠移動");
     4    }
     5 }
     6  
     7 class Dog extends Animal{
     8    public void move(){
     9       super.move(); // 應用super類的方法
    10       System.out.println("狗能夠跑和走");
    11    }
    12 }
    13  
    14 public class TestDog{
    15    public static void main(String args[]){
    16  
    17       Animal b = new Dog(); // Dog 對象
    18       b.move(); //執行 Dog類的方法
    19  
    20    }
    21 }
  • 方法重寫的規則
    • 參數列表必須徹底與被重寫方法的相同;
    • 返回類型必須徹底與被重寫方法的返回類型相同;
    • 訪問權限不能比父類中被重寫的方法的訪問權限更低。例如:若是父類的一個方法被聲明爲public,那麼在子類中重寫該方法就不能聲明爲protected。
    • 父類的成員方法只能被它的子類重寫。
    • 聲明爲final的方法不能被重寫。
    • 聲明爲static的方法不能被重寫,可是可以被再次聲明。
    • 子類和父類在同一個包中,那麼子類能夠重寫父類全部方法,除了聲明爲private和final的方法。
    • 子類和父類不在同一個包中,那麼子類只可以重寫父類的聲明爲public和protected的非final方法。
    • 重寫的方法可以拋出任何非強制異常,不管被重寫的方法是否拋出異常。可是,重寫的方法不能拋出新的強制性異常,或者比被重寫方法聲明的更普遍的強制性異常,反之則能夠。
    • 構造方法不能被重寫。
    • 若是不能繼承一個方法,則不能重寫這個方法。

 

【多態與重載】

  • 多態是同一行爲具備多種不一樣表現形式或形態的能力
  • 三種類型的多態
    • Ad hoc polymorphism (特殊多態):功能重載,一個函數能夠有多個同名的實現。
    • Parametric polymorphism (參數多態): 泛型或泛型編程,一個類型名字能夠表明多個類型
    • Subtyping (also called subtype polymorphism or inclusion polymorphism 子類型多態、包含多態):當一個名稱表示許多不一樣的類與一些常見的超類相關的實例。
  • 重載(overloading) 是在一個類裏面,方法名字相同,而參數不一樣,返回類型能夠相同也能夠不一樣。
  • 每一個重載的方法(或構造函數)都必須有一個獨一無二的參數類型列表。
  • 價值:方便client調用,client可用不一樣的參數列表,調用一樣的函數。
  • 重載是靜態多態,根據參數列表進行最佳匹配。在編譯階段時決定要具體執行哪一個方法 (static type checking) ,與之相反,重構方法則是在run-time進行dynamic checking!
  • 重載規則
    • 被重載的方法必須改變參數列表(參數個數或類型不同);
    • 被重載的方法能夠改變返回類型;
    • 被重載的方法能夠改變訪問修飾符;
    • 被重載的方法能夠聲明新的或更廣的檢查異常;
    • 方法可以在同一個類中或者在一個子類中被重載。
    • 沒法以返回值類型做爲重載函數的區分標準。
 1 public class OverloadExample { 
 2     public static void main(String args[]) { 
 3         System.out.println(add("C","D"));
 4         System.out.println(add("C","D","E")); 
 5         System.out.println(add(2,3));
 6     }
 7     public static String add(String c, String d) { 
 8         return c.concat(d);
 9     }
10     public static String add(String c, String d, String e){ 
11         return c.concat(d).concat(e);
12     }
13     public static int add(int a, int b) { 
14         return a+b;
15     }
16 }
特殊多態(Ad hoc)
public class Pair<E> { 
    private final E first, second; 
    public Pair(E first, E second) { 
        this.first = first; 
        this.second = second; 
    } 
    public E first() { 
        return first; 
    } 
    public E second() { 
    return second; 
    } 
}
Client:
Pair<String> p = new Pair<>("Hello", "world"); 
String result = p.first();
參數多態性和泛型編程

子類型多態

子類型的規約不能弱化超類型的規約。 
子類型多態:不一樣類型的對象能夠統一的處理而無需區分,從而隔離了「變化」。

 

【重寫與重載的區別】

區別點 重載方法 重寫方法
參數列表 必須修改 必定不能修改
返回類型 能夠修改 必定不能修改
異常 能夠修改 能夠減小或刪除,必定不能拋出新的或者更廣的異常
訪問 能夠修改 必定不能作更嚴格的限制(能夠下降限制)
 調用狀況 引用類型決定選擇哪一個重載版本(基於聲明的參數類型)。 在編譯時發生 對象類型(換句話說,堆上實際實例的類型)決定選擇哪一種方法在運行時發生。 

  方法的重寫(Overriding)和重載(Overloading)是java多態性的不一樣表現,重寫是父類與子類之間多態性的一種表現,重載能夠理解成多態的具體表現形式。

  • 方法重載是一個類中定義了多個方法名相同,而他們的參數的數量不一樣或數量相同而類型和次序不一樣,則稱爲方法的重載(Overloading)。
  • 方法重寫是在子類存在方法與父類的方法的名字相同,並且參數的個數與類型同樣,返回值也同樣的方法,就稱爲重寫(Overriding)。
  • 方法重載是一個類的多態性表現,而方法重寫是子類與父類的一種多態性表現。

 

【泛型】(參數多態)

  • 泛型的本質是參數化類型,也就是說所操做的數據類型被指定爲一個參數。
  • 能夠寫一個泛型方法,該方法在調用時能夠接收不一樣類型的參數。根據傳遞給泛型方法的參數類型,編譯器適當地處理每個方法調用。
  • 下面是定義泛型方法的規則:
    • 全部泛型方法聲明都有一個類型參數聲明部分(由尖括號分隔),該類型參數聲明部分在方法返回類型以前(在下面例子中的<E>)。
    • 每個類型參數聲明部分包含一個或多個類型參數,參數間用逗號隔開。一個泛型參數,也被稱爲一個類型變量,是用於指定一個泛型類型名稱的標識符。
    • 類型參數能被用來聲明返回值類型,而且能做爲泛型方法獲得的實際參數類型的佔位符。
    • 泛型方法體的聲明和其餘方法同樣。注意類型參數只能表明引用型類型,不能是原始類型(像int,double,char的等)。
ublic interface Set<E> {

    /**
     * Test for membership.
     * @param e an element
     * @return true iff this set contains e
     */
    public boolean contains(E e);

    /**
     * Modifies this set by adding e to the set.
     * @param e element to add
     */
    public void add(E e);

}

public class CharSet1 implements Set<Character> {

    private String s = "";

    @Override
    public boolean contains(Character e) {
        checkRep();
        return s.indexOf(e) != -1;
    }

    @Override
    public void add(Character e) {
        if (!contains(e)) s += e;
        checkRep();
    }

}
泛型接口,非泛型的實現類
public interface Set<E> {

    // ...
public class HashSet<E> implements Set<E> {

    // ...
泛型接口,泛型的實現類
  • 一些細節:
    • 能夠有多個類型參數:例如Map<E, F>, Map<String, Integer>
    • 通配符,只在使用泛型的時候出現,不能在定義中出現,例:List<?> list = new ArrayList<String>();
    • 泛型類型信息被刪除 
    • Cannot use instanceof() to check generic type 運行時泛型消失了!
    • 沒法建立通用數組
Pair<String>[] foo = new Pair<String>[42]; // won't compile

 

## 設計好的類

  •  好的類具備的特色
    • 簡單
    • 本質上是線程安全的
    • 能夠自由分享
    • 不須要防護式拷貝
    • 優秀的building blocks
  • 如何編寫一個不可變的類
    • 不要提供任何mutators
    • 確保沒有方法可能被覆蓋
    • 使全部的fields有final修飾
    • 使全部的fields有private修飾
    • 確保任何可變組件的安全性(避免表示泄露)
    • 實現toString()hashCode()clone()equals()等。
相關文章
相關標籤/搜索