抽象クラスとメソッド

実装の一部を子クラスに委ねる仕組み

抽象クラスの基本概念

抽象クラス(abstract class)とは、「abstract」キーワードを使って宣言されたクラスで、少なくとも1つの抽象メソッドを持つか、または明示的に抽象クラスとして宣言されているクラスです。抽象クラスはインスタンス化できず、継承されることを前提とします。

クラス図

図1: 抽象クラスのクラス図

基本的な抽象クラスの例

// 抽象クラスの定義
public abstract class Shape {
    // フィールド
    protected String color;
    protected boolean filled;
    
    // コンストラクタ
    public Shape() {
        this.color = "red";
        this.filled = true;
    }
    
    public Shape(String color, boolean filled) {
        this.color = color;
        this.filled = filled;
    }
    
    // 具象メソッド(通常のメソッド)
    public String getColor() {
        return color;
    }
    
    public void setColor(String color) {
        this.color = color;
    }
    
    public boolean isFilled() {
        return filled;
    }
    
    public void setFilled(boolean filled) {
        this.filled = filled;
    }
    
    // 抽象メソッド(実装がない)
    public abstract double getArea();
    public abstract double getPerimeter();
    
    @Override
    public String toString() {
        return "Shape[color=" + color + ", filled=" + filled + "]";
    }
}

// 抽象クラスを継承した具象クラス
public class Circle extends Shape {
    private double radius;
    
    public Circle() {
        super();
        this.radius = 1.0;
    }
    
    public Circle(double radius) {
        super();
        this.radius = radius;
    }
    
    public Circle(double radius, String color, boolean filled) {
        super(color, filled);
        this.radius = radius;
    }
    
    public double getRadius() {
        return radius;
    }
    
    public void setRadius(double radius) {
        this.radius = radius;
    }
    
    // 抽象メソッドの実装
    @Override
    public double getArea() {
        return Math.PI * radius * radius;
    }
    
    @Override
    public double getPerimeter() {
        return 2 * Math.PI * radius;
    }
    
    @Override
    public String toString() {
        return "Circle[" + super.toString() + ", radius=" + radius + "]";
    }
}

抽象クラスの特徴と用途

特徴:

  • 直接インスタンス化することはできない(new Shape() はエラー)
  • 抽象メソッドを持つことができる(abstract キーワードで宣言されたメソッド)
  • 具象メソッド(実装のあるメソッド)も持つことができる
  • コンストラクタとフィールドを持つことができる
  • サブクラスはすべての抽象メソッドを実装するか、またはそのサブクラス自体が抽象クラスでなければならない

用途:

  • 関連するクラス間で共通の基本機能を提供する
  • サブクラスに共通のインターフェースを強制する
  • デフォルトの実装を提供しつつ、特定の機能をサブクラスに委ねる
  • コードの重複を減らし、保守性を向上させる

抽象メソッド

抽象メソッドはメソッドの宣言のみを含み、実装は含みません。サブクラスはこれらのメソッドを実装することが要求されます。

抽象メソッドの特徴

// 抽象メソッドの定義
public abstract ReturnType methodName(Parameters);

// 例
public abstract void draw();
public abstract double calculateSalary();

抽象メソッドには以下の特徴があります:

  • abstract キーワードを使用する
  • メソッド本体(波括弧 {})を持たず、セミコロン ; で終わる
  • プライベートにすることはできない(サブクラスがアクセスできる必要があるため)
  • 静的メソッド(static)にすることはできない
  • 最終メソッド(final)にすることはできない(オーバーライドする必要があるため)

抽象クラスと具象メソッド

抽象クラスは抽象メソッドだけでなく、具象メソッド(実装のあるメソッド)も含むことができます。これにより、サブクラスに共通の実装を提供しつつ、特定の機能をサブクラスに委ねることができます。

抽象クラスと具象メソッドの例

public abstract class Database {
    // 具象メソッド - 共通の実装を提供
    public final void connect() {
        System.out.println("データベース接続を確立しています...");
        // 共通の接続ロジック
        performConnection();
        System.out.println("接続完了");
    }
    
    // 抽象メソッド - サブクラスが実装する必要がある
    protected abstract void performConnection();
    
    // 具象メソッド - オーバーライド可能
    public void disconnect() {
        System.out.println("標準的な切断処理を実行しています");
    }
    
    // 抽象メソッド - 各DBエンジン固有のクエリ実行方法
    public abstract void executeQuery(String query);
}

// MySQL用の具象サブクラス
public class MySQLDatabase extends Database {
    @Override
    protected void performConnection() {
        System.out.println("MySQL固有の接続プロトコルを使用しています");
    }
    
    @Override
    public void executeQuery(String query) {
        System.out.println("MySQLエンジンでクエリを実行: " + query);
    }
    
    @Override
    public void disconnect() {
        System.out.println("MySQL接続のクリーンアップを実行");
        super.disconnect();
    }
}

// PostgreSQL用の具象サブクラス
public class PostgreSQLDatabase extends Database {
    @Override
    protected void performConnection() {
        System.out.println("PostgreSQL固有の接続プロトコルを使用しています");
    }
    
    @Override
    public void executeQuery(String query) {
        System.out.println("PostgreSQLエンジンでクエリを実行: " + query);
    }
}

シーケンス図

