プロトコル指向設計の支援。Protocol拡張、Associated Types設計、依存性注入パターンを提案。「プロトコルを設計して」「依存性注入を実装して」で使用。
Swift protocol-oriented design support for abstraction, dependency injection, and testability. Use when you need protocol definitions, associated types, or dependency injection patterns.
/plugin marketplace add CAPHTECH/claude-marketplace/plugin install apple-platform-plugin@caphtech-marketplaceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
references/dependency-injection-guide.mdreferences/protocol-patterns.mdSwiftのプロトコル指向プログラミングを活用した設計を支援する。
プロトコル指向設計に対して以下の観点で支援を実施:
## 抽象化の目的
### 動機
- [ ] テスタビリティ向上
- [ ] 実装の交換可能性
- [ ] 共通インターフェースの提供
- [ ] ポリモーフィズムの実現
### 制約
- [ ] パフォーマンス要件
- [ ] ABI安定性要件
- [ ] 後方互換性要件
// Bad: 責任が多すぎる
protocol DataManager {
func fetch() async throws -> Data
func save(_ data: Data) async throws
func cache(_ data: Data)
func validate(_ data: Data) -> Bool
func transform(_ data: Data) -> ProcessedData
}
// Good: 責任を分離
protocol DataFetching {
func fetch() async throws -> Data
}
protocol DataPersisting {
func save(_ data: Data) async throws
}
protocol DataCaching {
func cache(_ data: Data)
func cached() -> Data?
}
// 必要に応じて合成
typealias DataRepository = DataFetching & DataPersisting
// または具体的な要件として定義
protocol DataRepository: DataFetching, DataPersisting {
// 追加のメソッドがあれば定義
}
protocol Identifiable {
var id: UUID { get }
}
extension Identifiable {
// デフォルト実装
var id: UUID { UUID() }
}
protocol Timestamped {
var createdAt: Date { get }
var updatedAt: Date { get }
}
extension Timestamped {
var isRecent: Bool {
updatedAt.timeIntervalSinceNow > -86400 // 24時間以内
}
}
// Collectionに対する拡張(要素がEquatableの場合のみ)
extension Collection where Element: Equatable {
func removingDuplicates() -> [Element] {
var seen: [Element] = []
return filter { element in
if seen.contains(element) {
return false
}
seen.append(element)
return true
}
}
}
// 特定の型に対する拡張
extension Array where Element == Int {
var sum: Int {
reduce(0, +)
}
}
protocol Repository {
associatedtype Entity
associatedtype ID: Hashable
func find(id: ID) async throws -> Entity?
func save(_ entity: Entity) async throws
func delete(id: ID) async throws
}
// 具体的な実装
struct UserRepository: Repository {
typealias Entity = User
typealias ID = UUID
func find(id: UUID) async throws -> User? {
// 実装
}
func save(_ entity: User) async throws {
// 実装
}
func delete(id: UUID) async throws {
// 実装
}
}
// Primary Associated Typeを指定
protocol Repository<Entity> {
associatedtype Entity
associatedtype ID: Hashable = UUID
func find(id: ID) async throws -> Entity?
func save(_ entity: Entity) async throws
}
// 使用側で型を明示できる
func processUsers(repository: some Repository<User>) async throws {
if let user = try await repository.find(id: UUID()) {
// userはUser型
}
}
// 存在型としても使用可能
let repositories: [any Repository<User>] = [
InMemoryUserRepository(),
RemoteUserRepository()
]
protocol NetworkClient: Sendable {
func request<T: Decodable>(_ endpoint: Endpoint) async throws -> T
}
final class UserService {
private let networkClient: NetworkClient
private let cache: UserCache
init(networkClient: NetworkClient, cache: UserCache) {
self.networkClient = networkClient
self.cache = cache
}
func fetchUser(id: UUID) async throws -> User {
if let cached = cache.get(id: id) {
return cached
}
let user: User = try await networkClient.request(.user(id: id))
cache.set(user, for: id)
return user
}
}
// プロトコル定義
protocol AnalyticsTracking: Sendable {
func track(event: AnalyticsEvent)
}
// 環境キー
struct AnalyticsKey: EnvironmentKey {
static let defaultValue: AnalyticsTracking = NoOpAnalytics()
}
extension EnvironmentValues {
var analytics: AnalyticsTracking {
get { self[AnalyticsKey.self] }
set { self[AnalyticsKey.self] = newValue }
}
}
// 使用
struct MyView: View {
@Environment(\.analytics) var analytics
var body: some View {
Button("Tap") {
analytics.track(event: .buttonTapped("MyButton"))
}
}
}
// プロダクションコード
protocol UserRepositoryProtocol {
func fetchUser(id: UUID) async throws -> User
func saveUser(_ user: User) async throws
}
final class UserRepository: UserRepositoryProtocol {
private let networkClient: NetworkClient
init(networkClient: NetworkClient) {
self.networkClient = networkClient
}
func fetchUser(id: UUID) async throws -> User {
try await networkClient.request(.user(id: id))
}
func saveUser(_ user: User) async throws {
try await networkClient.request(.saveUser(user))
}
}
// テストコード
final class MockUserRepository: UserRepositoryProtocol {
var fetchUserResult: Result<User, Error> = .success(.mock)
var savedUsers: [User] = []
func fetchUser(id: UUID) async throws -> User {
try fetchUserResult.get()
}
func saveUser(_ user: User) async throws {
savedUsers.append(user)
}
}
// テスト
@Test
func testUserService() async throws {
let mockRepository = MockUserRepository()
mockRepository.fetchUserResult = .success(User(name: "Test"))
let service = UserService(repository: mockRepository)
let user = try await service.getUser(id: UUID())
#expect(user.name == "Test")
}
# Protocol Design Document
## 概要
- 目的: ユーザーデータアクセスの抽象化
- スコープ: データ層
## プロトコル定義
### UserRepositoryProtocol
```swift
protocol UserRepositoryProtocol: Sendable {
func find(id: UUID) async throws -> User?
func save(_ user: User) async throws
func delete(id: UUID) async throws
func findAll() async throws -> [User]
}
| 項目 | 判断 | 理由 |
|---|---|---|
| Associated Type使用 | No | 単一のEntity型のみ使用 |
| Sendable要件 | Yes | 非同期コンテキストで使用 |
| Extension | Yes | findAllのデフォルト実装 |
UserService
└── UserRepositoryProtocol
├── CoreDataUserRepository
├── InMemoryUserRepository (テスト用)
└── RemoteUserRepository
final class CoreDataUserRepository: UserRepositoryProtocol {
// ...
}
final class MockUserRepository: UserRepositoryProtocol {
// ...
}
## ガードレール
### 設計原則
- Interface Segregation: クライアントが使わないメソッドに依存させない
- Dependency Inversion: 具体ではなく抽象に依存する
- Protocol Composition: 大きなプロトコルより小さなプロトコルの合成
### 避けるべきパターン
- 過度な抽象化(1つの実装しかないプロトコル)
- Fat Protocol(メソッドが多すぎる)
- Protocol Witnessテーブルのオーバーヘッドを無視した設計
### 注意点
- プロトコルは値型で使う場合はexistentialのオーバーヘッドに注意
- `some Protocol`(opaque type)と`any Protocol`(existential)の使い分け
- Swift 6のStrict Concurrency対応(Sendable要件)
## 関連スキル
- `swift-code-review`: コードレビュー
- `swift-concurrency`: 並行処理対応
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.