跳轉到

Java基礎:物件導向程式設計

1. 物件導向程式設計簡介

物件導向程式設計(Object-Oriented Programming,OOP)是一種以「物件」為核心的程式設計範式。它將資料和操作資料的方法組織在一起,形成了所謂的「物件」。這種方法有助於創建更加模組化、可重用和易於維護的程式碼。

Java 作為一種純物件導向的程式設計語言,擁抱 OOP 的概念。在 Java 中,除了基本資料型別外,幾乎所有東西都是物件。Java 的物件導向特性包括:

  1. 封裝:通過存取修飾符控制資料的可見性。
  2. 繼承:允許新類別基於現有類別創建,促進程式碼重用。
  3. 多型:同一個介面可以有多個實現,增加了程式的靈活性。

2. 類別與物件

在 Java 中,類別和物件是物件導向程式設計的基本構建塊。類別是物件的藍圖或模板,而物件則是類別的實例。

類別的定義與結構

類別在 Java 中的基本結構如下:

public class Car {
    // 屬性(成員變數)
    private String brand;
    private String model;
    private int year;

    // 建構子
    public Car(String brand, String model, int year) {
        this.brand = brand;
        this.model = model;
        this.year = year;
    }

    // 方法
    public void startEngine() {
        System.out.println("引擎啟動!");
    }

    // Getter 和 Setter 方法
    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    // 其他 getter 和 setter 方法...
}

物件的創建與使用

一旦定義了類別,我們就可以創建該類別的物件並使用它:

public class Main {
    public static void main(String[] args) {
        // 創建 Car 類別的物件
        Car myCar = new Car("Toyota", "Corolla", 2022);

        // 使用物件的方法
        myCar.startEngine();

        // 使用 getter 方法
        System.out.println("我的車是 " + myCar.getBrand());
    }
}

建構子

建構子是一種特殊的方法,用於初始化新創建的物件。它的名稱必須與類別名稱相同,且沒有回傳型別。

public class Person {
    private String name;
    private int age;

    // 無參數建構子
    public Person() {
        this.name = "未知";
        this.age = 0;
    }

    // 帶參數建構子
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

3. 封裝

封裝是物件導向程式設計的四大基本概念之一,它指的是將資料和操作資料的方法綁定在一起,並對外部隱藏內部的實現細節。在 Java 中,封裝主要通過存取修飾符和 getter/setter 方法來實現。

存取修飾符

Java 提供了四種存取修飾符,用於控制類別成員的可見性:

  1. private:只能在同一個類別中存取
  2. default(無修飾符):同一個套件中的類別可以存取
  3. protected:同一個套件中的類別和所有子類別可以存取
  4. public:任何類別都可以存取

Getter 和 Setter 方法

為了實現封裝,我們通常將類別的屬性設為 private,然後提供公開的 getter 和 setter 方法來存取和修改這些屬性。

public class Student {
    private String name;
    private int age;

    // Getter 方法
    public String getName() {
        return name;
    }

    // Setter 方法
    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        if (age > 0 && age < 120) {  // 加入驗證邏輯
            this.age = age;
        } else {
            System.out.println("無效的年齡");
        }
    }
}

資料隱藏的重要性

封裝的主要優點包括:

  1. 資料保護:防止外部直接存取和修改物件的內部狀態。
  2. 靈活性:可以改變內部實現而不影響外部程式碼。
  3. 可維護性:更容易管理和維護程式碼。

例如,我們可以在 setter 方法中加入驗證邏輯,確保資料的正確性:

public class BankAccount {
    private double balance;

    public double getBalance() {
        return balance;
    }

    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        } else {
            System.out.println("存款金額必須大於零");
        }
    }

    public void withdraw(double amount) {
        if (amount > 0 && amount <= balance) {
            balance -= amount;
        } else {
            System.out.println("提款金額無效或餘額不足");
        }
    }
}

