この記事を書いた理由
前回の記事の更新で、「オブジェクト指向」とはなんなのか?
ということをメインで記事を書きました。今回はその続きで、「class」とはなんなのか?という点を深掘りしていきたいと思います。
↓前回記事
前提
関数型のReactv18 ~ が開発経験の主体のため、classを使用して宣言したことが無く、どういう用途で使われるのかがピンときていません。
たぶん、関数型の考え方に慣れすぎている のが大きいのかなと。
それだけじゃなくて、React が class をあまり推奨していないことも影響している気も....
なのでこの記事を書きながら順次理解していきたいと思います。
classとは
Classの意味 = 「分類する,整理する,選り分ける」こと。
Class はオブジェクトを「分類(整理)」するための設計図みたいなもの。とのこと。
オブジェクト指向プログラミング(OOP)の概念を実現するための構文。それがclass。
classと関数の違いについて
class は状態を持つのが前提、関数は状態を外に出せる
前置き
コンストラクター (constructor) は new でインスタンスを生成したタイミングで実行される。
サンプルコード
export class Sound {
constructor(private animal: string, private name: string) {
console.log(`${name} (${animal}) が鳴くよ`);
}
makeSound() {
if (this.animal === "犬") {
console.log(`${this.name} は ワンワンワン`);
} else if (this.animal === "猫") {
console.log(`${this.name} は にゃーにゃーにゃー`);
} else {
console.log(`${this.name} は 知らない音`);
}
}
}
// インスタンス生成時に `animal` と `name` を渡す
const myDog = new Sound("犬", "ポチ"); // "ポチ (犬) が鳴くよ"
myDog.makeSound(); // "ポチ は ワンワンワン"
const myCat = new Sound("猫", "ミケ"); // "ミケ (猫) が鳴くよ"
myCat.makeSound(); // "ミケ は にゃーにゃーにゃー"
このコードから読み取る特徴。
- private をつけると クラス内でしか使えない変数 になる(外部からのアクセス不可)
- private をつけると コンストラクタの引数をそのまま this.〇〇 に代入する省略記法 になる
- newでインスタンス化された時に含まれる引数は constructor に渡され、class内でのthis はその constructor の値を参照する
- この記載方法は基本的にnew時の引数が必須となってしまっているが、コンストラクタへの引数のキー名でオプショナル化が可能、。
- private repeat: number = 1などと記載してデフォルト引数を用意することも可能。
呼び出し時にメソッドに対してのみ、引数を渡すことも可能。
export class Sound {
constructor(private animal: string) {}
makeSound(times: number) { // ✅ メソッドに引数 `times` を追加
let sound = "";
if (this.animal === "犬") {
sound = "ワンワンワン";
} else if (this.animal === "猫") {
sound = "にゃーにゃーにゃー";
} else {
sound = "知らない音";
}
for (let i = 0; i < times; i++) { // ✅ メソッドの引数 `times` を使う
console.log(sound);
}
}
}
// インスタンス生成時は `animal` だけ指定
const myDog = new Sound("犬");
// メソッド `makeSound()` に回数を指定
myDog.makeSound(3);
/*
ワンワンワン
ワンワンワン
ワンワンワン
*/
myDog.makeSound(1);
/*
ワンワンワン
*/
このコードから読み取る特徴。
- constractorに何も処理が含まれない場合であっても、{}自体は省略することができない。(構文エラーになる)
- メソッドに対してのみ、引数を渡すことも可能である。
chatGPTを使って問題を作成してみた。
出力された問題は以下
- User クラスを作成し、「名前」「年齢」「メールアドレス」 をプロパティとして持たせること。
クラスのコンストラクタで上記の情報を設定できるようにする。 - introduce() メソッドを作成し、「こんにちは、私は〇〇です。〇〇歳です。」と自己紹介する関数を定義すること。
- isAdult() メソッドを作成し、年齢が 18 歳以上なら true を、それ未満なら false を返すこと。
- AdminUser クラスを作成し、User を継承すること。管理者権限 (isAdmin: boolean) を追加する。
AdminUser では introduce() メソッドをオーバーライドし、「管理者の〇〇です。〇〇歳です。」と表示されるようにする。
※ まずは動作確認込み、問題なく動いてくれた自分で書いたコードです
export class User {
constructor(
private name: string,
private age: number,
private address: string
) {
this.name = name;
this.age = age;
this.address = address;
}
introduce() {
return `こんにちは、私は${this.name}です。${this.age}歳です`;
}
isAdult() {
return this.age >= 18;
}
}
export class AdminUser extends User {
constructor(
private name: string,
private age: number,
private address: string,
private isAdmin: boolean
) {
super(name, age, address);
}
introduce(): string {
return `管理者の${this.name}です。${this.age}際です`;
}
}
実行結果(動作上は問題なし)
import { AdminUser, User } from "./class";
import "./styles.css";
export default function App() {
const user = new User("太郎", 40, "tokyo");
console.log(user.introduce()); //こんにちは、私は太郎です。40歳です
console.log(user.isAdult()); // true
const isAdmin = new AdminUser("太郎", 40, "tokyo", true);
console.log(isAdmin.introduce()); //管理者の太郎です。40際です
const notAdmin = new AdminUser("太郎", 40, "tokyo", false);
console.log(notAdmin.introduce()); //管理者ではありません
return <div className="App"></div>;
}
chatGPTレビューを受けて修正した内容
export class User {
constructor(
private name: string,
private age: number,
private address: string
) {}
// コンストラクターからのnameを直接取得するゲッター関数を用意
getName(): string {
return this.name;
}
// コンストラクターからのageを直接取得するゲッター関数を用意
getAge(): number {
return this.age;
}
// コンストラクターから直接参照せずにgetter関数を使用したメソッド
introduce() {
return `こんにちは、私は${this.getName()}です。${this.getAge()}歳です`;
}
isAdult() {
return this.age >= 18;
}
}
// 新しくAdminUserクラスを定義して、extendsでUserを継承する。
export class AdminUser extends User {
constructor(
name: string,
age: number,
address: string,
private isAdmin: boolean
) {
super(name, age, address);
}
getIsAdmin() {
return this.isAdmin;
}
introduce(): string {
if (this.getIsAdmin())
return `管理者の${this.getName()}です。${this.getAge()}際です`;
return "管理者ではありません";
}
}
super()とは
- 親クラスのコンストラクタを(extends先)、「継承したクラスでも」実行するためのもの。
- 親クラス (User) に定義されたプロパティ(コンストラクタの引数として受け取って、this.〇〇に保存されたもの)を子クラス (AdminUser) でも使えるようになる!
- 継承時に、superを書き忘れていると「親クラスのコンストラクタを呼んでない」 というエラーが発生する。
- ゲッター関数を親側で定義していても、superを書き忘れると呼び出し時にundefindになる。
この記事を書いてわかったこと
- コンストラクター (constructor) は new でインスタンスを生成したタイミングで実行される。
- コンストラクターの引数を直接取得したい場合はgetプロパティ名としてゲッター関数を用意すると良い。
- コンストラクターの引数はprivateとしている場合はthisに含まれるため良い。
- メソッドなので関数と同様にreturnを使用する
- ゲッター関数とはなにかということ
- ただあまりクラスのインスタンス化した時に直接引数を渡すケースよりはメソッドの引数にすることのほうがケースとしては多そう。
- さまざまなオブジェクト指向言語で使われる概念が多いので、classの構造が理解できただけでも今後に行きそうな感じはかなりした。
最後に
割と書いてて、今後に生きる学習になったなぁと思います、ありがとうございました。