state-machine-design
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseState Machine Design Skill
状态机设计Skill
When to Use This Skill
何时使用该Skill
Use this skill when:
- State Machine Design tasks - Working on statechart and state machine modeling for lifecycle and behavior specification
- Planning or design - Need guidance on State Machine Design approaches
- Best practices - Want to follow established patterns and standards
在以下场景使用该Skill:
- 状态机设计任务 - 进行用于生命周期和行为规范的状态图与状态机建模工作
- 规划或设计阶段 - 需要State Machine设计方法的指导
- 最佳实践 - 希望遵循既定的模式与标准
Overview
概述
Design finite state machines and statecharts for modeling entity lifecycles, workflows, and system behavior.
设计有限状态机与状态图,用于建模实体生命周期、工作流及系统行为。
MANDATORY: Documentation-First Approach
强制要求:文档优先方法
Before designing state machines:
- Invoke skill for state machine patterns
docs-management - Verify implementation patterns via MCP servers (context7 for XState, etc.)
- Base all guidance on Harel statechart semantics
在设计状态机之前:
- 调用Skill获取状态机模式
docs-management - 通过MCP服务器验证实现模式(如XState使用context7等)
- 所有指导均基于Harel状态图语义
State Machine Concepts
状态机概念
Core Elements
核心元素
| Element | Description | Example |
|---|---|---|
| State | Condition the system can be in | |
| Transition | Change from one state to another | |
| Event | Trigger for a transition | |
| Guard | Condition that must be true | |
| Action | Side effect on transition | |
| Entry Action | Action when entering state | |
| Exit Action | Action when leaving state | |
| 元素 | 描述 | 示例 |
|---|---|---|
| State | 系统可处于的状态 | |
| Transition | 状态间的转换 | |
| Event | 触发状态转换的事件 | |
| Guard | 转换必须满足的条件 | |
| Action | 转换时的副作用动作 | |
| Entry Action | 进入状态时执行的动作 | |
| Exit Action | 离开状态时执行的动作 | |
State Types
状态类型
csharp
public enum StateType
{
Initial, // Starting state (filled circle)
Normal, // Regular state
Final, // End state (circle with border)
Composite, // Contains sub-states
Parallel, // Concurrent regions
History, // Remember last sub-state
Choice // Decision point
}csharp
public enum StateType
{
Initial, // Starting state (filled circle)
Normal, // Regular state
Final, // End state (circle with border)
Composite, // Contains sub-states
Parallel, // Concurrent regions
History, // Remember last sub-state
Choice // Decision point
}State Machine Notation
状态机表示法
PlantUML Syntax
PlantUML语法
plantuml
@startuml
title Order State Machine
[*] --> Draft : Create
state Draft {
Draft : entry / initializeOrder
Draft : exit / validateOrder
}
Draft --> Submitted : Submit [hasItems]
Draft --> Cancelled : Cancel
state Submitted {
Submitted : entry / reserveInventory
}
Submitted --> Paid : ProcessPayment [paymentValid]
Submitted --> Cancelled : Cancel / releaseInventory
Submitted --> Draft : RequireChanges
state Paid {
Paid : entry / confirmInventory
}
Paid --> Shipped : Ship
Paid --> Refunded : Refund
state Shipped {
Shipped : entry / sendTrackingNotification
}
Shipped --> Delivered : Deliver
Shipped --> Returned : Return
Delivered --> Completed : Finalize
Delivered --> Returned : Return
Returned --> Refunded : ProcessReturn
Completed --> [*]
Refunded --> [*]
Cancelled --> [*]
@endumlplantuml
@startuml
title Order State Machine
[*] --> Draft : Create
state Draft {
Draft : entry / initializeOrder
Draft : exit / validateOrder
}
Draft --> Submitted : Submit [hasItems]
Draft --> Cancelled : Cancel
state Submitted {
Submitted : entry / reserveInventory
}
Submitted --> Paid : ProcessPayment [paymentValid]
Submitted --> Cancelled : Cancel / releaseInventory
Submitted --> Draft : RequireChanges
state Paid {
Paid : entry / confirmInventory
}
Paid --> Shipped : Ship
Paid --> Refunded : Refund
state Shipped {
Shipped : entry / sendTrackingNotification
}
Shipped --> Delivered : Deliver
Shipped --> Returned : Return
Delivered --> Completed : Finalize
Delivered --> Returned : Return
Returned --> Refunded : ProcessReturn
Completed --> [*]
Refunded --> [*]
Cancelled --> [*]
@endumlMermaid Syntax
Mermaid语法
mermaid
stateDiagram-v2
[*] --> Draft : Create
state Draft {
direction LR
[*] --> Empty
Empty --> HasItems : AddItem
HasItems --> HasItems : AddItem
HasItems --> Empty : RemoveLastItem
}
Draft --> Submitted : Submit
Draft --> Cancelled : Cancel
Submitted --> Paid : PaymentReceived
Submitted --> Cancelled : Cancel
Submitted --> Draft : RequireChanges
Paid --> Shipped : Ship
Paid --> Refunded : Refund
Shipped --> Delivered : Deliver
Shipped --> Returned : Return
Delivered --> Completed : Finalize
Delivered --> Returned : Return
Returned --> Refunded : ProcessReturn
Completed --> [*]
Refunded --> [*]
Cancelled --> [*]mermaid
stateDiagram-v2
[*] --> Draft : Create
state Draft {
direction LR
[*] --> Empty
Empty --> HasItems : AddItem
HasItems --> HasItems : AddItem
HasItems --> Empty : RemoveLastItem
}
Draft --> Submitted : Submit
Draft --> Cancelled : Cancel
Submitted --> Paid : PaymentReceived
Submitted --> Cancelled : Cancel
Submitted --> Draft : RequireChanges
Paid --> Shipped : Ship
Paid --> Refunded : Refund
Shipped --> Delivered : Deliver
Shipped --> Returned : Return
Delivered --> Completed : Finalize
Delivered --> Returned : Return
Returned --> Refunded : ProcessReturn
Completed --> [*]
Refunded --> [*]
Cancelled --> [*]C# Implementation Patterns
C#实现模式
Simple State Machine
简单状态机
csharp
public sealed class Order : Entity
{
public OrderStatus Status { get; private set; }
private static readonly Dictionary<(OrderStatus From, OrderEvent Event), OrderStatus> _transitions =
new()
{
{ (OrderStatus.Draft, OrderEvent.Submit), OrderStatus.Submitted },
{ (OrderStatus.Draft, OrderEvent.Cancel), OrderStatus.Cancelled },
{ (OrderStatus.Submitted, OrderEvent.Pay), OrderStatus.Paid },
{ (OrderStatus.Submitted, OrderEvent.Cancel), OrderStatus.Cancelled },
{ (OrderStatus.Submitted, OrderEvent.RequireChanges), OrderStatus.Draft },
{ (OrderStatus.Paid, OrderEvent.Ship), OrderStatus.Shipped },
{ (OrderStatus.Paid, OrderEvent.Refund), OrderStatus.Refunded },
{ (OrderStatus.Shipped, OrderEvent.Deliver), OrderStatus.Delivered },
{ (OrderStatus.Shipped, OrderEvent.Return), OrderStatus.Returned },
{ (OrderStatus.Delivered, OrderEvent.Finalize), OrderStatus.Completed },
{ (OrderStatus.Delivered, OrderEvent.Return), OrderStatus.Returned },
{ (OrderStatus.Returned, OrderEvent.ProcessReturn), OrderStatus.Refunded },
};
public Result Transition(OrderEvent @event)
{
if (!_transitions.TryGetValue((Status, @event), out var newStatus))
{
return Result.Failure($"Cannot {@event} order in {Status} status");
}
var oldStatus = Status;
Status = newStatus;
AddDomainEvent(new OrderStatusChangedEvent(Id, oldStatus, newStatus, @event));
return Result.Success();
}
}
public enum OrderStatus
{
Draft, Submitted, Paid, Shipped, Delivered, Completed, Cancelled, Returned, Refunded
}
public enum OrderEvent
{
Submit, Cancel, Pay, RequireChanges, Ship, Refund, Deliver, Return, Finalize, ProcessReturn
}csharp
public sealed class Order : Entity
{
public OrderStatus Status { get; private set; }
private static readonly Dictionary<(OrderStatus From, OrderEvent Event), OrderStatus> _transitions =
new()
{
{ (OrderStatus.Draft, OrderEvent.Submit), OrderStatus.Submitted },
{ (OrderStatus.Draft, OrderEvent.Cancel), OrderStatus.Cancelled },
{ (OrderStatus.Submitted, OrderEvent.Pay), OrderStatus.Paid },
{ (OrderStatus.Submitted, OrderEvent.Cancel), OrderStatus.Cancelled },
{ (OrderStatus.Submitted, OrderEvent.RequireChanges), OrderStatus.Draft },
{ (OrderStatus.Paid, OrderEvent.Ship), OrderStatus.Shipped },
{ (OrderStatus.Paid, OrderEvent.Refund), OrderStatus.Refunded },
{ (OrderStatus.Shipped, OrderEvent.Deliver), OrderStatus.Delivered },
{ (OrderStatus.Shipped, OrderEvent.Return), OrderStatus.Returned },
{ (OrderStatus.Delivered, OrderEvent.Finalize), OrderStatus.Completed },
{ (OrderStatus.Delivered, OrderEvent.Return), OrderStatus.Returned },
{ (OrderStatus.Returned, OrderEvent.ProcessReturn), OrderStatus.Refunded },
};
public Result Transition(OrderEvent @event)
{
if (!_transitions.TryGetValue((Status, @event), out var newStatus))
{
return Result.Failure($"Cannot {@event} order in {Status} status");
}
var oldStatus = Status;
Status = newStatus;
AddDomainEvent(new OrderStatusChangedEvent(Id, oldStatus, newStatus, @event));
return Result.Success();
}
}
public enum OrderStatus
{
Draft, Submitted, Paid, Shipped, Delivered, Completed, Cancelled, Returned, Refunded
}
public enum OrderEvent
{
Submit, Cancel, Pay, RequireChanges, Ship, Refund, Deliver, Return, Finalize, ProcessReturn
}State Pattern Implementation
状态模式实现
csharp
public abstract class OrderState
{
public abstract OrderStatus Status { get; }
public virtual Result Submit(Order order) =>
Result.Failure($"Cannot submit order in {Status} state");
public virtual Result Cancel(Order order) =>
Result.Failure($"Cannot cancel order in {Status} state");
public virtual Result Pay(Order order) =>
Result.Failure($"Cannot pay order in {Status} state");
public virtual Result Ship(Order order) =>
Result.Failure($"Cannot ship order in {Status} state");
protected void TransitionTo(Order order, OrderState newState)
{
order.SetState(newState);
}
}
public sealed class DraftState : OrderState
{
public override OrderStatus Status => OrderStatus.Draft;
public override Result Submit(Order order)
{
if (!order.HasItems)
return Result.Failure("Order must have items to submit");
TransitionTo(order, new SubmittedState());
order.ReserveInventory();
return Result.Success();
}
public override Result Cancel(Order order)
{
TransitionTo(order, new CancelledState());
return Result.Success();
}
}
public sealed class SubmittedState : OrderState
{
public override OrderStatus Status => OrderStatus.Submitted;
public override Result Pay(Order order)
{
TransitionTo(order, new PaidState());
order.ConfirmInventory();
return Result.Success();
}
public override Result Cancel(Order order)
{
order.ReleaseInventory();
TransitionTo(order, new CancelledState());
return Result.Success();
}
}csharp
public abstract class OrderState
{
public abstract OrderStatus Status { get; }
public virtual Result Submit(Order order) =>
Result.Failure($"Cannot submit order in {Status} state");
public virtual Result Cancel(Order order) =>
Result.Failure($"Cannot cancel order in {Status} state");
public virtual Result Pay(Order order) =>
Result.Failure($"Cannot pay order in {Status} state");
public virtual Result Ship(Order order) =>
Result.Failure($"Cannot ship order in {Status} state");
protected void TransitionTo(Order order, OrderState newState)
{
order.SetState(newState);
}
}
public sealed class DraftState : OrderState
{
public override OrderStatus Status => OrderStatus.Draft;
public override Result Submit(Order order)
{
if (!order.HasItems)
return Result.Failure("Order must have items to submit");
TransitionTo(order, new SubmittedState());
order.ReserveInventory();
return Result.Success();
}
public override Result Cancel(Order order)
{
TransitionTo(order, new CancelledState());
return Result.Success();
}
}
public sealed class SubmittedState : OrderState
{
public override OrderStatus Status => OrderStatus.Submitted;
public override Result Pay(Order order)
{
TransitionTo(order, new PaidState());
order.ConfirmInventory();
return Result.Success();
}
public override Result Cancel(Order order)
{
order.ReleaseInventory();
TransitionTo(order, new CancelledState());
return Result.Success();
}
}Stateless Library Pattern
Stateless库模式
csharp
using Stateless;
public sealed class OrderStateMachine
{
private readonly StateMachine<OrderStatus, OrderEvent> _machine;
private readonly Order _order;
public OrderStateMachine(Order order)
{
_order = order;
_machine = new StateMachine<OrderStatus, OrderEvent>(
() => order.Status,
status => order.SetStatus(status));
ConfigureTransitions();
}
private void ConfigureTransitions()
{
_machine.Configure(OrderStatus.Draft)
.Permit(OrderEvent.Submit, OrderStatus.Submitted)
.Permit(OrderEvent.Cancel, OrderStatus.Cancelled)
.OnEntry(() => _order.InitializeOrder());
_machine.Configure(OrderStatus.Submitted)
.PermitIf(OrderEvent.Pay, OrderStatus.Paid,
() => _order.PaymentIsValid)
.Permit(OrderEvent.Cancel, OrderStatus.Cancelled)
.Permit(OrderEvent.RequireChanges, OrderStatus.Draft)
.OnEntry(() => _order.ReserveInventory())
.OnExit(() => { /* cleanup if needed */ });
_machine.Configure(OrderStatus.Paid)
.Permit(OrderEvent.Ship, OrderStatus.Shipped)
.Permit(OrderEvent.Refund, OrderStatus.Refunded)
.OnEntry(() => _order.ConfirmInventory());
_machine.Configure(OrderStatus.Shipped)
.Permit(OrderEvent.Deliver, OrderStatus.Delivered)
.Permit(OrderEvent.Return, OrderStatus.Returned)
.OnEntry(() => _order.SendTrackingNotification());
_machine.Configure(OrderStatus.Delivered)
.Permit(OrderEvent.Finalize, OrderStatus.Completed)
.Permit(OrderEvent.Return, OrderStatus.Returned);
_machine.Configure(OrderStatus.Returned)
.Permit(OrderEvent.ProcessReturn, OrderStatus.Refunded);
// Terminal states
_machine.Configure(OrderStatus.Completed);
_machine.Configure(OrderStatus.Cancelled);
_machine.Configure(OrderStatus.Refunded);
}
public bool CanFire(OrderEvent trigger) => _machine.CanFire(trigger);
public void Fire(OrderEvent trigger) => _machine.Fire(trigger);
public IEnumerable<OrderEvent> GetPermittedTriggers() =>
_machine.GetPermittedTriggers();
}csharp
using Stateless;
public sealed class OrderStateMachine
{
private readonly StateMachine<OrderStatus, OrderEvent> _machine;
private readonly Order _order;
public OrderStateMachine(Order order)
{
_order = order;
_machine = new StateMachine<OrderStatus, OrderEvent>(
() => order.Status,
status => order.SetStatus(status));
ConfigureTransitions();
}
private void ConfigureTransitions()
{
_machine.Configure(OrderStatus.Draft)
.Permit(OrderEvent.Submit, OrderStatus.Submitted)
.Permit(OrderEvent.Cancel, OrderStatus.Cancelled)
.OnEntry(() => _order.InitializeOrder());
_machine.Configure(OrderStatus.Submitted)
.PermitIf(OrderEvent.Pay, OrderStatus.Paid,
() => _order.PaymentIsValid)
.Permit(OrderEvent.Cancel, OrderStatus.Cancelled)
.Permit(OrderEvent.RequireChanges, OrderStatus.Draft)
.OnEntry(() => _order.ReserveInventory())
.OnExit(() => { /* cleanup if needed */ });
_machine.Configure(OrderStatus.Paid)
.Permit(OrderEvent.Ship, OrderStatus.Shipped)
.Permit(OrderEvent.Refund, OrderStatus.Refunded)
.OnEntry(() => _order.ConfirmInventory());
_machine.Configure(OrderStatus.Shipped)
.Permit(OrderEvent.Deliver, OrderStatus.Delivered)
.Permit(OrderEvent.Return, OrderStatus.Returned)
.OnEntry(() => _order.SendTrackingNotification());
_machine.Configure(OrderStatus.Delivered)
.Permit(OrderEvent.Finalize, OrderStatus.Completed)
.Permit(OrderEvent.Return, OrderStatus.Returned);
_machine.Configure(OrderStatus.Returned)
.Permit(OrderEvent.ProcessReturn, OrderStatus.Refunded);
// Terminal states
_machine.Configure(OrderStatus.Completed);
_machine.Configure(OrderStatus.Cancelled);
_machine.Configure(OrderStatus.Refunded);
}
public bool CanFire(OrderEvent trigger) => _machine.CanFire(trigger);
public void Fire(OrderEvent trigger) => _machine.Fire(trigger);
public IEnumerable<OrderEvent> GetPermittedTriggers() =>
_machine.GetPermittedTriggers();
}XState Pattern (TypeScript)
XState模式(TypeScript)
typescript
import { createMachine, assign } from 'xstate';
interface OrderContext {
items: LineItem[];
customerId: string;
paymentId?: string;
trackingNumber?: string;
}
type OrderEvent =
| { type: 'ADD_ITEM'; item: LineItem }
| { type: 'REMOVE_ITEM'; itemId: string }
| { type: 'SUBMIT' }
| { type: 'PAY'; paymentId: string }
| { type: 'CANCEL' }
| { type: 'SHIP'; trackingNumber: string }
| { type: 'DELIVER' }
| { type: 'RETURN' }
| { type: 'REFUND' };
const orderMachine = createMachine({
id: 'order',
initial: 'draft',
context: {
items: [],
customerId: '',
} as OrderContext,
states: {
draft: {
entry: 'initializeOrder',
on: {
ADD_ITEM: {
actions: assign({
items: ({ context, event }) => [...context.items, event.item],
}),
},
REMOVE_ITEM: {
actions: assign({
items: ({ context, event }) =>
context.items.filter(i => i.id !== event.itemId),
}),
},
SUBMIT: {
target: 'submitted',
guard: 'hasItems',
},
CANCEL: 'cancelled',
},
},
submitted: {
entry: 'reserveInventory',
exit: 'onSubmittedExit',
on: {
PAY: {
target: 'paid',
guard: 'paymentValid',
actions: assign({
paymentId: ({ event }) => event.paymentId,
}),
},
CANCEL: {
target: 'cancelled',
actions: 'releaseInventory',
},
},
},
paid: {
entry: 'confirmInventory',
on: {
SHIP: {
target: 'shipped',
actions: assign({
trackingNumber: ({ event }) => event.trackingNumber,
}),
},
REFUND: 'refunded',
},
},
shipped: {
entry: 'sendTrackingNotification',
on: {
DELIVER: 'delivered',
RETURN: 'returned',
},
},
delivered: {
on: {
RETURN: 'returned',
},
after: {
// Auto-complete after 14 days
'14d': 'completed',
},
},
returned: {
on: {
REFUND: 'refunded',
},
},
completed: { type: 'final' },
cancelled: { type: 'final' },
refunded: { type: 'final' },
},
}, {
guards: {
hasItems: ({ context }) => context.items.length > 0,
paymentValid: ({ event }) => event.type === 'PAY' && !!event.paymentId,
},
actions: {
initializeOrder: () => console.log('Order initialized'),
reserveInventory: ({ context }) =>
console.log(`Reserving ${context.items.length} items`),
confirmInventory: () => console.log('Inventory confirmed'),
releaseInventory: () => console.log('Inventory released'),
sendTrackingNotification: ({ context }) =>
console.log(`Tracking: ${context.trackingNumber}`),
},
});typescript
import { createMachine, assign } from 'xstate';
interface OrderContext {
items: LineItem[];
customerId: string;
paymentId?: string;
trackingNumber?: string;
}
type OrderEvent =
| { type: 'ADD_ITEM'; item: LineItem }
| { type: 'REMOVE_ITEM'; itemId: string }
| { type: 'SUBMIT' }
| { type: 'PAY'; paymentId: string }
| { type: 'CANCEL' }
| { type: 'SHIP'; trackingNumber: string }
| { type: 'DELIVER' }
| { type: 'RETURN' }
| { type: 'REFUND' };
const orderMachine = createMachine({
id: 'order',
initial: 'draft',
context: {
items: [],
customerId: '',
} as OrderContext,
states: {
draft: {
entry: 'initializeOrder',
on: {
ADD_ITEM: {
actions: assign({
items: ({ context, event }) => [...context.items, event.item],
}),
},
REMOVE_ITEM: {
actions: assign({
items: ({ context, event }) =>
context.items.filter(i => i.id !== event.itemId),
}),
},
SUBMIT: {
target: 'submitted',
guard: 'hasItems',
},
CANCEL: 'cancelled',
},
},
submitted: {
entry: 'reserveInventory',
exit: 'onSubmittedExit',
on: {
PAY: {
target: 'paid',
guard: 'paymentValid',
actions: assign({
paymentId: ({ event }) => event.paymentId,
}),
},
CANCEL: {
target: 'cancelled',
actions: 'releaseInventory',
},
},
},
paid: {
entry: 'confirmInventory',
on: {
SHIP: {
target: 'shipped',
actions: assign({
trackingNumber: ({ event }) => event.trackingNumber,
}),
},
REFUND: 'refunded',
},
},
shipped: {
entry: 'sendTrackingNotification',
on: {
DELIVER: 'delivered',
RETURN: 'returned',
},
},
delivered: {
on: {
RETURN: 'returned',
},
after: {
// Auto-complete after 14 days
'14d': 'completed',
},
},
returned: {
on: {
REFUND: 'refunded',
},
},
completed: { type: 'final' },
cancelled: { type: 'final' },
refunded: { type: 'final' },
},
}, {
guards: {
hasItems: ({ context }) => context.items.length > 0,
paymentValid: ({ event }) => event.type === 'PAY' && !!event.paymentId,
},
actions: {
initializeOrder: () => console.log('Order initialized'),
reserveInventory: ({ context }) =>
console.log(`Reserving ${context.items.length} items`),
confirmInventory: () => console.log('Inventory confirmed'),
releaseInventory: () => console.log('Inventory released'),
sendTrackingNotification: ({ context }) =>
console.log(`Tracking: ${context.trackingNumber}`),
},
});Design Best Practices
设计最佳实践
State Design Guidelines
状态设计指南
- Name states as conditions: not
SubmittedSubmit - Name events as commands: not
SubmitSubmitted - Use guards for conditional transitions
- Keep states atomic: One responsibility per state
- Document entry/exit actions
- Consider terminal states (final states)
- 将状态命名为条件:使用(已提交)而非
Submitted(提交)Submit - 将事件命名为命令:使用(提交)而非
Submit(已提交)Submitted - 使用守卫条件实现条件转换
- 保持状态原子性:每个状态仅承担单一职责
- 记录进入/退出动作
- 考虑终止状态(最终状态)
Common Patterns
常见模式
| Pattern | Use Case |
|---|---|
| Linear | Simple sequential flow |
| Choice | Conditional branching |
| Parallel | Concurrent activities |
| Hierarchical | Complex nested states |
| History | Resume from last state |
| 模式 | 适用场景 |
|---|---|
| Linear(线性) | 简单的顺序流程 |
| Choice(选择) | 条件分支场景 |
| Parallel(并行) | 并发活动场景 |
| Hierarchical(分层) | 复杂嵌套状态场景 |
| History(历史) | 需要从上次状态恢复的场景 |
Workflow
工作流程
When designing state machines:
- Identify entity: What has the lifecycle?
- List states: What conditions can it be in?
- Define events: What triggers state changes?
- Map transitions: State + Event → New State
- Add guards: What conditions must be true?
- Define actions: What happens on transitions?
- Draw diagram: Visualize for review
- Implement: Choose appropriate pattern
设计状态机时遵循以下流程:
- 确定实体:哪个对象具有生命周期?
- 列出状态:该实体可处于哪些状态?
- 定义事件:哪些事件会触发状态变更?
- 映射转换关系:状态 + 事件 → 新状态
- 添加守卫条件:转换必须满足哪些条件?
- 定义动作:转换时会执行哪些操作?
- 绘制示意图:可视化以便评审
- 实现:选择合适的实现模式
References
参考资料
For detailed guidance:
Last Updated: 2025-12-26
如需详细指导:
最后更新时间: 2025-12-26