+5

🌞SOLID原則コヌドをきれいにしお理解しやすくする🌞

コヌディングをするずき、読みやすく理解しやすいものにするこずが倧切です。特に倧きなプロゞェクトで耇数の郚分があるずきは、特に重芁です。コヌドを理解しやすくするための方法ずしお、SOLIDの原則を守るこずがありたす。

SOLIDずは、5぀の原則を意味したす

  1. Single Responsibility Principle (SRP)
  2. Open-Closed Principle (OCP)
  3. Liskov Substitution Principle (LSP)
  4. Interface Segregation Principle (ISP)
  5. Dependency Inversion Principle (DIP)

それぞれの原則を䞀぀ず぀芋おいき、どのようにコヌドをより良くするのかを芋おいきたしょう。

説明

Single Responsibility Principle (SRP)

SRPは、コヌドの各郚分が1぀の仕事だけをするようにするずいうこずを蚀っおいたす。これは、1぀のコヌドが耇数のこずをする堎合、小さな郚分に分けお、それぞれに仕事を䞎えるべきだずいうこずを意味したす。

䟋えば、ボタンが䜕回クリックされたかを蚘録し、画面にクリック数を衚瀺するコヌドがあるずしたす。SRPによるず、このコヌドは2぀の別々の郚分に分けるべきです1぀はクリックを蚘録するもの、もう1぀は画面にクリック数を衚瀺するものです。

// Incorrect way of doing it
let clickCount = 0;

function handleButtonClick() {
    clickCount += 1;
    document.getElementById("click-count").innerHTML = clickCount;
}
// Correct way of doing it
let clickCount = 0;

function handleButtonClick() {
    clickCount += 1;
}

function updateClickCount() {
    document.getElementById("click-count").innerHTML = clickCount;
}

Open-Closed Principle (OCP)

OCPずは、コヌドを拡匵するために開いおいるべきであり、修正するためには閉じおいるべきだずいうこずです。これは、既存のコヌドを倉曎せずに新しい機胜を远加できるようにするこずを意味したす。

䟋えば、2぀の数字を足し合わせるプログラムがあるずしたす。OCPに埓うず、既存の足し算のコヌドを倉曎せずに、新しい機胜匕き算などを远加できるようになりたす。

// Incorrect way of doing it
function add(a, b) {
    return a + b;
}

// It is not a good idea to change the `add` function to a `subtract` function.
function subtract(a, b) {
    return a - b;
}
// Correct way of doing it
class Calculator {
    static add(a, b) {
        return a + b;
    }

    static subtract(a, b) {
        return a - b;
    }
}

Liskov Substitution Principle (LSP)

LSPずは、芪クラスを䜿う堎所でサブクラスを䜿えるようにするずいうものです。これは、サブクラスが芪クラスの「より良い」バヌゞョンであり、芪クラスを䜿うコヌドを壊さないこずを意味したす。

䟋えば、「Animal」ずいう芪クラスず「Dogs」ずいうサブクラスがあるずしたす。LSPによるず、「Animal」オブゞェクトを䜿う堎所で「Dog」オブゞェクトを䜿えるはずであり、コヌドは正しく動䜜するはずです。

class Animals {
    constructor(name) {
        this.name = name;
    }

    speak() {
        return "Animals make noise";
    }
}

class Dogs extends Animals {
    speak() {
        return "Woof";
    }
}

const animal = new Animals("Animals");
console.log(animal.speak()); // prints "Animals make noise"

const dog = new Dogs("Dog");
console.log(dog.speak()); // prints "Woof"
console.log(dog instanceof Animals); // prints "true"

Interface Segregation Principle (ISP)

ISPずは、クラむアントに䜿わないむンタヌフェヌスを匷制するべきではないずいうこずです。これは、関連する機胜のグルヌプごずに小さなむンタヌフェヌスを䜜成するべきだずいうこずです。

䟋えば、「Automobile」ずいうむンタヌフェヌスが、走行ず飛行の䞡方の機胜を持っおいるずしたす。ISPによるず、走行しかできない車のクラスは、「Automobile」むンタヌフェヌスからの飛行機胜を匷制されおはなりたせん。

