継承 vs 合成: コード再利用への 2 つのアプローチ
L'遺伝 そして 構成 これらは 2 つの基本的なメカニズムです オブジェクト指向プログラミングでのコードの再利用用。どちらも可能にします 単純なコンポーネントから始めて、アプローチと影響を伴う複雑なソフトウェアを構築する とても違う。
🎯 何を学ぶか
- 継承をいつ使用するか、いつ合成を使用するか
- 抽象クラスとインターフェイスの違い
- 「継承より合成」の原則
- よくある遺伝の問題を回避する方法
継承: 「IS-A」関係
継承は関係を表現します 「それは」 (です-a)。犬 それは 動物、 円 それは1つです 形状。子クラスは親クラスのすべてのメンバーを継承し、次のことができます。 それらを拡張または上書きします。
class Vehicle {{ '{' }}
protected speed: number = 0;
constructor(protected brand: string) {{ '{' }}{{ '}' }}
accelerate(amount: number): void {{ '{' }}
this.speed += amount;
console.log(`${{ '{' }}this.brand{{ '}' }} accelera a ${{ '{' }}this.speed{{ '}' }} km/h`);
{{ '}' }}
getInfo(): string {{ '{' }}
return `${{ '{' }}this.brand{{ '}' }} - Velocità: ${{ '{' }}this.speed{{ '}' }} km/h`;
{{ '}' }}
{{ '}' }}
class Car extends Vehicle {{ '{' }}
private doors: number;
constructor(brand: string, doors: number) {{ '{' }}
super(brand); // Chiama il costruttore del padre
this.doors = doors;
{{ '}' }}
// Estende la funzionalità del padre
getInfo(): string {{ '{' }}
return `${{ '{' }}super.getInfo(){{ '}' }} - Porte: ${{ '{' }}this.doors{{ '}' }}`;
{{ '}' }}
openTrunk(): void {{ '{' }}
console.log("Bagagliaio aperto");
{{ '}' }}
{{ '}' }}
const myCar = new Car("Tesla", 4);
myCar.accelerate(50);
console.log(myCar.getInfo());
// Output: Tesla - Velocità: 50 km/h - Porte: 4
抽象クラスとインターフェイスの比較
TypeScript は両方を提供します 抽象クラス それ インターフェース 定義する 再利用可能な契約と構造。
抽象クラス:
- 実装を持つことができます
- 修飾子をサポート (プライベート、保護)
- 単一継承 (1 つだけを拡張)
- 共通のコードを共有するために使用します
インターフェース:
- 署名のみ、実装なし
- メンバーは全員公開です
- 複数の実装 (複数のインターフェイスを実装)
- 契約を定義するために使用します
abstract class Shape {{ '{' }}
protected color: string;
constructor(color: string) {{ '{' }}
this.color = color;
{{ '}' }}
// Metodo concreto (con implementazione)
getColor(): string {{ '{' }}
return this.color;
{{ '}' }}
// Metodo astratto (da implementare nelle sottoclassi)
abstract area(): number;
abstract perimeter(): number;
{{ '}' }}
class Circle extends Shape {{ '{' }}
constructor(private radius: number, color: string) {{ '{' }}
super(color);
{{ '}' }}
area(): number {{ '{' }}
return Math.PI * this.radius ** 2;
{{ '}' }}
perimeter(): number {{ '{' }}
return 2 * Math.PI * this.radius;
{{ '}' }}
{{ '}' }}
const circle = new Circle(5, "rosso");
console.log(circle.area()); // 78.54
console.log(circle.getColor()); // "rosso"
interface Flyable {{ '{' }}
fly(): void;
altitude: number;
{{ '}' }}
interface Swimmable {{ '{' }}
swim(): void;
depth: number;
{{ '}' }}
// Una classe può implementare più interfacce
class Duck implements Flyable, Swimmable {{ '{' }}
altitude: number = 0;
depth: number = 0;
fly(): void {{ '{' }}
this.altitude = 100;
console.log(`Volo a ${{ '{' }}this.altitude{{ '}' }}m di altitudine`);
{{ '}' }}
swim(): void {{ '{' }}
this.depth = 2;
console.log(`Nuoto a ${{ '{' }}this.depth{{ '}' }}m di profondità`);
{{ '}' }}
{{ '}' }}
const duck = new Duck();
duck.fly(); // "Volo a 100m di altitudine"
duck.swim(); // "Nuoto a 2m di profondità"
構成:「HAS-A」レポート
構図は関係性を表現する 「ある」 (ある)。車 持っています エンジン、 コンピューター 1つあります CPU。継承する代わりに、他のクラスのインスタンスをメンバーとして含めます。
class Engine {{ '{' }}
constructor(private horsepower: number) {{ '{' }}{{ '}' }}
start(): void {{ '{' }}
console.log(`Motore da ${{ '{' }}this.horsepower{{ '}' }}HP avviato`);
{{ '}' }}
getHorsepower(): number {{ '{' }}
return this.horsepower;
{{ '}' }}
{{ '}' }}
class Transmission {{ '{' }}
constructor(private type: string) {{ '{' }}{{ '}' }}
shift(gear: number): void {{ '{' }}
console.log(`Cambio ${{ '{' }}this.type{{ '}' }}: marcia ${{ '{' }}gear{{ '}' }}`);
{{ '}' }}
{{ '}' }}
// Car "ha un" Engine e "ha una" Transmission
class CarComposed {{ '{' }}
private engine: Engine;
private transmission: Transmission;
constructor(horsepower: number, transmissionType: string) {{ '{' }}
this.engine = new Engine(horsepower);
this.transmission = new Transmission(transmissionType);
{{ '}' }}
start(): void {{ '{' }}
this.engine.start();
{{ '}' }}
drive(): void {{ '{' }}
this.transmission.shift(1);
console.log("Auto in movimento");
{{ '}' }}
getSpecs(): string {{ '{' }}
return `${{ '{' }}this.engine.getHorsepower(){{ '}' }}HP`;
{{ '}' }}
{{ '}' }}
const car = new CarComposed(200, "automatico");
car.start(); // "Motore da 200HP avviato"
car.drive(); // "Cambio automatico: marcia 1"
// "Auto in movimento"
継承よりも構成
原則 「継承より合成」 好ましい構成を示唆している 可能な限り継承します。なぜ?
✅ 構成の利点:
- 柔軟性: 実行時に動作を変更する
- 再利用: さまざまな方法でコンポーネントを組み合わせる
- テスト: コンポーネントを個別にテストする
- 弱い結合: より安全な編集
⚠️相続問題:
- 変更が難しい厳格な階層構造
- 父と息子の強い結びつき
- 脆弱な基本クラスの問題
- ダイヤモンド問題 (多重継承)
継承を使用する場合
継承は次の場合に適切です。
- 1つあります 明確な「is-a」関係 (犬は動物です)
- サブクラスは 1 つです 真の専門化 スーパークラスの
- 便利です 多態性 (共通のインターフェイスでさまざまなオブジェクトを処理します)
- 階層は 安定した そしてそれは頻繁には変わりません
// Gerarchia chiara e stabile
abstract class Employee {{ '{' }}
constructor(
protected name: string,
protected salary: number
) {{ '{' }}{{ '}' }}
abstract calculateBonus(): number;
getDetails(): string {{ '{' }}
return `${{ '{' }}this.name{{ '}' }} - Stipendio: €${{ '{' }}this.salary{{ '}' }}`;
{{ '}' }}
{{ '}' }}
class Manager extends Employee {{ '{' }}
calculateBonus(): number {{ '{' }}
return this.salary * 0.2; // 20% bonus
{{ '}' }}
{{ '}' }}
class Developer extends Employee {{ '{' }}
constructor(name: string, salary: number, private language: string) {{ '{' }}
super(name, salary);
{{ '}' }}
calculateBonus(): number {{ '{' }}
return this.salary * 0.15; // 15% bonus
{{ '}' }}
{{ '}' }}
合成を使用する場合
この組成物は次の場合に適しています。
- 関係は 「ある」 (車にはエンジンが付いています)
- 便利です 柔軟な再利用 機能性の
- 行動は次のようにする必要があります 交換可能
- 構造は、 進化 時間とともに
// Strategy pattern con composizione
interface PaymentStrategy {{ '{' }}
pay(amount: number): void;
{{ '}' }}
class CreditCardPayment implements PaymentStrategy {{ '{' }}
pay(amount: number): void {{ '{' }}
console.log(`Pagamento di €${{ '{' }}amount{{ '}' }} con carta di credito`);
{{ '}' }}
{{ '}' }}
class PayPalPayment implements PaymentStrategy {{ '{' }}
pay(amount: number): void {{ '{' }}
console.log(`Pagamento di €${{ '{' }}amount{{ '}' }} con PayPal`);
{{ '}' }}
{{ '}' }}
class ShoppingCartComposed {{ '{' }}
private items: number[] = [];
private paymentStrategy!: PaymentStrategy;
addItem(price: number): void {{ '{' }}
this.items.push(price);
{{ '}' }}
// Cambia strategia a runtime!
setPaymentStrategy(strategy: PaymentStrategy): void {{ '{' }}
this.paymentStrategy = strategy;
{{ '}' }}
checkout(): void {{ '{' }}
const total = this.items.reduce((sum, price) => sum + price, 0);
this.paymentStrategy.pay(total);
{{ '}' }}
{{ '}' }}
const cart = new ShoppingCartComposed();
cart.addItem(50);
cart.addItem(30);
cart.setPaymentStrategy(new CreditCardPayment());
cart.checkout(); // "Pagamento di €80 con carta di credito"
cart.setPaymentStrategy(new PayPalPayment());
cart.checkout(); // "Pagamento di €80 con PayPal"
結論
継承と合成は敵ではなく補完的なツールです。遺伝は強力です ポリモーフィズムを使用して安定した階層をモデル化する一方で、構成は柔軟性と モジュール式の再利用。経験則: 作曲から始まります、使用します 継承は、「is-a」関係が明確で、利益がコストを上回る場合にのみ行われます。
🎯 重要なポイント
- 継承 = "is-a"、構成 = "has-a"
- 共有コードの抽象クラス、コントラクトのインターフェイス
- 継承よりも構成により柔軟性が向上
- 明確な多態性を備えた安定した階層のために継承を使用する