通過封裝,我們可以確保物件的內部狀態始終保持一致和有效,同時為未來的修改和擴展提供了靈活性。

4. 繼承

繼承是物件導向程式設計中的一個核心概念,它允許我們基於現有的類別創建新的類別。在 Java 中,繼承使用 extends 關鍵字來實現。

繼承的概念與語法

基本語法如下:

public class 子類別 extends 父類別 {
    // 子類別的成員
}

例如:

public class Animal {
    protected String name;

    public void eat() {
        System.out.println(name + " 正在吃東西");
    }
}

public class Dog extends Animal {
    public void bark() {
        System.out.println(name + " 汪汪叫");
    }
}

在這個例子中,Dog 類別繼承了 Animal 類別,因此 Dog 類別擁有 Animal 類別的所有公開和受保護的成員。

方法覆寫

子類別可以覆寫(override)父類別的方法,提供自己的實現:

public class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println(name + " 優雅地吃東西");
    }
}

使用 @Override 註解可以確保我們確實在覆寫父類別的方法。

super 關鍵字

super 關鍵字用於呼叫父類別的方法或建構子:

public class Bird extends Animal {
    public Bird(String name) {
        super(name);  // 呼叫父類別的建構子
    }

    @Override
    public void eat() {
        super.eat();  // 呼叫父類別的 eat 方法
        System.out.println(name + " 啄食");
    }
}

繼承的優點與注意事項

繼承的主要優點包括:
1. 程式碼重用
2. 建立類別層次結構
3. 多型的基礎

繼承要注意:
1. 避免過度使用繼承,可能導致類別層次過於複雜
2. 遵循「組合優於繼承」的原則
3. Java 不支援多重繼承,但可以通過介面實現類似的功能

5. 多型

多型是物件導向程式設計的另一個重要概念,允許我們以一致的方式處理不同類別的物件。在 Java 中,多型主要通過方法覆寫和介面實現。

多型的概念

多型允許一個介面有多種實現。這意味著可以使用父類別的參考變數來引用子類別的物件。

方法多型

方法多型是通過方法覆寫實現的。例如:

public class Animal {
    public void makeSound() {
        System.out.println("動物發出聲音");
    }
}

public class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("狗:汪汪!");
    }
}

public class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("貓:喵喵!");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal1 = new Dog();
        Animal animal2 = new Cat();

        animal1.makeSound();  // 輸出:狗:汪汪!
        animal2.makeSound();  // 輸出:貓:喵喵!
    }
}

運算子多型

Java 也支援某些運算子的多型,如 + 運算子可用於數字相加或字串連接。

向上轉型與向下轉型

  • 向上轉型(Upcasting):將子類別的參考賦值給父類別的變數,這是自動完成的。
  • 向下轉型(Downcasting):將父類別的參考轉換為子類別的參考,需要明確的類型轉換。
Animal animal = new Dog();  // 向上轉型
Dog dog = (Dog) animal;     // 向下轉型

if (animal instanceof Cat) {
    Cat cat = (Cat) animal;  // 使用 instanceof 檢查類型安全
}

6. 抽象類別與介面

抽象類別和介面是 Java 中實現抽象化的兩種重要機制,它們在設計複雜系統時扮演著關鍵角色。

抽象類別的定義與用途

抽象類別是一種不能被實例化的類別,它可以包含抽象方法(沒有實作的方法)和具體方法。

abstract class Shape {
    protected String color;

    public Shape(String color) {
        this.color = color;
    }

    // 抽象方法
    public abstract double getArea();

    // 具體方法
    public void displayColor() {
        System.out.println("顏色是:" + color);
    }
}

class Circle extends Shape {
    private double radius;

    public Circle(String color, double radius) {
        super(color);
        this.radius = radius;
    }

    @Override
    public double getArea() {
        return Math.PI * radius * radius;
    }
}

抽象類別的主要用途: 1. 為一組相關的類別提供一個共同的基類 2. 定義模板方法,子類別可以根據需要覆寫這些方法

