CodeGym /コース /JAVA 25 SELF /実務の課題における抽象化の例

実務の課題における抽象化の例

JAVA 25 SELF
レベル 19 , レッスン 3
使用可能

1. 実際のアプリケーションにおける抽象化: なぜ必要か

これまでの講義で抽象化の概念と簡単な例を見てきました。ここでは、より現実的な課題でこのアプローチがどのように機能するかを確認します。 実務のプロジェクトでは、異なるが似通ったオブジェクトを扱うことがほとんどです。たとえば、さまざまな支払い方法、さまざまな交通手段、グラフィックエディタのさまざまな図形です。抽象化を使わないと、コードはすぐに「if-else」の羅列とコピペの塊になります。抽象化を使えば、整然として美しく、何より拡張や保守がしやすくなります。

抽象化の効用:

  • 実装の詳細を隠せる: オブジェクトの内部構造を気にせず、共通のインターフェース越しに扱える。
  • コードの重複を避けられる: 共通の振る舞いやフィールドを基底クラスに集約できる。
  • システムを容易に拡張できる: 新しい種類のオブジェクトを追加しても既存コードを書き換えずに済む。
  • コードを柔軟にできる: 実装を差し替えても他の部分を変更せずに済む。

いくつかの分野別の例を見ていきましょう。

2. 例 1: 決済システム

課題

あなたはオンラインショップのモジュールを実装しています。タスクは異なる種類の支払い(クレジットカード、PayPal、暗号資産)を処理することです。どれも「支払いを処理する」必要がありますが、詳細はそれぞれ異なります。

抽象化: クラス Payment

public abstract class Payment {
    protected double amount;

    public Payment(double amount) {
        this.amount = amount;
    }

    // 抽象メソッド: 決済の処理方法はサブクラスが決める
    public abstract void process();

    // すべての決済に共通のメソッド
    public void printAmount() {
        System.out.println("支払金額: " + amount + " RUB");
    }
}

具体的な実装

public class CreditCardPayment extends Payment {
    private String cardNumber;

    public CreditCardPayment(double amount, String cardNumber) {
        super(amount);
        this.cardNumber = cardNumber;
    }

    @Override
    public void process() {
        System.out.println("カード決済を処理中: " + cardNumber);
        // ここに銀行との連携コードが入るはず :)
    }
}
public class PaypalPayment extends Payment {
    private String email;

    public PaypalPayment(double amount, String email) {
        super(amount);
        this.email = email;
    }

    @Override
    public void process() {
        System.out.println("PayPal 決済をこのアカウントで処理中: " + email);
        // ここでは PayPal API を呼び出す
    }
}
public class CryptoPayment extends Payment {
    private String walletAddress;

    public CryptoPayment(double amount, String walletAddress) {
        super(amount);
        this.walletAddress = walletAddress;
    }

    @Override
    public void process() {
        System.out.println("暗号通貨の支払いをウォレットへ処理中: " + walletAddress);
        // ここにはブロックチェーンの魔法があるかも
    }
}

抽象化の利用

import java.util.*;

public class PaymentDemo {
    public static void main(String[] args) {
        List<Payment> payments = new ArrayList<>();
        payments.add(new CreditCardPayment(1500.0, "1234 5678 9012 3456"));
        payments.add(new PaypalPayment(500.0, "user@example.com"));
        payments.add(new CryptoPayment(0.05, "0xABCD..."));

        for (Payment payment : payments) {
            payment.printAmount();
            payment.process();
            System.out.println("---");
        }
    }
}

実行結果:

支払金額: 1500.0 RUB
カード決済を処理中: 1234 5678 9012 3456
---
支払金額: 500.0 RUB
PayPal 決済をこのアカウントで処理中: user@example.com
---
支払金額: 0.05 RUB
暗号通貨の支払いをウォレットへ処理中: 0xABCD...
---

利点:

  • 既存コードを変えずに新しい支払い方法(例: Apple Pay)を追加できる。
  • 決済を扱うコードは具体的なタイプに依存しない。
  • 共通ロジック(例: printAmount() で金額を出力)は1か所に実装される。

3. 例 2: 交通手段

課題

ゲームやシミュレータには、車、自転車、列車などさまざまな交通手段があります。いずれも「移動」できますが、その方法は異なります。給油が必要なものもあれば、不要なものもあります。

抽象化: クラス Transport

public abstract class Transport {
    protected String name;

    public Transport(String name) {
        this.name = name;
    }

    public abstract void move();

    // すべての乗り物が給油を必要とするわけではない。デフォルトでは不要とする
    public void fuelUp() {
        System.out.println(name + ": 給油は不要です。");
    }
}

具体的な実装

public class Car extends Transport {
    public Car(String name) {
        super(name);
    }

    @Override
    public void move() {
        System.out.println(name + " は道路を走っています。");
    }

    @Override
    public void fuelUp() {
        System.out.println(name + ": ガソリンを給油します。");
    }
}
public class Bicycle extends Transport {
    public Bicycle(String name) {
        super(name);
    }

    @Override
    public void move() {
        System.out.println(name + " はペダルをこいでいます。");
    }
    // fuelUp はオーバーライドしない — 自転車に給油は不要
}
public class Train extends Transport {
    public Train(String name) {
        super(name);
    }

    @Override
    public void move() {
        System.out.println(name + " は線路の上を疾走しています。");
    }

    @Override
    public void fuelUp() {
        System.out.println(name + ": ディーゼルまたは電力で補給します。");
    }
}

抽象化の利用

import java.util.*;

public class TransportDemo {
    public static void main(String[] args) {
        List<Transport> vehicles = Arrays.asList(
            new Car("Toyota"),
            new Bicycle("Stels"),
            new Train("Sapsan")
        );

        for (Transport t : vehicles) {
            t.move();
            t.fuelUp();
            System.out.println("---");
        }
    }
}