// Incorrect way of doing it
interface Automobile {
    drive(): void;
    fly(): void;
}

class Car implements Automobile {
    drive(): void {
        // code for driving
    }

    fly(): void {
        // code for flying (not applicable for cars)
    }
}
// Correct way of doing it
interface Drivable {
    drive(): void;
}

interface Flyable {
    fly(): void;
}

class Car implements Drivable {
    drive(): void {
        // code for driving
    }
}

Dependency Inversion Principle (DIP)

DIPずは、高レベルのモゞュヌルは䜎レベルのモゞュヌルに䟝存しおはいけないが、䞡方ずも抜象化に䟝存するべきだずいうこずです。これは、あなたのコヌドが特定のクラスや関数に䟝存しおはいけないずいうこずを意味したすが、むしろ抜象的な抂念に䟝存するべきです。

䟋えば、「Car」ずいうクラスが「Engine」ずいうクラスに䟝存しおいるずしたす。DIPによるず、「Car」クラスは特定の「Engine」クラスに䟝存しおはいけず、゚ンゞンずは䜕かずいう抜象化に䟝存すべきです。

// Incorrect way of doing it
class Engine {
    start(): void {
        // code for starting the engine
    }
}

class Car {
    private engine: Engine;

    constructor() {
        this.engine = new Engine();
    }

    start(): void {
        this.engine.start();
    }
}
// Correct way of doing it
interface Engine {
    start(): void;
}

class RealEngine implements Engine {
    start(): void {
        // code for starting the engine
    }
}

class Car {
    private engine: Engine;

    constructor(engine: Engine) {
        this.engine = engine;
    }

    start(): void {
        this.engine.start();
    }
}

const car = new Car(new RealEngine());

ケヌスを䜿う

1. むンタヌネットショッピングのチェックアりトプロセス

お客様がカヌトにアむテムを远加し、チェックアりトペヌゞに進むeコマヌスりェブサむトを考えたしょう。チェックアりトプロセスには、アむテムの合蚈金額を蚈算し、割匕やプロモヌションを適甚し、そしお支払いを凊理するこずが含たれたす。

SRPによるず、チェックアりトプロセスを異なるクラスに分割すべきです。それぞれが独自の責任を持぀ようにしたす。䟋えば、カヌト内のアむテムを远跡するCartクラス、割匕やプロモヌションを適甚するDiscountsクラス、そしお支払い凊理を行うPaymentクラスなどを䜜成するこずができたす。

class Cart {
    items = [];
    addItem(item) {
        this.items.push(item);
    }
    getTotal() {
        return this.items.reduce((total, item) => total + item.price, 0);
    }
}

class Discounts {
    applyDiscount(total) {
        return total * 0.9; // 10% off
    }
}

class Payment {
    processPayment(total) {
        // code for processing the payment
    }
}

class Checkout {
    cart;
    discounts;
    payment;

    constructor(cart, discounts, payment) {
        this.cart = cart;
        this.discounts = discounts;
        this.payment = payment;
    }

    processCheckout() {
        const total = this.discounts.applyDiscount(this.cart.getTotal());
        this.payment.processPayment(total);
    }
}

const cart = new Cart();
cart.addItem({ name: "item1", price: 20 });
cart.addItem({ name: "item2", price: 30 });

const checkout = new Checkout(cart, new Discounts(), new Payment());
checkout.processCheckout();

この䟋では、それぞれのクラスに1぀の責任がありたすCartクラスはカヌト内の商品を远跡したす、Discountsクラスは割匕を適甚したす、Paymentクラスは支払いを凊理したす、Checkoutクラスはプロセスを調敎したす。これにより、コヌドが保守性が高く、理解しやすくなりたす。

2. 倩気アプリ

倩気アプリに、珟圚の堎所の気枩、湿床、気圧を衚瀺する機胜がありたす。新しい機胜ずしお、颚速ず颚向きを衚瀺する機胜を远加したいず思いたす。Open-Closed PrincipleOCPによるず、既存のコヌドを倉曎するこずなく、この新しい機胜を远加できるはずです。

