All skills
rshankras avatar

/architecture-patterns

@1361023

Deep dive into software architecture for macOS. Covers SOLID principles, design patterns, and modular code organization. Use when designing app architecture or refactoring.

Use this Skill: https://skilld.dev/gh/rshankras/claude-code-apple-skills/architecture-patterns

This session only. Nothing lands on disk.

solid-detailed.md

≈2.1k tokens on demand. Your agent reads this file only when SKILL.md points to it.

SOLID Principles in Detail

Real-world Swift examples for each SOLID principle with refactoring patterns. Focus on practical application, not academic theory.

Single Responsibility Principle (SRP)

A type should have one reason to change.

Violation

class DocumentManager {
    var documents: [Document] = []

    // Responsibility 1: Document CRUD
    func createDocument(title: String) -> Document { ... }
    func deleteDocument(_ doc: Document) { ... }

    // Responsibility 2: Persistence
    func saveToFile(_ doc: Document) throws { ... }
    func loadFromFile(_ url: URL) throws -> Document { ... }

    // Responsibility 3: Export
    func exportToPDF(_ doc: Document) -> Data { ... }
    func exportToHTML(_ doc: Document) -> String { ... }

    // Responsibility 4: Search
    func search(query: String) -> [Document] { ... }
}

Refactored

// Each class has one reason to change
@Observable class DocumentStore {
    var documents: [Document] = []
    func create(title: String) -> Document { ... }
    func delete(_ doc: Document) { ... }
}

struct DocumentPersistence {
    func save(_ doc: Document, to url: URL) throws { ... }
    func load(from url: URL) throws -> Document { ... }
}

struct DocumentExporter {
    func toPDF(_ doc: Document) -> Data { ... }
    func toHTML(_ doc: Document) -> String { ... }
}

struct DocumentSearch {
    func search(_ documents: [Document], query: String) -> [Document] { ... }
}

When to Bend SRP

  • Tiny apps: A single ViewModel handling fetch + display is fine for 1-2 screens
  • Data models: @Model classes naturally combine data + persistence — that's SwiftData's design
  • Value types: Small structs with 2-3 responsibilities are often clearer than over-split types

Open/Closed Principle (OCP)

Open for extension, closed for modification.

Use protocols and enums to add behavior without changing existing code.

Violation

class ReportGenerator {
    func generate(type: String, data: ReportData) -> String {
        switch type {
        case "pdf": return generatePDF(data)
        case "html": return generateHTML(data)
        case "csv": return generateCSV(data)
        // Adding "markdown" requires modifying this class
        default: fatalError()
        }
    }
}

Refactored

protocol ReportFormat {
    func generate(from data: ReportData) -> String
}

struct PDFReport: ReportFormat {
    func generate(from data: ReportData) -> String { ... }
}

struct HTMLReport: ReportFormat {
    func generate(from data: ReportData) -> String { ... }
}

// Adding Markdown requires no changes to existing code
struct MarkdownReport: ReportFormat {
    func generate(from data: ReportData) -> String { ... }
}

class ReportGenerator {
    func generate(format: ReportFormat, data: ReportData) -> String {
        format.generate(from: data)
    }
}

Swift-Specific OCP: Protocol Extensions

protocol Cacheable {
    var cacheKey: String { get }
    var cacheExpiry: TimeInterval { get }
}

// Default behavior via extension — open for override, closed for modification
extension Cacheable {
    var cacheExpiry: TimeInterval { 300 } // 5 minutes default
}

Liskov Substitution Principle (LSP)

Subtypes must be substitutable for their base types without breaking behavior.

Violation

class FileStorage {
    func save(_ data: Data, to path: String) throws { ... }
    func load(from path: String) throws -> Data { ... }
    func delete(at path: String) throws { ... }
}

class ReadOnlyStorage: FileStorage {
    override func save(_ data: Data, to path: String) throws {
        throw StorageError.readOnly  // Breaks the contract!
    }
    override func delete(at path: String) throws {
        throw StorageError.readOnly  // Breaks the contract!
    }
}

Refactored

protocol ReadableStorage {
    func load(from path: String) throws -> Data
}

protocol WritableStorage: ReadableStorage {
    func save(_ data: Data, to path: String) throws
    func delete(at path: String) throws
}

struct FileStorage: WritableStorage {
    func load(from path: String) throws -> Data { ... }
    func save(_ data: Data, to path: String) throws { ... }
    func delete(at path: String) throws { ... }
}

