npx claudepluginhub caphtech/claude-marketplace --plugin mobile-pluginWant just this skill?
Then install: npx claudepluginhub u/[userId]/[slug]
プロトコル指向設計の支援。Protocol拡張、Associated Types設計、依存性注入パターンを提案。「プロトコルを設計して」「依存性注入を実装して」で使用。
This skill uses the workspace's default tool permissions.
references/dependency-injection-guide.mdreferences/protocol-patterns.mdSwift Protocol
Swiftのプロトコル指向プログラミングを活用した設計を支援する。
概要
プロトコル指向設計に対して以下の観点で支援を実施:
- Protocol定義とExtensionの活用
- Associated Types(関連型)の設計
- Primary Associated Typesの活用(Swift 5.7+)
- プロトコルによる依存性注入
- テスタビリティの確保
実行条件
- 抽象化が必要な設計時
- テスト容易性の向上が必要な時
- 依存性注入パターンの実装時
- 既存クラス継承の見直し時
プロセス
Step 1: 要件分析
## 抽象化の目的
### 動機
- [ ] テスタビリティ向上
- [ ] 実装の交換可能性
- [ ] 共通インターフェースの提供
- [ ] ポリモーフィズムの実現
### 制約
- [ ] パフォーマンス要件
- [ ] ABI安定性要件
- [ ] 後方互換性要件
Step 2: Protocol設計
単一責任の原則
// 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 {
// 追加のメソッドがあれば定義
}
Step 3: Protocol Extension活用
デフォルト実装
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時間以内
}
}
条件付きExtension
// 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, +)
}
}
Step 4: Associated Types設計
基本的なAssociated Type
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 Types (Swift 5.7+)
// 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()
]
Step 5: 依存性注入パターン
コンストラクタ注入
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
}
}
環境による注入(SwiftUI)
// プロトコル定義
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"))
}
}
}
Step 6: テスタビリティ確保
モック可能な設計
// プロダクションコード
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`: 並行処理対応
Similar Skills
Expert guidance for Next.js Cache Components and Partial Prerendering (PPR). **PROACTIVE ACTIVATION**: Use this skill automatically when working in Next.js projects that have `cacheComponents: true` in their next.config.ts/next.config.js. When this config is detected, proactively apply Cache Components patterns and best practices to all React Server Component implementations. **DETECTION**: At the start of a session in a Next.js project, check for `cacheComponents: true` in next.config. If enabled, this skill's patterns should guide all component authoring, data fetching, and caching decisions. **USE CASES**: Implementing 'use cache' directive, configuring cache lifetimes with cacheLife(), tagging cached data with cacheTag(), invalidating caches with updateTag()/revalidateTag(), optimizing static vs dynamic content boundaries, debugging cache issues, and reviewing Cache Component implementations.
Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.