実行結果:

Toyota は道路を走っています。
Toyota: ガソリンを給油します。
---
Stels はペダルをこいでいます。
Stels: 給油は不要です。
---
Sapsan は線路の上を疾走しています。
Sapsan: ディーゼルまたは電力で補給します。
---

利点:

  • タイプを判定せず、どの乗り物も同じように処理できる。
  • 新しい乗り物(例: 電動キックボード)を簡単に追加できる。

4. 例 3: グラフィックエディタ

課題

ミニ・グラフィックエディタを作成しているとします。そこには線分、楕円、多角形があり、いずれも「図形」です。図形は描画とサイズ変更ができますが、その実装は図形ごとに異なります。

抽象化: クラス Figure

public abstract class Figure {
    protected String color = "black";

    public abstract void draw();

    public abstract void resize(double factor);

    public void setColor(String color) {
        this.color = color;
    }
}

具体的な実装

public class Line extends Figure {
    private double length;

    public Line(double length) {
        this.length = length;
    }

    @Override
    public void draw() {
        System.out.println("長さ " + length + " の線を色 " + color + " で描画します");
    }

    @Override
    public void resize(double factor) {
        length *= factor;
        System.out.println("線の新しい長さ: " + length);
    }
}
public class Ellipse extends Figure {
    private double a, b;

    public Ellipse(double a, double b) {
        this.a = a;
        this.b = b;
    }

    @Override
    public void draw() {
        System.out.println("長径 " + a + " と短径 " + b + " の楕円を色 " + color + " で描画します");
    }

    @Override
    public void resize(double factor) {
        a *= factor;
        b *= factor;
        System.out.println("楕円の新しいサイズ: " + a + " x " + b);
    }
}
public class Polygon extends Figure {
    private int sides;

    public Polygon(int sides) {
        this.sides = sides;
    }

    @Override
    public void draw() {
        System.out.println("辺の数が " + sides + " の多角形を色 " + color + " で描画します");
    }

    @Override
    public void resize(double factor) {
        System.out.println("スケール係数 " + factor + " で辺の数が " + sides + " の多角形のサイズを変更します");
    }
}

抽象化の利用

import java.util.*;

public class EditorDemo {
    public static void main(String[] args) {
        List<Figure> figures = new ArrayList<>();
        figures.add(new Line(10));
        figures.add(new Ellipse(5, 3));
        figures.add(new Polygon(6));

        for (Figure f : figures) {
            f.setColor("green");
            f.draw();
            f.resize(2);
            System.out.println("---");
        }
    }
}

実行結果:

長さ 10.0 の線を色 green で描画します
線の新しい長さ: 20.0
---
長径 5.0 と短径 3.0 の楕円を色 green で描画します
楕円の新しいサイズ: 10.0 x 6.0
---
辺の数が 6 の多角形を色 green で描画します
スケール係数 2.0 で辺の数が 6 の多角形のサイズを変更します
---

利点:

  • すべての図形を同じリストに保持し、同じ流れで処理できる。
  • 新しい図形(例: 星形やハート)を容易に追加できる。
  • 共通メソッド(例: setColor() での色設定)は1回だけ実装すればよい。

5. 抽象化がコードを簡潔にする仕組み

上の各例には共通のパターンがあります:

  • 基底の抽象クラスが契約(オブジェクトが何をできるか)を定義する。
  • 具体的なサブクラスが詳細を実装する。
  • 抽象に対して書かれたコードはオブジェクトの型に依存せず、システムを柔軟かつ拡張可能にする。

アプローチ比較表

抽象化なし(if-else 抽象化あり(OOP)
型ごとの条件分岐が多い 新しい型 — コードを変更する
ロジックの重複 ロジックは1か所に集約
拡張が難しい 追加が容易
テストしづらい 実装の差し替えが容易

6. 抽象設計でよくある誤り

よくある誤り1: 抽象化のための抽象化。
オブジェクトが1種類しかなく拡張予定がないなら、抽象クラスは不要です。理由なくコードを複雑にしないでください。

よくある誤り2: 抽象が抽象的すぎる。
基底クラスがあいまいすぎると、サブクラス同士が名前以外の共通点を持たなくなります。例えば、あらゆるものを表す「Thing」のような抽象です。これは保守や理解を難しくします。

よくある誤り3: サブクラス間のコード重複。
すべてのサブクラスで同じ実装になっているメソッドは、基底クラスに移して抽象でなくすべきです。

よくある誤り4: 「一般から特殊へ」の原則違反。
抽象クラスに特定のサブクラスだけが必要とする詳細が現れたら、その抽象の切り方は誤りです。

よくある誤り5: 抽象メソッドの実装漏れ。
サブクラスで抽象メソッドをすべて実装しないと、コンパイラはそのクラスも抽象クラスにするよう要求します。ときにこれは予想外です :)

1
タスク
JAVA 25 SELF, レベル 19, レッスン 3
ロック未解除
グラフィカルアプリケーションでの面積計算 🎨
グラフィカルアプリケーションでの面積計算 🎨
1
タスク
JAVA 25 SELF, レベル 19, レッスン 3
ロック未解除
汎用メッセージ配信システム 📧💬
汎用メッセージ配信システム 📧💬
1
タスク
JAVA 25 SELF, レベル 19, レッスン 3
ロック未解除
企業の車両管理 🚚🚲
企業の車両管理 🚚🚲
1
タスク
JAVA 25 SELF, レベル 19, レッスン 3
ロック未解除
小売店向け注文処理モジュール 🛒
小売店向け注文処理モジュール 🛒
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION