Design Patterns¶
This document catalogs the essential design patterns used in Viper Runtime, focusing on the patterns that define its architecture.
Overview¶
Viper employs a consistent set of design patterns across its 20 domains and ~570 .hpp files. These patterns ensure type safety, memory safety, and maintainability.
Category |
Count |
Most Common |
|---|---|---|
Creational |
6 |
Token Pattern, Factory Method |
Structural |
9 |
Adapter, Composite, Facade |
Behavioral |
11 |
Strategy, Template Method, Command |
Viper-Specific |
5 |
Metadata Everywhere, Content-Addressable |
Token Constructor Pattern¶
Intent: Force all construction through factory methods.
A private struct Token{} parameter prevents direct construction while still allowing
std::make_shared:
class ValueDouble : public Value {
public:
struct Token {};
ValueDouble(Token, double value); // Cannot call directly
static std::shared_ptr<ValueDouble> make(double value) {
return std::make_shared<ValueDouble>(Token{}, value);
}
};
Benefits:
Prevents accidental stack allocation
Guarantees
shared_ptrownershipEnables validation in factory before construction
Self-documenting API - only
make()is usable
Used In: All 21 domains
Content-Addressable Storage¶
Intent: Identify objects by cryptographic hash of their content.
// BlobId is computed from layout + blob data using SHA-1
BlobId blobId{layout, blob}; // Constructor computes hash
blobId.hexString(); // -> "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3"
blobId.isValid(); // -> true (not Invalid)
Identifier |
Hash |
Content |
Size |
|---|---|---|---|
BlobId |
SHA-1 |
layout + data |
20 bytes |
CommitId |
SHA-1 |
serialized header |
20 bytes |
RuntimeId |
MD5 |
type structure |
16 bytes |
Benefits:
Automatic deduplication
Integrity verification
Distributed consistency
Used In: Blob System, Commit System, Type System
TypeCode Dispatch¶
Intent: Fast polymorphic dispatch without C++ RTTI.
Instead of dynamic_cast, Viper uses TypeCode enum with switch statements:
void serialize(std::shared_ptr<Value const> const & value, StreamWriting & writer) {
switch (value->typeCode()) { // Virtual method on Value
case TypeCode::Int32:
writer.writeInt32(ValueInt32::cast(value)->value);
break;
case TypeCode::Double:
writer.writeDouble(ValueDouble::cast(value)->value);
break;
// ... 33 cases total
}
}
Benefits:
Switch compiles to jump table (O(1))
Works with
-fno-rtticompiler flagCompiler warns on missing cases
TypeCode transmitted via RPC
Used In: All serialization, encoding, path navigation
Strategy Pattern¶
Intent: Define a family of algorithms, encapsulate each one, and make them interchangeable.
// Interface
class StreamCodecInstancing {
virtual std::shared_ptr<StreamEncoding> createEncoder() const = 0;
virtual std::shared_ptr<StreamDecoding> createDecoder(Blob const & blob) const = 0;
};
// Implementations
class StreamBinaryCodec : public StreamCodecInstancing { ... };
class StreamTokenBinaryCodec : public StreamCodecInstancing { ... };
class StreamRawCodec : public StreamCodecInstancing { ... };
Used In:
Codecs (
StreamCodecInstancing)Hashing (
Hashinginterface)Database (
Databasinginterface)Storage (
ValueVectorStoring)
Interface Segregation (Read/Write)¶
Intent: Separate reading and writing operations into distinct interfaces.
Read Interface |
Write Interface |
Implementations |
|---|---|---|
|
|
Database, DatabaseRemote |
|
|
Database |
|
|
BinaryReader, BinaryWriter, Hasher |
|
|
CommitState, CommitMutableState |
Benefits:
Read-only views can be shared safely
Multiple implementations (SQLite, Remote, InMemory)
Easier testing with mock read interfaces
RAII (Resource Acquisition Is Initialization)¶
Intent: Tie resource lifetime to object lifetime.
class SQLite {
public:
SQLite(Token, const string& path) {
sqlite3_open(path.c_str(), &_db);
}
~SQLite() {
sqlite3_close(_db); // Automatic cleanup
}
private:
sqlite3* _db;
};
Benefits:
No resource leaks (destructor always called)
Exception safety (cleanup on stack unwinding)
Simplified code (no manual cleanup needed)
Used In: SQLite, Socket, Semaphore, SharedMemory
Facade Pattern¶
Intent: Provide unified interface to a complex subsystem.
// High-level facade
class Database {
public:
void set(Attachment, Key, Value);
optional<Value> get(Attachment, Key);
// ... simple API
private:
shared_ptr<Databasing> _databasing; // Delegates to implementation
};
Facade |
Hides |
|---|---|
|
SQLite tables, transactions |
|
64 commit-related classes |
|
RPC connection, serialization |
Command Pattern¶
Intent: Encapsulate requests as objects for serialization and replay.
The program uses 10 opcodes types:
Opcodes |
Description |
|---|---|
|
Replace entire document |
|
Update at path |
|
Add elements to set |
|
Remove elements from set |
|
Add/update map entries |
|
Remove map keys |
|
Update existing keys only |
|
Insert at position |
|
Update at position |
|
Remove at position |
Each command is:
Serializable (for persistence)
Replayable (for state reconstruction)
Composable (in ValueProgram collections)
Type-Value Duality¶
Intent: For each Type class, provide a corresponding Value class.
Type |
Value |
|---|---|
TypeDouble |
ValueDouble |
TypeVector |
ValueVector |
TypeMap |
ValueMap |
TypeStructure |
ValueStructure |
TypeKey |
ValueKey |
This pattern ensures:
Type describes structure
Value holds actual data
Automatic serialization via type metadata
Fluent Interface¶
Intent: Enable method chaining for readable construction.
auto path = Path::make()
->field("user")
->index(0)
->field("name");
Each method returns shared_from_this() for chaining.
Used In: Path System
Platform Abstraction¶
Intent: Hide platform differences behind a common interface.
#ifdef _WIN32
SOCKET _socket;
GUID _uuid;
#else
int _socket;
uuid_t _uuid;
#endif
Used In: IPC (Socket, Semaphore, SharedMemory), Utilities (UUId)
See Also¶
Philosophy - Metadata Everywhere principle
Type System - TypeCode dispatch
Architecture - Domain overview
Glossary - Pattern terminology