1
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?

個人的アウトプットカレンダーAdvent Calendar 2024

Day 17

【JavaScript】関数型とオブジェクト指向

Last updated at Posted at 2024-12-17

今回はJavaScriptで関数型プログラミングオブジェクト指向プログラミングを比較してみました。

関数型プログラミング (Functional Programming)とは

関数型プログラミングとは何でしょうか。

Wikipediaには下記のように記載されています。

関数型プログラミングは、関数を主軸にしたプログラミングを行うスタイルである。ここでの関数は、数学的なものを指し、引数の値が定まれば結果も定まるという参照透過性を持つものである。

参照透過性とは、数学的な関数と同じように同じ値を返す式を与えたら必ず同じ値を返すような性質である。

同じ入力に対して、常に同じ出力を返すような関数を使用します。

特徴

関数型プログラミングは下記のような特徴を用いて行います。

純粋関数

純粋関数とは同じ入力に対して常に同じ出力を返す関数です。

function add(a, b) {
  return a + b; // 入力に依存し、外部状態を変更しない
}

不変性

基のデータを変更せずに新しいデータ構造を生成します。

const originalArray = [1, 2, 3];
const newArray = [...originalArray, 4]; // 元の配列は変更されない

高階関数

mapfilterreduceなど、関数を引数として受け取る、または関数を返す関数を使用します。

const numbers = [1, 2, 3];
const doubled = numbers.map(num => num * 2); // [2, 4, 6]

再帰関数

関数の中で自分自身を呼び出す関数を使用します。

const sum = ([x, ...rest]) => x === undefined ? 0 : x + sum(rest);

関数の合成

複数の単純な関数を組み合わせて、新たな関数を作成します。

const addOne = x => x + 1;
const double = x => x * 2;

const compose = (f, g) => x => f(g(x));

const addOneThenDouble = compose(double, addOne);

console.log(addOneThenDouble(3)); // 出力: 8

オブジェクト指向プログラミング (Object-Oriented Programming) とは

オブジェクト指向プログラミングとは何でしょうか。

Wikipediaには下記のように記載されています。

OOPでは、相互に作用するオブジェクトを組み合わせてプログラムを設計する。

オブジェクト指向プログラミングでは、
プログラムをオブジェクトの集合として構造化する手法です。

JavaScriptはそんなオブジェクト中心のオブジェクト指向言語です。

特徴

オブジェクト指向プログラミングは下記のような特徴を用いて行います。

クラスとインスタンス

クラスは、オブジェクトを生成するための設計図やひな形として機能します。

class Car {
  constructor(brand, model) {
    this.brand = brand;
    this.model = model;
  }

  getInfo() {
    return `${this.brand} ${this.model}`;
  }
}

インスタンスは、クラスから生成された具体的なオブジェクトです。
new を使用して作成します。

const myCar = new Car("Honda", "Civic");
console.log(myCar.getInfo()); // "Honda Civic"

継承

継承は既存のクラス(親クラス)の特性を新しいクラス(子クラス)に引き継ぐメカニズムです。
extends を使用して継承を実装します。

継承を使用することでコードの再利用性が向上し、
階層構造を持つオブジェクトを作成することができます。

class ElectricCar extends Car {
  constructor(brand, model, batteryCapacity) {
    super(brand, model);
    this.batteryCapacity = batteryCapacity;
  }

  getInfo() {
    return `${super.getInfo()}, Battery: ${this.batteryCapacity}kWh`;
  }
}

const myElectricCar = new ElectricCar("Tesla", "Model 3", 75);
console.log(myElectricCar.getInfo()); // "Tesla Model 3, Battery: 75kWh"

カプセル化

カプセル化はデータと操作をオブジェクト内にまとめ、
外部からの直接のアクセスを制限する概念です。

カプセル化には下記のようなメリットがあります。

  • データの整合性の保護
  • 外部からの不正なアクセス防止
  • 内部実装の隠蔽
class BankAccount {
  #balance = 0;

  deposit(amount) {
    if (amount > 0) {
      this.#balance += amount;
    }
  }

  getBalance() {
    return this.#balance;
  }
}

const account = new BankAccount();
account.deposit(100);
console.log(account.getBalance()); // 100
console.log(account.#balance); // エラー: プライベートフィールドにアクセスできない

注意
#」はES2022で追加された private を意味する記号です。
ES2022以前は慣例的に「_ (アンダースコア)」が使用されていました。

比較

ではここまで学んだ FPOOP の違いを見てみましょう。

特徴 関数型プログラミング (FP) オブジェクト指向プログラミング (OOP)
基本単位 関数 オブジェクト・クラス
データ 不変 状態を持ち、変更可能
振る舞い 関数の組み合わせ、純粋関数 オブジェクトがメソッドを通じて状態を変更
副作用 副作用を避ける 状態変更が一般的(副作用あり)
特徴 高階関数、再帰、関数合成 継承、カプセル化

実例

具体的な例でオブジェクト指向 (OOP)関数型プログラミング (FP) のアプローチを比較してみます。

ChatGPTに下記の内容でコードを生成してもらいました。

■処理内容
あるアプリケーションで、ユーザーのリストから年齢が20歳以上のユーザーをフィルタリングし、その名前を大文字に変換する処理を行います。

関数型プログラミング
const users = [
  { name: 'alice', age: 18 },
  { name: 'bob', age: 25 },
  { name: 'charlie', age: 30 }
];

// 成人をフィルタリングする関数
const filterAdults = users => users.filter(user => user.age >= 20);

// 名前を大文字に変換する関数
const capitalizeNames = users => 
  users.map(user => ({ ...user, name: user.name.toUpperCase() }));

// パイプライン処理で関数を組み合わせる
const processUsers = users => capitalizeNames(filterAdults(users));

const result = processUsers(users);

console.log(result);
オブジェクト指向プログラミング
class User {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

class UserList {
  constructor(users) {
    this.users = users;
  }

  filterAdults() {
    this.users = this.users.filter(user => user.age >= 20);
  }

  capitalizeNames() {
    this.users = this.users.map(user => {
      return new User(user.name.toUpperCase(), user.age);
    });
  }

  getUsers() {
    return this.users;
  }
}

// 使用例
const users = [
  new User('alice', 18),
  new User('bob', 25),
  new User('charlie', 30)
];

const userList = new UserList(users);
userList.filterAdults();        // 成人のみをフィルタリング
userList.capitalizeNames();     // 名前を大文字に変換

console.log(userList.getUsers());

同じ内容でもかなり処理が異なりますね。

おわりに

それぞれにそれぞれの良さがあり、
どちらか一方だけを使用するというよりも、
適した処理に適した方法で採用するのが良いのかなと思いました。

それでは。

参考文献

1
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
1
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?