struct BundleStorage: ReadableStorage {
    func load(from path: String) throws -> Data { ... }
    // No save/delete — not part of the contract
}

Interface Segregation Principle (ISP)

Clients shouldn't depend on methods they don't use.

Violation

protocol DataService {
    func fetchUsers() async throws -> [User]
    func fetchPosts() async throws -> [Post]
    func fetchComments() async throws -> [Comment]
    func createUser(_ user: User) async throws
    func createPost(_ post: Post) async throws
    func deleteUser(_ id: UUID) async throws
}

// UserListView only needs fetchUsers but depends on the entire protocol
struct UserListViewModel {
    let service: DataService  // Forced to depend on posts, comments, etc.
}

Refactored

protocol UserFetching {
    func fetchUsers() async throws -> [User]
}

protocol UserManaging: UserFetching {
    func createUser(_ user: User) async throws
    func deleteUser(_ id: UUID) async throws
}

protocol PostFetching {
    func fetchPosts() async throws -> [Post]
}

// A single concrete type can conform to all
class APIService: UserManaging, PostFetching {
    func fetchUsers() async throws -> [User] { ... }
    func createUser(_ user: User) async throws { ... }
    func deleteUser(_ id: UUID) async throws { ... }
    func fetchPosts() async throws -> [Post] { ... }
}

// Each consumer depends only on what it needs
struct UserListViewModel {
    let userFetcher: UserFetching  // Minimal dependency
}

Dependency Inversion Principle (DIP)

High-level modules shouldn't depend on low-level modules. Both should depend on abstractions.

Violation

class AnalyticsViewModel {
    let tracker = FirebaseAnalytics()  // Direct dependency on concrete type

    func trackEvent(_ name: String) {
        tracker.logEvent(name, parameters: nil)
    }
}

Refactored

protocol AnalyticsTracking {
    func trackEvent(_ name: String, properties: [String: Any])
}

struct FirebaseTracker: AnalyticsTracking {
    func trackEvent(_ name: String, properties: [String: Any]) { ... }
}

struct MockTracker: AnalyticsTracking {
    var trackedEvents: [(String, [String: Any])] = []
    mutating func trackEvent(_ name: String, properties: [String: Any]) {
        trackedEvents.append((name, properties))
    }
}

@Observable class AnalyticsViewModel {
    private let tracker: AnalyticsTracking

    init(tracker: AnalyticsTracking) {
        self.tracker = tracker
    }
}

DIP with SwiftUI Environment

// Define the abstraction
protocol ImageLoader {
    func load(url: URL) async throws -> NSImage
}

// Environment key
struct ImageLoaderKey: EnvironmentKey {
    static let defaultValue: ImageLoader = URLSessionImageLoader()
}

extension EnvironmentValues {
    var imageLoader: ImageLoader {
        get { self[ImageLoaderKey.self] }
        set { self[ImageLoaderKey.self] = newValue }
    }
}

// Inject via environment
ContentView()
    .environment(\.imageLoader, CachedImageLoader())

// Consume in views
struct ThumbnailView: View {
    @Environment(\.imageLoader) private var imageLoader
}

Pragmatic SOLID

SOLID principles are guidelines, not laws. Apply them proportionally:

App Size SRP OCP LSP ISP DIP
Prototype Loose Skip Follow Skip Skip
Small (1-3 screens) Moderate Where natural Follow Light For testing
Medium (4-10 screens) Strict For extensible areas Follow Moderate For services
Large (10+ screens) Strict Everywhere Follow Strict Everywhere

Rules of thumb:

  • If a class is under 100 lines, SRP violations are probably fine
  • If you'll never extend a type, OCP is unnecessary overhead
  • LSP should always be followed — it prevents bugs
  • ISP matters most at module boundaries
  • DIP matters most for testability and swappable implementations

Source: SKILL.md on GitHub

No alerts2mo3 checks · Risk SAFE
  • Gen Agent Trust Hub2mo

    The skill is a comprehensive guide for macOS software architecture using Swift. It is generally safe but possesses a standard attack surface for indirect prompt injection as it analyzes external code using file-reading tools without explicit boundary instructions.

  • Socket2mo

    No alerts

  • Snyk2mo

    Risk: LOW · No issues

Signed by skilld at 1361023. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Steadyupdated 3 months ago
What it can do
Reads files
last_verified
2026-07-16
review_by
2027-06-22
os_version
iOS 27 / macOS 27
All 3 allowed tools
ReadGlobGrep

README badge

README badge for rshankras/claude-code-apple-skills/architecture-patterns