SOLID原則とReact+TypeScript
オブジェクト指向設計の 5 つの基本原則、SOLID 原則を React+TypeScript のサンプルで解説。
S - 単一責務の原則 (Single Responsibility Principle)
説明:
一つのクラスや関数は、一つの責務だけを持つべき
「単一責務の原則」は、その名の通り、一つのクラスや関数が一つの「役割」や「責務」だけを持つべきだという考え方です。
これによって、コードが読みやすくなり、バグの発生を減少させることができます。
React+TypeScript の例:
// ユーザーデータを取得する関数
const fetchUserData = (userId: string): Promise<User> => {
// APIからデータを取得するロジック
};
// ユーザーのプロフィールを表示するコンポーネント
const UserProfile: React.FC<{ user: User }> = ({ user }) => (
<div>
<h2>{user.name}</h2>
<p>{user.bio}</p>
</div>
);
1. ユーザーデータを取得する関数: fetchUserData
この関数の役割は、指定されたユーザー ID に基づいて API からユーザーデータを取得することです。
データの取得以外の責務、例えばデータの表示や加工、はこの関数の役割ではありません。
const fetchUserData = (userId: string): Promise<User> => {
// APIからデータを取得するロジック
};
2. ユーザーのプロフィールを表示するコンポーネント:UserProfile
このコンポーネントの役割は、受け取ったユーザーデータを表示することです。
データの取得や保存などの操作はこのコンポーネントの役割ではありません。
const UserProfile: React.FC<{ user: User }> = ({ user }) => (
<div>
<h2>{user.name}</h2>
<p>{user.bio}</p>
</div>
);
この 2 つの例から、各部分が自分の役割をきちんと果たしており、混在していないことがわかります。
このように役割を明確に分けることで、後でコードを見直す際やバグを修正する際にも、
どこを修正すればよいのかがはっきりと分かるようになります。
O - オープン/クローズドの原則 (Open/Closed Principle)
説明:
「オープン/クローズドの原則」とは、ソフトウェアの部品(クラスや関数など)は、
新しい機能の追加には対応できるように「オープン(開いている)」であるべきだが、
既存のコードの変更には「クローズド(閉じている)」であるべきだ、という考え方です。
React+TypeScript の例:
type ButtonProps = {
onClick: () => void;
children: React.ReactNode;
};
const PrimaryButton: React.FC<ButtonProps> = (props) => (
<button
onClick={props.onClick}
style={{ backgroundColor: "blue", color: "white" }}
>
{props.children}
</button>
);
const SecondaryButton: React.FC<ButtonProps> = (props) => (
<button
onClick={props.onClick}
style={{ backgroundColor: "gray", color: "white" }}
>
{props.children}
</button>
);
1. ボタンのプロパティ型: ButtonProps
すべてのボタンが持つ共通のプロパティを定義しています。これにより、新しいボタンタイプを追加する際にも、この型定義を使用することで一貫性を持たせることができます。
type ButtonProps = {
onClick: () => void;
children: React.ReactNode;
};
2. プライマリボタン: PrimaryButton
特定のスタイル(青背景)でボタンを表示するコンポーネントです。
const PrimaryButton: React.FC<ButtonProps> = (props) => (
<button
onClick={props.onClick}
style={{ backgroundColor: "blue", color: "white" }}
>
{props.children}
</button>
);
3. セカンダリボタン: SecondaryButton
異なるスタイル(灰色背景)でボタンを表示するコンポーネントです。
const SecondaryButton: React.FC<ButtonProps> = (props) => (
<button
onClick={props.onClick}
style={{ backgroundColor: "gray", color: "white" }}
>
{props.children}
</button>
);
上記のように、新しいボタンのスタイルや動作を追加する場合、
既存の PrimaryButton や SecondaryButton を変更することなく、
新しいコンポーネントを追加するだけで対応可能です。
このようにして、既存のコードは変更せずに新しい機能を追加することができるのが
「オープン/クローズドの原則」のメリットです。
L - リスコフの置換原則 (Liskov Substitution Principle)
「リスコフの置換原則」とは、あるクラスを継承したサブクラスが存在する場合、
そのサブクラスのインスタンスは、基底クラスのインスタンスとして振る舞うことができる、
という考え方です。
具体的には、サブクラスは、基底クラスの重要な振る舞いや性質を変更すべきではありません。
React+TypeScript の例:
interface Bird {
fly: () => void;
}
class Sparrow implements Bird {
fly() {
console.log("Sparrow is flying");
}
}
class Ostrich {
run() {
console.log("Ostrich is running");
}
}
1. 鳥を表すインターフェース: Bird
飛ぶ動作を持つ鳥を定義しています。
interface Bird {
fly: () => void;
}
2. スズメ: Sparrow
スズメは Bird インターフェースを実装しており、飛ぶ動作を持っています。
class Sparrow implements Bird {
fly() {
console.log("Sparrow is flying");
}
}
3. ダチョウ: Ostrich
ダチョウは Bird インターフェースを実装していませんが、
代わりに走る動作を持っています。
class Ostrich {
run() {
console.log("Ostrich is running");
}
}
この例で言うと、Ostrich は Bird インターフェースを実装していないため、
この原則に違反しているわけではありません。
ただし、もし Ostrich が Bird を実装し、fly メソッドが何もしないか、エラーを発生させるような実装になっていた場合、
それはリスコフの置換原則に違反していることになります。
I - インターフェース分離の原則 (Interface Segregation Principle)
説明:
クラスは、不必要なインターフェイスを実装するべきではない
React+TypeScript の例:
interface Worker {
work: () => void;
}
interface Eater {
eat: () => void;
}
class Human implements Worker, Eater {
work() {
console.log("Human is working");
}
eat() {
console.log("Human is eating");
}
}
class Robot implements Worker {
work() {
console.log("Robot is working");
}
}
D - 依存性逆転の原則 (Dependency Inversion Principle)
説明:
高レベルのモジュールは、低レベルのモジュールに依存すべきではなく、両方とも抽象に依存すべき
React+TypeScript の例:
interface Database {
save: (data: string) => void;
}
class MongoDB implements Database {
save(data: string) {
console.log(`Saving ${data} to MongoDB`);
}
}
class App {
constructor(private database: Database) {}
saveData(data: string) {
this.database.save(data);
}
}
以上、SOLID 原則を React+TypeScript サンプル付きで解説してみました。
日常のコーディングに適用することで、より品質の高いコードを書くことの糧になってもらえたら幸いです。
Discussion