図2: 抽象クラスのシーケンス図

抽象クラスとインターフェースの比較

抽象クラスとインターフェースは似ていますが、重要な違いがあります。

特徴 抽象クラス インターフェース
継承 単一継承のみ(1つの抽象クラスのみ拡張可能) 複数実装可能(多くのインターフェースを実装可能)
メソッド 抽象メソッドと具象メソッドの両方を含むことができる Java 8以降: 抽象メソッド、デフォルトメソッド、静的メソッド
Java 9以降: privateメソッドも可能
フィールド どんな種類のフィールドも持つことができる 定数(public static final)のみ
コンストラクタ コンストラクタを持つことができる コンストラクタを持つことができない
アクセス修飾子 クラス、メソッド、フィールド用のすべてのアクセス修飾子を使用可能 すべてがpublic(明示的に指定しなくてもpublic)
用途 関連するクラスのグループに共通の基本機能を提供する オブジェクトが実装すべき振る舞いを定義する
デザイン方針 「is-a」関係を表現(AはBの一種) 「has-a」または「can-do」関係を表現(AはBができる)

テンプレートメソッドパターン

抽象クラスを使用した一般的なデザインパターンの1つが「テンプレートメソッドパターン」です。これは、アルゴリズムの骨格を抽象クラスで定義し、いくつかのステップをサブクラスで実装できるようにするパターンです。

テンプレートメソッドパターンの例

public abstract class DocumentGenerator {
    // テンプレートメソッド - アルゴリズムの骨格を定義
    public final void generateDocument() {
        createHeader();
        createBody();
        createFooter();
        applyFormatting();
        
        if (shouldAddWatermark()) {
            addWatermark();
        }
    }
    
    // 抽象メソッド - サブクラスが必ず実装
    protected abstract void createHeader();
    protected abstract void createBody();
    protected abstract void createFooter();
    
    // 具象メソッド - 共通の実装を提供
    protected void applyFormatting() {
        System.out.println("標準フォーマットを適用中...");
    }
    
    // フックメソッド - サブクラスがオーバーライドしてもしなくてもOK
    protected boolean shouldAddWatermark() {
        return false; // デフォルトでは透かしなし
    }
    
    // 具象メソッド - 共通の実装
    private void addWatermark() {
        System.out.println("ドキュメントに透かしを追加中...");
    }
}

// PDFドキュメント生成器
public class PDFGenerator extends DocumentGenerator {
    @Override
    protected void createHeader() {
        System.out.println("PDFヘッダーを作成中...");
    }
    
    @Override
    protected void createBody() {
        System.out.println("PDF本文を作成中...");
    }
    
    @Override
    protected void createFooter() {
        System.out.println("PDFフッターを作成中...");
    }
    
    @Override
    protected void applyFormatting() {
        System.out.println("PDF固有のフォーマットを適用中...");
    }
    
    @Override
    protected boolean shouldAddWatermark() {
        return true; // PDFには透かしを追加
    }
}

// HTMLドキュメント生成器
public class HTMLGenerator extends DocumentGenerator {
    @Override
    protected void createHeader() {
        System.out.println("HTMLヘッダーを作成中...");
    }
    
    @Override
    protected void createBody() {
        System.out.println("HTML本文を作成中...");
    }
    
    @Override
    protected void createFooter() {
        System.out.println("HTMLフッターを作成中...");
    }
}

抽象クラスを使用する際のベストプラクティス

  • 抽象メソッドは慎重に選択する: 子クラスが必ず独自の実装を提供する必要があるメソッドだけを抽象化する
  • 共通コードを抽出する: 複数のサブクラスで共有される機能を抽象クラスの具象メソッドとして実装する
  • 保護フィールドを適切に使用する: サブクラスが直接アクセスする必要のあるフィールドには protected アクセス修飾子を使用する
  • 抽象クラスを単純に保つ: 抽象クラスがあまりに多くの責任を持つと、サブクラスに不必要な機能が継承される
  • フックメソッドを提供する: サブクラスが処理の一部をカスタマイズできるフックメソッドを用意する
  • テンプレートメソッドには final を付ける: アルゴリズムの骨格を定義するテンプレートメソッドはオーバーライドされるべきではない

抽象クラスと具象クラスの選択

設計の決定において、クラスを抽象にするか具象にするかを考慮する必要があります。

抽象クラスを選択する場合:

  • クラスが不完全であり、それ自体では役に立たない場合
  • サブクラスに共通の振る舞いを提供しつつ、一部の機能は子クラスが実装する必要がある場合
  • 複数のサブクラスが同じコードを共有している場合
  • サブクラスが特定のメソッドを必ず実装するように強制したい場合

具象クラスを選択する場合:

  • クラスが完全であり、それ自体で意味のあるオブジェクトを表現する場合
  • 全ての機能が実装されていて、インスタンス化して直接使用できる必要がある場合
  • 特定の具体的な実体や概念を表現する場合

まとめ

抽象クラスは、オブジェクト指向設計において強力なメカニズムであり、コードの再利用性と拡張性を高めます。共通の振る舞いと構造を持つクラス階層を設計する際に、抽象クラスを適切に活用することで、一貫性のある柔軟なコードベースを構築できます。インターフェースと組み合わせることで、さらに強力な設計パターンを実現することが可能です。