0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

抽象化とは(学習メモ)

0
Posted at

はじめに

抽象化とは、細かい違いをいったん無視して、共通する本質だけを取り出すこと

今回次のような関係を例に抽象化について学んだことをまとめる

社員
├─ エンジニア
└─ 営業

エンジニアも営業も、職種は違う。
でも、どちらも会社に所属する「社員」。

つまり、共通部分だけを見るとこうなる。

社員番号を持っている
勤怠を入力する

この共通部分を 社員 としてまとめるのが抽象化。


目的

処理の目的はこれ。

エンジニア、営業などの具体的な職種に共通する要素を「社員」としてまとめる。
職種ごとの差分は個別クラスに書き、共通処理は親クラスやinterfaceに集約する。

悪い例:共通部分を各クラスに重複して書く

class Engineer {
  private employeeNumber: string;

  constructor(employeeNumber: string) {
    this.employeeNumber = employeeNumber;
  }

  inputAttendance(): void {
    console.log(`${this.employeeNumber} が勤怠を入力しました`);
  }

  programming(): void {
    console.log("プログラミングをします");
  }
}

class Sales {
  private employeeNumber: string;

  constructor(employeeNumber: string) {
    this.employeeNumber = employeeNumber;
  }

  inputAttendance(): void {
    console.log(`${this.employeeNumber} が勤怠を入力しました`);
  }

  salesActivity(): void {
    console.log("営業活動をします");
  }
}

何が悪いのか

EngineerSales の両方に、同じものがある。

private employeeNumber: string;

inputAttendance(): void {
  console.log(`${this.employeeNumber} が勤怠を入力しました`);
}

これは重複。

現場で困るのは、勤怠入力の仕様が変わったとき。

例えば、勤怠入力時にログを追加したい場合、両方を修正する必要がある。

Engineerを修正
Salesを修正
今後Managerが増えたらManagerも修正

クラスが増えるほど修正漏れが起きる。


良い例:共通部分を Employee に抽象化する

// 抽象化:エンジニア・営業に共通する「社員」という概念を作る
abstract class Employee {
  // 抽象化:全社員に共通する属性
  protected readonly employeeNumber: string;

  constructor(employeeNumber: string) {
    if (!employeeNumber) {
      throw new Error("社員番号は必須");
    }

    this.employeeNumber = employeeNumber;
  }

  // 抽象化:全社員に共通する振る舞い
  inputAttendance(): void {
    console.log(`${this.employeeNumber} が勤怠を入力しました`);
  }

  // 抽象化:職種ごとの仕事内容は共通化できないので、子クラスに任せる
  abstract work(): void;
}

// 具体化:エンジニア固有の振る舞いを書く
class Engineer extends Employee {
  work(): void {
    this.programming();
  }

  private programming(): void {
    console.log("プログラミングをします");
  }
}

// 具体化:営業固有の振る舞いを書く
class Sales extends Employee {
  work(): void {
    this.salesActivity();
  }

  private salesActivity(): void {
    console.log("営業活動をします");
  }
}

抽象化しているコードはどこか

ここ。
これは、EngineerSales の共通部分を Employee としてまとめている。

abstract class Employee {

さらにここ。

protected readonly employeeNumber: string;

これは、全社員に共通する「社員番号」を親クラスに持たせている。

さらにここ。

inputAttendance(): void {
  console.log(`${this.employeeNumber} が勤怠を入力しました`);
}

これは、全社員に共通する「勤怠を入力する」という処理を親クラスにまとめている。

さらにここ。

abstract work(): void;

これは、全社員は何かしら働くが、具体的な仕事内容は職種ごとに違うため、子クラスに任せている。


どこに何を書くべきか

Employee に書くもの

Employee には、全社員に共通するものを書く。

abstract class Employee {
  protected readonly employeeNumber: string;

  inputAttendance(): void {
    console.log(`${this.employeeNumber} が勤怠を入力しました`);
  }

  abstract work(): void;
}

書いてよいもの。

社員番号
氏名
勤怠入力
社員として共通するチェック

理由は、エンジニアでも営業でも共通して使うから。


Engineer に書くもの

Engineer には、エンジニア固有のものを書く。

class Engineer extends Employee {
  work(): void {
    this.programming();
  }

  private programming(): void {
    console.log("プログラミングをします");
  }
}

書いてよいもの。

プログラミングする
コードレビューする
設計する
技術調査する

理由は、営業には関係ないから。


Sales に書くもの

Sales には、営業固有のものを書く。

class Sales extends Employee {
  work(): void {
    this.salesActivity();
  }