class WeatherData {
  constructor(temperature, humidity, pressure) {
    this.temperature = temperature;
    this.humidity = humidity;
    this.pressure = pressure;
  }
}

class WeatherDisplay {
  display(weatherData) {
    console.log(`Temperature: ${weatherData.temperature}`);
    console.log(`Humidity: ${weatherData.humidity}`);
    console.log(`Pressure: ${weatherData.pressure}`);
  }
}

class WindDisplay {
  display(weatherData) {
    console.log(`Wind speed: ${weatherData.windSpeed}`);

    console.log(`Wind direction: ${(weatherData, windDirection)}`);
  }
}

class WeatherApp {
  weatherData;
  weatherDisplay;
  windDisplay;
  constructor(weatherData) {
    this.weatherData = weatherData;
    this.weatherDisplay = new WeatherDisplay();
    this.windDisplay = new WindDisplay();
  }

  displayWeather() {
    this.weatherDisplay.display(this.weatherData);
    this.windDisplay.display(this.weatherData);
  }
}

const weatherData = new WeatherData(72, 50, 1013);
weatherData.windSpeed = 5;
weatherData.windDirection = "NW";
const weatherApp = new WeatherApp(weatherData);
weatherApp.displayWeather();

この䟋では、既存のWeatherDisplayクラスを倉曎せずに、新しいWindDisplayクラスを远加するこずで、WeatherAppクラスを拡匵できたす。これにより、既存のコヌドに圱響を䞎えるこずなく、アプリに新しい機胜を远加できたす。

3. ゲヌムのキャラクタヌ

新しいキャラクタヌを远加したいず思っおいたすが、既存のゲヌムメカニクスを壊さないようにしたいです。LSPずいうものを䜿えば、芪キャラクタヌクラスを䜿う堎所で新しいキャラクタヌクラスを䜿っおも、ゲヌムが正しく動くようになりたす。

class Character {
    move() {
        console.log("Character moved");
    }
}

class Warrior extends Character {
    attack() {
        console.log("Warrior attacked");
    }
}

class Mage extends Character {
    castSpell() {
        console.log("Mage cast a spell");
    }
}

class Paladin extends Warrior {
    heal() {
        console.log("Paladin healed");
    }
}

const characters = [new Warrior(), new Mage(), new Paladin()];
for (let character of characters) {
    character.move();
    if (character instanceof Warrior) {
        character.attack();
    }
    if (character instanceof Mage) {
        character.castSpell();
    }
    if (character instanceof Paladin) {
        character.heal();
    }
}

この䟋では、PaladinクラスはWarriorクラスのサブクラスで、回埩する独自の胜力を持っおいたすが、芪クラスからmoveメ゜ッドを正しく実装しおいるので、キャラクタヌオブゞェクトが䜿われるどこでも䜿えたす。これにより、既存のゲヌムメカニクスを壊さずに新しいキャラクタヌタむプを远加できたす。

4. チャットアプリ

メッセヌゞを送信する機胜ずファむルを送信する機胜を分けるこずで、片方の機胜しか必芁ないクラむアントはもう片方の機胜を実装する必芁がなくなりたす。むンタヌフェヌス分離の原則ISPに埓うず、メッセヌゞを送信するむンタヌフェヌスずファむルを送信するむンタヌフェヌスを分けるべきです。

interface MessageSender {
  sendMessage(message: string): void;
}

interface FileSender {
  sendFile(file: File): void;
}

class ChatClient implements MessageSender {
  sendMessage(message: string): void {
    // code for sending a message
  }
}

class FileTransferClient implements FileSender {
  sendFile(file: File): void {
    // code for sending a file
  }
}

class AdvancedChatClient implements MessageSender, FileSender {
  sendMessage(message: string): void {
    // code for sending a message
  }
  sendFile(file: File): void {
    // code for sending a file
  }
}

const chatClient = new ChatClient();
chatClient.sendMessage("Hello!");

const fileTransferClient = new FileTransferClient();
fileTransferClient.sendFile(new File("file.txt"));

const advancedChatClient = new AdvancedChatClient();
advancedChatClient.sendMessage("Hello!");
advancedChatClient.sendFile(new File("file.txt"));

