Skip to main content

solid

Use when applying or evaluating SOLID object-oriented design principles — Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, and Dependency Inversion. USE FOR: class design, interface design, dependency management, evaluating OO design quality, reducing coupling, improving testability DO NOT USE FOR: architecture layer decisions (use clean-architecture), design pattern selection (use dev/design-patterns), refactoring mechanics (use refactoring)

소스 정보

저장소
Tyler-R-Kendrick/agent-skills
최근 소스 활동
2026년 2월 11일 05:14
감지된 SKILL.md 언어
영어
스타
11
포크
4

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

파일 탐색기
12 개 파일

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
solid
description
Use when applying or evaluating SOLID object-oriented design principles — Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, and Dependency Inversion. USE FOR: class design, interface design, dependency management, evaluating OO design quality, reducing coupling, improving testability DO NOT USE FOR: architecture layer decisions (use clean-architecture), design pattern selection (use dev/design-patterns), refactoring mechanics (use refactoring)
license
MIT
metadata
{"displayName":"SOLID Principles","author":"Tyler-R-Kendrick"}
compatibility
claude, copilot, cursor
references
[{"title":"Robert C. Martin — The Principles of OOD","url":"http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod"},{"title":"SOLID — Wikipedia","url":"https://en.wikipedia.org/wiki/SOLID"}]
# SOLID Principles ## Overview SOLID is a mnemonic for five object-oriented design principles introduced by Robert C. Martin. Together they guide developers toward code that is easier to understand, more flexible to change, and simpler to test. | Letter | Principle | One-Line Summary | |--------|-----------|------------------| | **S** | Single Responsibility | A class should have only one reason to change. | | **O** | Open/Closed | Open for extension, closed for modification. | | **L** | Liskov Substitution | Subtypes must be substitutable for their base types. | | **I** | Interface Segregation | No client should be forced to depend on methods it does not use. | | **D** | Dependency Inversion | Depend on abstractions, not concretions. | --- ## S — Single Responsibility Principle (SRP) > "A class should have one, and only one, reason to change." — Robert C. Martin A "reason to change" maps to a **stakeholder** or **actor**. If two different actors can request changes to the same class, that class has more than one responsibility. ### Violation ```typescript class UserService { createUser(name: string, email: string): User { // validate input if (!email.includes("@")) throw new Error("Invalid email"); // persist to database const user = this.db.query( "INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *", [name, email] ); // send welcome email this.emailClient.send({ to: email, subject: "Welcome!", body: `Hello ${name}, welcome aboard.`, }); // write audit log this.logger.info(`User created: ${email}`); return user; } } ``` This class has **four reasons to change**: validation rules, database schema, email template, and logging format. ### Corrected ```typescript class UserValidator { validate(name: string, email: string): void { if (!email.includes("@")) throw new InvalidEmailError(email); } } class UserRepository { save(name: string, email: string): Promise<User> { return this.db.query( "INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *", [name, email] ); } } class WelcomeEmailSender { send(user: User): Promise<void> { return this.emailClient.send({ to: user.email, subject: "Welcome!", body: `Hello ${user.name}, welcome aboard.`, }); } } class CreateUserUseCase { constructor( private validator: UserValidator, private repo: UserRepository, private welcomeEmail: WelcomeEmailSender, private logger: Logger ) {} async execute(name: string, email: string): Promise<User> { this.validator.validate(name, email); const user = await this.repo.save(name, email); await this.welcomeEmail.send(user); this.logger.info(`User created: ${email}`); return user; } } ``` Each class now has one reason to change, and `CreateUserUseCase` orchestrates them. --- ## O — Open/Closed Principle (OCP) > "Software entities should be open for extension, but closed for modification." — Bertrand Meyer You should be able to add new behavior **without modifying existing code**. This is typically achieved through abstraction and polymorphism. ### Violation ```typescript class DiscountCalculator { calculate(order: Order): number { switch (order.customerType) { case "regular": return 0; case "premium": return order.total * 0.1; case "vip": return order.total * 0.2; default: return 0; } } } // Adding a new customer type requires modifying this class. ``` ### Corrected ```typescript interface DiscountStrategy { calculate(order: Order): number; } class RegularDiscount implements DiscountStrategy { calculate(order: Order): number { return 0; } } class PremiumDiscount implements DiscountStrategy { calculate(order: Order): number { return order.total * 0.1; } } class VipDiscount implements DiscountStrategy { calculate(order: Order): number { return order.total * 0.2; } } class DiscountCalculator { constructor(private strategy: DiscountStrategy) {} calculate(order: Order): number { return this.strategy.calculate(order); } } // Adding a new customer type = adding a new class. No existing code changes. class EmployeeDiscount implements DiscountStrategy { calculate(order: Order): number { return order.total * 0.3; } } ``` --- ## L — Liskov Substitution Principle (LSP) > "Objects of a superclass should be replaceable with objects of a subclass without breaking the program." — Barbara Liskov If `S` is a subtype of `T`, then objects of type `T` may be replaced with objects of type `S` without altering the correctness of the program. Subtypes must honor the behavioral contract of the base type. ### Violation ```typescript class Rectangle { constructor(protected width: number, protected height: number) {} setWidth(w: number): void { this.width = w; } setHeight(h: number): void { this.height = h; } area(): number { return this.width * this.height; } } class Square extends Rectangle { setWidth(w: number): void { this.width = w; this.height = w; // violates Rectangle's contract } setHeight(h: number): void { this.width = h; this.height = h; // violates Rectangle's contract } } // Client code that breaks with Square: function assertAreaCorrect(rect: Rectangle): void { rect.setWidth(5); rect.setHeight(4); console.assert(rect.area() === 20); // Fails for Square! area() returns 16 } ``` ### Corrected ```typescript interface Shape { area(): number; } class Rectangle implements Shape { constructor(private width: number, private height: number) {} area(): number { return this.width * this.height; } } class Square implements Shape { constructor(private side: number) {} area(): number { return this.side * this.side; } } // Both satisfy the Shape contract. No inheritance relationship forces // Square to pretend it is a Rectangle. function printArea(shape: Shape): void { console.log(`Area: ${shape.area()}`); } ``` ### LSP Rules of Thumb - **Preconditions** cannot be strengthened in a subtype. - **Postconditions** cannot be weakened in a subtype. - **Invariants** of the base type must be preserved by the subtype. - The **history constraint**: a subtype should not introduce state changes the base type would not allow. --- ## I — Interface Segregation Principle (ISP) > "No client should be forced to depend on methods it does not use." — Robert C. Martin Large, "fat" interfaces force implementing classes to provide stub methods for features they do not support. Split interfaces along client boundaries. ### Violation ```typescript interface Printer { print(doc: Document): void; scan(doc: Document): void; fax(doc: Document): void; staple(doc: Document): void; } class SimplePrinter implements Printer { print(doc: Document): void { /* works */ } scan(doc: Document): void { throw new Error("Not supported"); } fax(doc: Document): void { throw new Error("Not supported"); } staple(doc: Document): void { throw new Error("Not supported"); } } ``` `SimplePrinter` is forced to know about scanning, faxing, and stapling even though it supports none of them. ### Corrected ```typescript interface Printable { print(doc: Document): void; } interface Scannable { scan(doc: Document): void; } interface Faxable { fax(doc: Document): void; } interface Stapleable { staple(doc: Document): void; } class SimplePrinter implements Printable { print(doc: Document): void { /* works */ } } class MultiFunctionPrinter implements Printable, Scannable, Faxable { print(doc: Document): void { /* works */ } scan(doc: Document): void { /* works */ } fax(doc: Document): void { /* works */ } } ``` Each client depends only on the interface it actually uses. --- ## D — Dependency Inversion Principle (DIP) > "High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions." — Robert C. Martin ### Violation ```typescript class MySqlDatabase { save(data: Record<string, unknown>): void { // MySQL-specific implementation } } class OrderService { private db = new MySqlDatabase(); // direct dependency on a concrete class createOrder(order: Order): void { this.db.save(order.toRecord()); } } // Changing from MySQL to PostgreSQL requires modifying OrderService. ``` ### Corrected ```typescript // Abstraction — defined in the high-level module interface OrderRepository { save(order: Order): Promise<void>; } // Detail — depends on the abstraction class MySqlOrderRepository implements OrderRepository { async save(order: Order): Promise<void> { // MySQL-specific implementation } } class PostgresOrderRepository implements OrderRepository { async save(order: Order): Promise<void> { // PostgreSQL-specific implementation } } // High-level module — depends on the abstraction class OrderService { constructor(private repo: OrderRepository) {} async createOrder(order: Order): Promise<void> { await this.repo.save(order); } } // Composition root — wiring const service = new OrderService(new PostgresOrderRepository()); ``` The high-level policy (`OrderService`) and the low-level detail (`PostgresOrderRepository`) both depend on the abstraction (`OrderRepository`). The dependency is **inverted**. --- ## How SOLID Relates to Other Concepts ### SOLID and Clean Architecture Clean Architecture is the architectural expression of SOLID at the system level: | SOLID Principle | Clean Architecture Manifestation | |----------------|----------------------------------| | SRP | Each layer has a single reason to change (UI, business rules, data). | | OCP | New features are added by creating new Use Cases, not modifying existing ones. | | LSP | Interface Adapters can be swapped (PostgreSQL for MySQL) without breaking Use Cases. | | ISP | Ports (interfaces) are defined per use case, not as monolithic repository interfaces. | | DIP | The Dependency Rule — inner layers define interfaces, outer layers implement them. | ### SOLID and Design Patterns Many GoF design patterns exist specifically to satisfy SOLID: | Pattern | Primary SOLID Principle | |---------|------------------------| | Strategy | OCP, DIP | | Observer | OCP, DIP | | Decorator | OCP, SRP | | Factory Method / Abstract Factory | DIP | | Adapter | ISP, DIP | | Command | SRP, OCP | | Template Method | OCP, LSP | ### SOLID and Testability Each SOLID principle directly improves testability: | Principle | Testing Benefit | |-----------|----------------| | SRP | Smaller classes with fewer dependencies = simpler test setup. | | OCP | New behavior tested in isolation via new classes, existing tests remain green. | | LSP | Test doubles (mocks, stubs) can replace real implementations safely. | | ISP | Narrow interfaces require fewer mock methods. | | DIP | Dependencies are injected, making them trivially replaceable with test doubles. | ## Common Pitfalls | Pitfall | Description | |---------|-------------| | Over-engineering with SRP | Splitting every method into its own class creates an explosion of trivial types. SRP means one *reason to change*, not one *method*. | | Premature abstraction for OCP | Do not create Strategy/Plugin architectures for code that has only one variant. Wait for the second use case. | | Ignoring LSP in collections | A `ReadOnlyList` that extends `List` and throws on `add()` violates LSP. | | Interface explosion with ISP | Do not create a one-method interface for every single method. Group by **client need**. | | DIP everywhere | Not every dependency needs an interface. Stable, unlikely-to-change dependencies (e.g., standard library) can be referenced directly. | ## Best Practices - Apply SOLID principles as guardrails during code review, not as upfront design mandates. - When you feel friction adding a feature, check which SOLID principle is being violated. - Use constructor injection as the default mechanism for DIP — it makes dependencies explicit and immutable. - Write tests first (TDD) — SOLID violations surface quickly when code is hard to test. - Balance SOLID with YAGNI: do not add abstractions speculatively. Introduce them when the second variation appears. - Review class names regularly — if a class name contains "And" or "Manager" or "Service" doing too many things, SRP is likely violated.
GitHub에서 보기