  private salesActivity(): void {
    console.log("営業活動をします");
  }
}

書いてよいもの。

営業活動をする
顧客訪問する
見積もりを提案する

理由は、エンジニアには関係ないから。


良し悪しの判断基準

良い抽象化

良い抽象化は、共通点だけを親に置く

abstract class Employee {
  protected readonly employeeNumber: string;

  inputAttendance(): void {
    console.log(`${this.employeeNumber} が勤怠を入力しました`);
  }

  abstract work(): void;
}

これは良い。

理由は、employeeNumberinputAttendance() は全社員に共通するから。


悪い抽象化

悪い例。

abstract class Employee {
  protected readonly employeeNumber: string;

  inputAttendance(): void {
    console.log(`${this.employeeNumber} が勤怠を入力しました`);
  }

  programming(): void {
    console.log("プログラミングをします");
  }

  salesActivity(): void {
    console.log("営業活動をします");
  }
}

これは悪い。

理由は、programming() は営業には不要で、salesActivity() はエンジニアには不要だから。

Employee に何でも詰め込むと、親クラスが不自然になる。


何を良しとするのか

抽象化で良いと判断する基準はこれ。

判断ポイント 良い状態
共通する属性か 親に置く
共通する処理か 親に置く
一部の職種だけの処理か 子クラスに置く
親クラス名が自然か 自然
子クラスが不要なメソッドを持たされていないか 持たされていない

Javaで書く場合

// 抽象化:エンジニア・営業に共通する「社員」という概念
public abstract class Employee {

    // 抽象化:全社員に共通する属性
    protected final String employeeNumber;

    public Employee(String employeeNumber) {
        if (employeeNumber == null || employeeNumber.isBlank()) {
            throw new IllegalArgumentException("社員番号は必須");
        }

        this.employeeNumber = employeeNumber;
    }

    // 抽象化:全社員に共通する振る舞い
    public void inputAttendance() {
        System.out.println(employeeNumber + " が勤怠を入力しました");
    }

    // 抽象化:働くこと自体は共通。ただし具体的な仕事内容は子クラスに任せる
    public abstract void work();
}
// 具体化:エンジニア固有の処理
public class Engineer extends Employee {

    public Engineer(String employeeNumber) {
        super(employeeNumber);
    }

    @Override
    public void work() {
        programming();
    }

    private void programming() {
        System.out.println("プログラミングをします");
    }
}
// 具体化:営業固有の処理
public class Sales extends Employee {

    public Sales(String employeeNumber) {
        super(employeeNumber);
    }

    @Override
    public void work() {
        salesActivity();
    }

    private void salesActivity() {
        System.out.println("営業活動をします");
    }
}

現場で意識すること

現場では、抽象化は「きれいに見せるため」にやるものではない。

目的はこれ。

重複を減らす
変更箇所を減らす
共通ルールを1か所に集める
差分だけを子クラスに書く

例えば勤怠入力の仕様が変わったら、ここだけ直せばよい。

inputAttendance(): void {
  console.log(`${this.employeeNumber} が勤怠を入力しました`);
}

EngineerSales を直さなくてよい。

これが抽象化のメリット。


やってはいけないこと

1. 共通していないものを親に置く

abstract class Employee {
  programming(): void {
    console.log("プログラミングをします");
  }
}

これはダメ。

営業も Employee なので、営業まで programming() を持ってしまう。


2. 何でも親クラスに集める

abstract class Employee {
  inputAttendance(): void {}
  programming(): void {}
  salesActivity(): void {}
  accounting(): void {}
  manageTeam(): void {}
}

これはダメ。

親クラスが肥大化して、何のクラスなのかわからなくなる。


3. 名前だけ抽象的で中身がバラバラ

abstract class BaseClass {
}

BaseClass のような名前は避けた方がよい。

何を表しているクラスなのかわからない。

良い名前はこれ。

Employee
User
Order
Payment
Notification

業務上の意味がある名前にする。


まとめ

抽象化とは、共通点を抜き出して、違いを隠すこと

今回の例なら、

エンジニア、営業
↓
共通点を見る
↓
社員番号を持つ
勤怠を入力する
↓
Employee にまとめる

同じコードを書くときは、こう考える。

これは全員に共通するか?
一部だけの処理ではないか?
親クラスに置くことで修正箇所は減るか?
子クラスが不要な機能を持たされていないか?

結論としては、以下。

共通するものは親クラスへ
違うものは子クラスへ
共通していないものを無理に親へ入れない

これが抽象化の基本だと学んだ

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?