この䟋では、ChatClientクラスはMessageSenderむンタヌフェヌスのみを実装し、FileSenderむンタヌフェヌスを実装する必芁はありたせん。たた、FileTransferClientクラスはFileSenderむンタヌフェヌスのみを実装し、MessageSenderむンタヌフェヌスを実装する必芁はありたせん。これにより、クラむアントは必芁な機胜だけを実装し、コヌドを明確か぀理解しやすく保぀こずができたす。

5. ゜ヌシャルメディアプラットフォヌム

私たちがテキストや画像の曎新を扱うコヌドを倉曎せずに、ナヌザヌが動画の曎新を投皿できる新しい機胜を远加したいずしたす。䟝存性反転原則DIPによるず、曎新を凊理するコヌドは特定のクラスや関数に䟝存しないようにしお、抜象的な抂念に䟝存するようにしなければなりたせん。

interface Update {
  display(): void;
}

class TextUpdate implements Update {
  text: string;
  constructor(text: string) {
    this.text = text;
  }
  display(): void {
    console.log(`Text Update: ${this.text}`);
  }
}

class ImageUpdate implements Update {
  imageUrl: string;
  constructor(imageUrl: string) {
    this.imageUrl = imageUrl;
  }

  display(): void {
    console.log(Image Update: ${ this.imageUrl });
  }
}

class VideoUpdate implements Update {
  videoUrl: string;
  constructor(videoUrl: string) {
    this.videoUrl = videoUrl;
  }
  display(): void {
    console.log(Video Update: ${ this.videoUrl });
  }
}

class SocialMediaApp {
  updates: Update[];
  constructor() {
    this.updates = [];
  }
  addUpdate(update: Update) {
    this.updates.push(update);
  }

  displayUpdates() {
    this.updates.forEach(update => update.display());
  }
}

const socialMediaApp = new SocialMediaApp();
socialMediaApp.addUpdate(new TextUpdate("Hello, world!"));
socialMediaApp.addUpdate(new ImageUpdate("image.jpg"));
socialMediaApp.addUpdate(new VideoUpdate("video.mp4"));
socialMediaApp.displayUpdates();

この䟋では、テキスト、画像、たたはビデオ曎新を凊理する特定のクラスではなく、「Update」むンタヌフェヌスの抜象的な抂念に䟝存しおいたす。これにより、ビデオ曎新などの新しいタむプの曎新をテキストず画像の曎新を凊理する既存のコヌドを倉曎するこずなく远加できたす。

Conclusion

SOLIDの原則は、開発者がきれいで保守性があり、理解しやすいコヌドを曞くのを助けるガむドラむンの集たりです。これらの原則を守るこずで、開発者はコヌドが扱いやすく、将来的に拡匵したり倉曎したりするのが容易になりたす。゜リッドの原則ずは、Single Responsibility Principle SRP, Open-Closed PrincipleOCP, Liskov Substitution Principle LSP, Interface Segregation PrincipleISP, and Dependency Inversion PrincipleDIPの5぀です。それぞれの原則には、解説、利点、実際のコヌドスニペットを含む実甚䟋がありたす。重芁なのは、゜リッドの原則は厳栌なルヌルではなく、特定のプロゞェクトやアプリケヌションに応じお異なる方法で適甚できる䞀般的なガむドラむンであるずいうこずです。

Mình hy vọng bạn thích bài viết này và học thêm được điều gì đó mới.

Donate mình một ly cafe hoặc 1 cây bút bi để mình có thêm động lực cho ra nhiều bài viết hay và chất lượng hơn trong tương lai nhé. À mà nếu bạn có bất kỳ câu hỏi nào thì đừng ngại comment hoặc liên hệ mình qua: Zalo - 0374226770 hoặc Facebook. Mình xin cảm ơn.

Momo: NGUYỄN ANH TUẀN - 0374226770

TPBank: NGUYỄN ANH TUẀN - 0374226770 (hoặc 01681423001)

image.png


All rights reserved

Viblo
Hãy đăng kÃœ một tài khoản Viblo để nhận được nhiều bài viết thú vị hÆ¡n.
Đăng kí