ゴール:「どこまで外に見せるか」を自分でコントロールできるようになる
public / private / protected は、
「クラスの中身をどこまで外に見せるか」を決めるスイッチです。
ここをなんとなくで書くと、
- 触ってほしくないところを外からいじられる
- クラスの使い方がバラバラになる
- 後から仕様変更しづらくなる
みたいな“じわじわ効く痛み”が出てきます。
逆に、ここを意識して設計できると、
「このクラスは、ここだけ見て使ってね」
という“きれいな顔”を作れるようになります。
順番に、かみ砕いていきます。
public:みんなに見せる「表の顔」
public は「どこからでもアクセスOK」
public は「外から自由に触っていいよ」という意味です。
何も書かなかった場合も public 扱いになります。
class User {
public id: number;
public name: string;
public constructor(id: number, name: string) {
this.id = id;
this.name = name;
}
public greet(): void {
console.log(`こんにちは、${this.name}です`);
}
}
const u = new User(1, "Taro");
console.log(u.id); // OK
u.name = "Jiro"; // OK
u.greet(); // OK
TypeScript「このクラスを使う人に、ここは自由に触ってほしい」
という場所には public を付けます。
「とりあえず全部 public」は危険
初心者がやりがちなのが、
class User {
id: number;
name: string;
isDeleted: boolean;
// いろんなメソッド…
}
TypeScriptとりあえず何も付けずに書いて、
結果的に「全部 public」になってしまうパターンです。
これをやると、
isDeletedを外から勝手に書き換えられる- 内部の都合で持っているメソッドまで外から呼べてしまう
など、「クラスの中身が丸裸」になります。
public は「見せたいものだけ」に絞る
という感覚を持てるようになると、一気に設計が良くなります。
private:クラスの中だけで使う「裏側の仕組み」
private は「クラスの外からは触っちゃダメ」
private は「このクラスの中からしか触れない」という意味です。
class User {
constructor(
public name: string,
private passwordHash: string
) {}
public checkPassword(raw: string): boolean {
const hash = this.hash(raw);
return hash === this.passwordHash;
}
private hash(raw: string): string {
// 実際にはもっとちゃんとしたハッシュ処理
return `hashed:${raw}`;
}
}
const u = new User("Taro", "hashed:secret");
// u.passwordHash; // エラー:private
// u.hash("secret"); // エラー:private
u.checkPassword("secret"); // OK
TypeScriptここでのポイントは、
passwordHashは外から見えないhashメソッドも外から呼べない- 外からできるのは
checkPasswordを呼ぶことだけ
という状態になっていることです。
「外から触らせたくないもの」は全部 private にする
クラスの中には、
- ビジネスルール上、外から変えられたら困る値
- 内部実装の都合で存在するだけのメソッド
- 将来、実装を変えたいかもしれない部分
がたくさん出てきます。
そういうものは、積極的に private にします。
class Order {
private items: { name: string; price: number }[] = [];
public addItem(name: string, price: number): void {
this.items.push({ name, price });
}
public getTotal(): number {
return this.items.reduce((sum, item) => sum + item.price, 0);
}
}
TypeScriptもし items が public だったら、
order.items.push({ name: "変な商品", price: -9999 });
TypeScriptみたいなことが外からできてしまいます。
「このプロパティやメソッドを外から触られたら困るか?」
と自分に聞いて、「困る」と思ったら private にする、
これだけでも設計がかなり締まります。
protected:継承したクラスには見せる「家族だけの共有」
protected は「外からはダメ、子クラスからはOK」
protected は、
「クラスの外からは見えないけど、継承したクラスからは見える」
という中間的な立場です。
class Animal {
constructor(
protected name: string
) {}
protected move(distance: number): void {
console.log(`${this.name} moved ${distance}m`);
}
}
class Dog extends Animal {
public bark(): void {
console.log(`${this.name} がワンと鳴いた`); // OK(protected)
this.move(5); // OK(protected)
}
}
const d = new Dog("Pochi");
// d.name; // エラー:外からは見えない
// d.move(5); // エラー:外からは見えない
d.bark(); // OK
TypeScriptイメージとしては、
- public:誰でも見ていい
- protected:家族(継承したクラス)だけ見ていい
- private:本人(そのクラス自身)しか見ちゃダメ
という感じです。
継承前提のクラスで効いてくる
protected が本領発揮するのは、
「継承されることを前提にしたクラス」です。
abstract class Shape {
constructor(
protected color: string
) {}
abstract area(): number;
protected logArea(): void {
console.log(`色: ${this.color}, 面積: ${this.area()}`);
}
}
class Circle extends Shape {
constructor(
color: string,
private radius: number
) {
super(color);
}
area(): number {
return this.radius * this.radius * Math.PI;
}
public debug(): void {
this.logArea(); // protected メソッドを子クラスから利用
}
}
TypeScriptlogArea は外から直接呼ばせたくないけれど、
子クラスからは使わせたい、というときに protected が使えます。
どう使い分ける?実務でのざっくり指針
まず「全部 private だと思って」必要なものだけ public にする
一番おすすめの考え方はこれです。
「最初は全部 private だと思って、
外から必要なものだけ public に“昇格”させる」
いきなり public でベタッと書くのではなく、
- このクラスを使う人は、どこに触れれば用が足りる?
- 逆に、触れてほしくないところはどこ?
を考えて、「表の顔(public)」を絞り込んでいきます。
class User {
// 本当は全部 private から考える
private id: number;
private name: string;
constructor(id: number, name: string) {
this.id = id;
this.name = name;
}
// 外から必要な操作だけ public にする
public rename(newName: string): void {
this.name = newName;
}
public getDisplayName(): string {
return `ID:${this.id} / ${this.name}`;
}
}
TypeScriptこうすると、
- 外からできることが明確になる
- 内部の実装を変えても、public な部分さえ守れば壊れない
という「変更に強いクラス」になっていきます。
「テストしたいから public にする」は危険
ありがちな罠がこれです。
「この内部メソッドもテストしたいから、とりあえず public にしちゃおう」
class Something {
public internalLogic(): number {
// 本当は外から呼ばせたくない
}
}
TypeScriptテストのために public を増やすと、
- 本来隠したかった内部実装が API として露出する
- そのメソッドを他のコードからも呼ばれ始める
- 後から変えづらくなる
という悪循環に入ります。
テストしたい内部ロジックは、
別の純粋な関数としてクラスの外に切り出す
という手もあります。
function calcSomething(x: number): number {
// テストしやすい純粋関数
}
class Something {
public do(): number {
return calcSomething(42);
}
}
TypeScriptアクセス修飾子は「テストの都合」ではなく、
「クラスの設計としてどうあるべきか」で決めるのが大事です。
まとめ:public / private / protected を自分の言葉で言うと
最後に、あなた自身の言葉でこう整理してみてください。
- public:
「このクラスを使う人に“ここは触っていいよ”と約束する場所」
クラスの“顔”になる部分。最小限に絞る。 - private:
「クラスの中だけで完結させたい裏側の仕組み」
外から触られたくない状態・メソッドは全部ここに閉じ込める。 - protected:
「外からは隠したいけど、子クラスには見せたい共有部分」
継承前提の設計で効いてくる。
コードを書いていて、
プロパティやメソッドを1つ追加するときに、
毎回一瞬だけでいいので、
「これは本当に public でいい?」
「private にできない?」
「継承するなら protected のほうが自然?」
と自分に問いかけてみてください。
その小さな習慣が、
クラスを「なんでも public な箱」から、
“外から見たときにスッと理解できる、きれいなインターフェース”
へと育てていきます。