介面的定義與實現

介面是一種完全抽象的類型,只包含抽象方法的宣告。

interface Drawable {
    void draw();
}

class Rectangle extends Shape implements Drawable {
    private double width;
    private double height;

    public Rectangle(String color, double width, double height) {
        super(color);
        this.width = width;
        this.height = height;
    }

    @Override
    public double getArea() {
        return width * height;
    }

    @Override
    public void draw() {
        System.out.println("繪製一個矩形");
    }
}

介面的主要特點:
1. 可以實現多重繼承
2. 定義一組相關方法的規範
3. 默認方法(default method)和靜態方法(static method)(Java 8+)

默認方法(Default Method)
定義位置:默認方法是在接口中定義的,使用 default 關鍵字。
目的:允許接口添加新方法而不影響實現該接口的類。這樣可以保持向後兼容性。
實現:默認方法可以有具體的實現,實現類可以選擇使用默認實現或重寫該方法。

interface MyInterface {
    default void myDefaultMethod() {
        System.out.println("這是默認方法");
    }
}

靜態方法(Static Method)
定義位置:靜態方法可以在類中或接口中定義,但在接口中使用 static 關鍵字。
目的:靜態方法屬於類本身,而不是類的實例。可以直接通過類名調用。
無法重寫:靜態方法不能被實現類重寫,因為它們不屬於對象的實例。

class MyClass {
    static void myStaticMethod() {
        System.out.println("這是靜態方法");
    }
}

抽象類別 vs 介面

選擇使用抽象類別還是介面取決於具體需求:

使用抽象類別

  • 當你想要在相關的類別之間共享程式碼
  • 需要存取非公開的成員變數或方法
  • 需要宣告非靜態或非 final 的成員

使用介面

  • 當你想要定義一個可由不相關類別實現的類型
  • 需要實現多重繼承
  • 想要指定特定行為,而不關心誰來實現它

7. 物件導向設計原則

在物件導向程式設計中,遵循一些設計原則可以幫助我們創建更加靈活、可維護和可擴展的系統。以下是一些重要的設計原則:

SOLID 原則簡介

SOLID 是五個物件導向設計原則的縮寫:

單一職責原則(Single Responsibility Principle, SRP)

  • 一個類別應該只有一個改變的理由。

開放封閉原則(Open-Closed Principle, OCP)

  • 軟體實體應該對擴展開放,對修改封閉。

里氏替換原則(Liskov Substitution Principle, LSP)

  • 子類別必須能夠替換其父類別而不影響程式的正確性。

介面隔離原則(Interface Segregation Principle, ISP)

  • 多個特定客戶端介面優於一個通用介面。

依賴反轉原則(Dependency Inversion Principle, DIP)

  • 高層模組不應該依賴於低層模組,兩者都應該依賴於抽象。

設計模式概述

設計模式是解決常見設計問題的可重用解決方案。一些常見的設計模式包括:

  1. 創建型模式:如單例模式、工廠模式、建造者模式等。
  2. 結構型模式:如適配器模式、裝飾者模式、代理模式等。
  3. 行為型模式:如觀察者模式、策略模式、命令模式等。

物件導向設計的最佳實踐

  1. 組合優於繼承:盡可能使用組合來實現程式碼重用,而不是過度依賴繼承。

  2. 程式設計到介面,而不是具體實現:這增加了程式的靈活性。

  3. 保持類別的高內聚性和低耦合性:每個類別應該專注於單一任務,並儘量減少對其他類別的依賴。

  4. 遵循 DRY 原則(Don't Repeat Yourself):避免程式碼重複。

  5. YAGNI 原則(You Ain't Gonna Need It):只實現當前需要的功能,避免過度設計。

  6. 使用設計模式,但不要濫用:設計模式是工具,不是目標。

本篇文章同步刊載iThome: iThome
筆者個人的網站: JUNYI