In-Memory File System
A directory tree in memory - mkdir, touch, ls, cat - where a directory and a file
answer to the same interface for everything except "what's actually inside you." This is
Composite, played completely straight.
Requirements
Functional
- Create a file with content at a given path, creating any missing parent directories.
- Create a directory at a given path.
- List the immediate children of a directory.
- Read a file's content by path; read a directory's total size as the sum of everything under it.
- Delete a file or a directory (and everything inside it).
Non-functional
- Path resolution (
/a/b/c) should walk the tree node by node, not string-match against a flat table of every path ever created. - Computing a directory's size must not require a separate "keep this in sync" step scattered across every mutation - it should fall out of composing the sizes of its children.
Design
FileSystemNode is the interface both File and Directory implement; anything that
walks the tree (size, listing, deletion) calls the same methods on whichever kind of node
it's holding and never checks "is this a file or a directory" itself. Directory holds a
map of child name to FileSystemNode, so a subdirectory is stored exactly like a file -
just a child that happens to have children of its own.
- 1The caller gives a full path - the filesystem walks it, the caller never touches a node directly.
- 2Each missing segment along the path becomes a new Directory, created on demand.
- 3The final directory in the path gets the new File added as a named child.
- 4Asking a directory for its size and asking a file for its size use the exact same method name.
- 5A directory sums the size() of every child without knowing whether that child is a file or another directory.
Path resolution is a fold over path segments: start at the root, and for each segment ask the current directory for that named child, moving one level deeper each time - there's no separate index of "every path that exists."
Class diagram
Code
Design decisions
FileSystemNodeis the Composite interface -Fileis a leaf,Directorycomposes more nodes.size()on a file returns its content length;size()on a directory sums its children'ssize()calls, whatever those children are. Neither implementation needs to know the other exists, which is the entire point of Composite: the recursive structure lives in the interface's contract, not in a type-switching helper function.Directorystores children in a name-keyed map, not a list scanned by name on every lookup. Path resolution touches one map lookup per segment instead of a linear scan per level, which matters once a directory has more than a handful of entries.- Path resolution walks the tree, it never consults a separate path-to-node table. A flat table would need updating on every create, move, and delete and could drift from the tree it's supposed to describe; walking the tree from the root means there is exactly one source of truth for what exists.
- What's missing for a real system: creating missing parent directories on
touchis handled here as an eagermkdir -p; a real filesystem would also need move/rename (relinking a node under a new parent without recreating it, to keep it cheap regardless of subtree size) and permission checks per node, both of which are out of scope for the composite structure itself.