๐ต๏ธ Proxy: Smart Lazy-Loading Video Player
๐ Overview
The Proxy Pattern provides a surrogate or placeholder for another object to control access to it. It is commonly used to delay the creation of "expensive" objects until they are absolutely necessary, a technique known as lazy loading, or to add a layer of security and logging around a sensitive resource.
Core Concepts
- Subject Interface: The common interface shared by both the Proxy and the Real Subject, ensuring transparency for the client.
- Real Subject: The heavy or sensitive object that the proxy represents (e.g., a
HighResVideoor aDatabaseConnection). - Proxy: The lightweight object that manages access to the Real Subject.
- Virtual Proxy: A specific type of proxy used for lazy loading of resource-intensive objects.
๐ญ The Engineering Story & Problem
๐ก The Villain (The Problem)
The "Startup Crash" โ a video library application that attempts to load 100 high-definition video files (totalling 50GB) into RAM as soon as the app opens. The system freezes, the memory is exhausted, and the app crashes before the user can even see the menu.
๐ฆธ The Hero (The Solution)
The "Stunt Double" โ the Proxy Pattern, which provides a lightweight placeholder for every video. It knows the title and the thumbnail but leaves the heavy video data on the disk until the user actually clicks "Play." When ProxyVideo.play() is called, it checks if RealVideo exists โ if not, it creates it (triggering the load) and then delegates the call.
๐ Requirements & Constraints
- (Functional): The client must be able to use
ProxyVideoandRealVideointerchangeably via a shared interface. - (Functional): The expensive disk I/O must only happen when the
display()orplay()method is invoked (on-demand loading). - (Technical): The
ProxyVideomust implement the exact same interface as theRealVideoso the client can't tell the difference (transparency). - (Technical): The client should not be responsible for managing the lifecycle of the
RealVideoobject (encapsulation).
๐๏ธ Structure & Blueprint
Class Diagram
classDiagram
direction TB
note "Proxy"
class RealSubject {
<<interface>>
+request(): void
}
class RealSubjectImpl {
+request(): void
}
class Proxy {
-realSubject: RealSubject
+request(): void
}
class Client {
-subject: RealSubject
+doRequest(): void
}
RealSubject <|.. RealSubjectImpl : Implements
RealSubject <|.. Proxy : Implements
Proxy --> RealSubjectImpl : Represents/Delegates to
Client --> RealSubject : Uses
Runtime Context (Sequence)
sequenceDiagram
participant Gallery
participant Proxy as ProxyVideo
participant Real as RealVideo
Gallery->>Proxy: new ProxyVideo("movie.mp4")
Note over Proxy: No file loaded yet! (lightweight)
Gallery->>Proxy: play()
Proxy->>Proxy: realVideo == null?
Note over Proxy: Yes โ lazy load
Proxy->>Real: new RealVideo("movie.mp4")
Note over Real: Loading 2GB file...
Proxy->>Real: play()
Real-->>Gallery: Playing movie.mp4
๐ป Implementation & Code
๐ง SOLID Principles Applied
- Single Responsibility: The Proxy handles lifecycle management (when to load); the Real Subject handles the actual work (playing video).
- Open/Closed: You can add new proxy types (Protection, Logging, Caching) without modifying the Real Subject.
๐ The Code
The Villain's Code (Without Pattern)
The Hero's Code (With Pattern)
from abc import ABC, abstractmethod
# class Video(ABC):
# @abstractmethod
# def __init__(self) -> None:
# pass
# class RealVideo(Video):
# def __init__(self) -> None:
# print("Loading video from disk...")
# class ProxyVideo:
# def display(self) -> Video:
# return RealVideo()
# my_proxy = ProxyVideo()
# my_proxy.display()
class Video(ABC):
@abstractmethod
def display(self) -> None:
pass
class RealVideo(Video):
def __init__(self, filename: str) -> None:
self.filename: str = filename
self._load_on_disk()
def _load_on_disk(self) -> None:
print("Heavy Operation")
def display(self) -> None:
print(f"Displaying {self.filename}")
class ProxyVideo(Video):
def __init__(self, filename: str) -> None:
self.filename: str = filename
self._real_video: RealVideo | None = None
def display(self) -> None:
if self._real_video is None:
self._real_video = RealVideo(self.filename)
self._real_video.display()
# Test
proxy = ProxyVideo("Inception.mp4") # Nothing happens here
print("Proxy created...")
proxy.display() # Loading happens now
proxy.display() # Second time, it's already loaded!
โ๏ธ Trade-offs & Testing
| Pros (Why it works) | Cons (The Twist / Pitfalls) |
|---|---|
| Access Control: Controls access to the real object without the client ever knowing it. | User Latency (Jank): Lazy loading can cause sudden UI freezes if the real object is heavy and loaded synchronously. |
| Lifecycle Management: Can delay instantiation of a heavy object until it's actually accessed (Lazy Loading). | Indirection Layer: Code is slightly more complex and harder to trace due to the extra defensive layer. |
| Startup Speed: Significantly improves initial app load time by deferring heavy resource allocation. | Over-abstraction: Using a proxy for lightweight objects adds unnecessary classes and method calls. |
๐งช Testing Strategy
The hallmark proxy test: Verify that the RealSubject's constructor or expensive network call is strictly NOT executed when the Proxy is created, but is perfectly triggered exactly the first time the relevant proxy method is accessed by the client.
๐ค Interview Toolkit
- Interview Signal: Demonstrates a developer's concern for System Performance and Memory Management. It shows they know how to handle "heavy" resources and understand the trade-offs between eager and lazy loading.
- When to Use:
- Virtual Proxy: When you have a resource-heavy object that should be loaded on demand.
- Protection Proxy: When you need to check access rights before letting a client use an object.
- Logging Proxy: When you want to keep a history of calls to a service without modifying the service code.
- Scalability Probe: How would you handle a user playing 50 videos in a row? Won't memory still fill up? (Answer: Implement a Least Recently Used (LRU) Cache inside the proxy system to dispose of the
RealVideoobjects that haven't been played recently). - Design Alternatives:
- Decorator: A Proxy controls the lifecycle and access to its object; a Decorator adds features to an object that already exists.