Recent from talks
Nothing was collected or created yet.
Structural pattern
View on WikipediaIn software engineering, structural design patterns are design patterns that ease the design by identifying a simple way to realize relationships among entities.
Examples of Structural Patterns include:
- Adapter pattern: 'adapts' one interface for a class into one that a client expects
- Aggregate pattern: a version of the Composite pattern with methods for aggregation of children
- Bridge pattern: decouple an abstraction from its implementation so that the two can vary independently
- Tombstone: An intermediate "lookup" object contains the real location of an object.[4]
- Composite pattern: a tree structure of objects where every object has the same interface
- Decorator pattern: add additional functionality to an object at runtime where subclassing would result in an exponential rise of new classes
- Extensibility pattern: a.k.a. Framework - hide complex code behind a simple interface
- Facade pattern: create a simplified interface of an existing interface to ease usage for common tasks
- Flyweight pattern: a large quantity of objects share a common properties object to save space
- Marker pattern: an empty interface to associate metadata with a class.
- Pipes and filters: a chain of processes where the output of each process is the input of the next
- Opaque pointer: a pointer to an undeclared or private type, to hide implementation details
- Proxy pattern: a class functioning as an interface to another thing
See also
[edit]References
[edit]- ^ "Adapter Pipeline". Cunningham & Cunningham, Inc. 2010-12-31. Archived from the original on 2010-12-31. Retrieved 2012-07-20.
- ^ BobbyWoolf (2002-06-19). "Retrofit Interface Pattern". Cunningham & Cunningham, Inc. Archived from the original on 2002-06-19. Retrieved 2012-07-20.
- ^ MartinZarate (2010-12-31). "External Polymorphism". Cunningham & Cunningham, Inc. Archived from the original on 2010-12-31. Retrieved 2012-07-20.
- ^ "Tomb Stone". Cunningham & Cunningham, Inc. 2007-06-17. Archived from the original on 2007-06-17. Retrieved 2012-07-20.
Structural pattern
View on GrokipediaOverview
Definition and Purpose
Structural patterns are a category of design patterns in software engineering that focus on how classes and objects can be composed to form larger, more complex structures while maintaining flexibility and efficiency. These patterns provide solutions to common design challenges by simplifying the realization of relationships between entities, allowing developers to build systems where components can be assembled without tightly coupling their implementations.[2] The primary purposes of structural patterns include achieving flexibility and composability in object-oriented systems, simplifying interactions among complex structures, and promoting loose coupling between components. By emphasizing composition over inheritance where appropriate, these patterns enable easier modification and extension of software architectures without widespread disruptions. For instance, the Adapter pattern illustrates this by allowing incompatible interfaces to work together seamlessly, adapting one to fit the expectations of another.[2] Key characteristics of structural patterns distinguish them from other design pattern categories: they primarily address class and object composition rather than individual object behaviors or creation mechanisms. In contrast, creational patterns focus on flexible object instantiation, while behavioral patterns concern object collaboration and responsibility assignment. This composition-centric approach ensures that larger structures remain stable and adaptable as requirements evolve.[2] The term "structural patterns" originated in the seminal 1994 book Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, commonly known as the Gang of Four. This work catalogs 23 classic design patterns, of which seven are classified as structural, establishing a foundational framework for object-oriented design that has influenced software development practices worldwide.[2]Classification Within Design Patterns
Design patterns, as cataloged in the seminal work by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, are broadly classified into three primary categories: creational, structural, and behavioral.[4] Creational patterns focus on mechanisms for object creation, providing flexibility in instantiating classes and objects while hiding the creation logic from client code.[4] Structural patterns, in contrast, emphasize the composition of classes and objects to form larger, more flexible structures.[4] Behavioral patterns address the communication between objects and the assignment of responsibilities among them.[4] The specific role of structural patterns lies in manipulating the relationships and compositions among classes or objects, enabling systems to achieve greater adaptability without altering underlying code structures.[4] These patterns leverage object composition as a fundamental building block to promote loose coupling and extensibility in software architectures.[4] Unlike creational patterns, which deal with "how" objects are instantiated, or behavioral patterns, which concern "what" responsibilities objects hold and how they interact, structural patterns primarily address "how" classes and objects relate to one another to build composite entities.[4] For instance, they facilitate scenarios where incompatible interfaces must interoperate or where hierarchies of objects need to be treated uniformly.[4] The Gang of Four identified seven classic structural patterns: Adapter, Bridge, Composite, Decorator, Facade, Flyweight, and Proxy.[4] These patterns collectively provide reusable solutions for organizing code to enhance maintainability and scalability in object-oriented designs.[4]Core Principles
Object Composition
Object composition is a fundamental principle in structural design patterns, establishing a "has-a" relationship where objects are assembled to form larger, more complex structures, in contrast to inheritance, which relies on an "is-a" relationship through class hierarchies.[5] This approach allows for the delegation of responsibilities to component objects, enabling dynamic behavior at runtime rather than static binding at compile time.[5] The benefits of object composition include greater flexibility in modifying and extending systems, as components can be swapped or reconfigured without altering the overall structure, leading to easier maintenance and reduced tight coupling between classes compared to deep inheritance hierarchies.[5] By encapsulating functionality within independent objects, composition promotes reusability and avoids the fragility often associated with inheritance, where changes in a base class can ripple through subclasses.[5] Key techniques in object composition involve aggregation and composition as distinct forms of object relationships, alongside the use of interfaces to ensure interchangeable components. Aggregation represents a weaker "has-a" association where the contained objects (parts) can exist independently of the container (whole), such as a university containing students who retain their identity outside the institution.[6] In contrast, composition establishes a stronger ownership where parts are integral to the whole and share its lifecycle, meaning the destruction of the whole also destroys the parts, as in a house containing rooms that cease to exist without it.[6] Interfaces facilitate this by defining contracts that allow components to be plugged in seamlessly, promoting loose coupling and polymorphism without exposing internal implementations.[5] Conceptually, object composition often manifests in tree-like structures, where leaf nodes represent simple, atomic objects and internal nodes act as composites that aggregate or compose multiple children to build hierarchical assemblies, enabling uniform operations across the structure.[5] This principle directly underpins patterns like the Composite pattern, which treats individual objects and compositions uniformly.[5]| Aspect | Aggregation | Composition |
|---|---|---|
| Relationship Strength | Weak; parts independent of whole | Strong; parts owned by and dependent on whole |
| Lifecycle | Parts can survive whole's destruction | Parts destroyed with whole |
| Example | Library has books (books exist without library) | Car has engine (engine doesn't exist without car) |
Interface Segregation and Stability
The Interface Segregation Principle (ISP) asserts that clients should not be forced to depend on interfaces they do not use, advocating for the division of broad interfaces into narrower, client-specific ones to reduce coupling and improve maintainability. In the context of structural patterns, this principle is enforced through mechanisms like adaptation and bridging, which allow incompatible interfaces to interact without imposing extraneous methods on clients, thereby minimizing unintended dependencies and facilitating modular evolution. Structural stability refers to the ability of a system's architecture to withstand changes in components without propagating instability across the design, a goal achieved by decoupling abstractions from concrete implementations. For example, patterns such as Bridge enable independent variation of high-level abstractions and their realizations, preventing modifications in one from rippling to the other and thus supporting long-term adaptability in complex systems. This decoupling fosters resilience, as components can be extended or replaced while preserving the integrity of the overall structure. Central to these stability efforts are the Dependency Inversion Principle (DIP) and the Liskov Substitution Principle (LSP). DIP requires that high-level modules depend on abstractions rather than low-level details, inverting traditional dependency flows to promote flexibility and reduce fragility in structural compositions.[7] Complementing this, LSP mandates that subtypes must be substitutable for their base types without altering program behavior, ensuring that composed structures remain predictable and correct under substitution.[8] Together, these concepts underpin the robustness of structural patterns by aligning dependencies with abstractions and enforcing behavioral consistency. However, applying these principles involves trade-offs, particularly in balancing the flexibility of composition against the performance costs of indirection, such as increased memory usage and invocation overhead from additional layers. In performance-sensitive domains, excessive abstraction can introduce measurable latency, necessitating careful evaluation to avoid undermining efficiency gains from modularity. The Proxy pattern, for instance, enhances stability through controlled access but may amplify these indirection costs in high-throughput scenarios.Key Patterns
Adapter Pattern
The Adapter pattern is a structural design pattern that converts the interface of a class into another interface that a client expects, allowing otherwise incompatible classes to work together seamlessly. It addresses the challenge of integrating legacy systems or third-party libraries with mismatched interfaces into an existing codebase, enabling reuse without altering the original classes or the client's expectations. This pattern promotes flexibility in software design by acting as a bridge between disparate components.[9][10] The pattern exists in two primary variants: the class adapter and the object adapter. The class adapter employs multiple inheritance, where the adapter subclass inherits from both the target interface and the adaptee class, directly overriding methods to translate calls; this approach is feasible in languages like C++ but limited in single-inheritance languages such as Java. In contrast, the object adapter uses composition, with the adapter holding a reference to an instance of the adaptee and delegating calls through that reference, making it more portable and widely applicable across programming languages.[10][9] Structurally, the Adapter pattern comprises three main elements: the Target, an interface or abstract class defining the desired operations that the client interacts with; the Adaptee, the existing class providing the incompatible functionality; and the Adapter, a class that implements the Target interface while encapsulating the Adaptee and translating method invocations between them. In operation, when the client calls a method on the Target, the Adapter intercepts it and maps it to the corresponding method on the Adaptee, ensuring compatibility without exposing the underlying mismatch.[9][10] A well-known metaphor for the Adapter pattern is adapting a square peg to fit a round hole, where the adapter modifies the peg's shape to match the hole's requirements without altering either original form. Consider a simple example in pseudocode where a client expects aTarget with a request() method, but the Adaptee only provides specificRequest():
// Target interface
interface Target {
void request();
}
// Adaptee class
class Adaptee {
void specificRequest() {
// Specific implementation
print("Adaptee's specific request");
}
}
// Adapter (object variant using composition)
class Adapter implements Target {
private Adaptee adaptee;
Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
void request() {
// Translate to adaptee's method
adaptee.specificRequest();
}
}
// Client usage
void clientCode(Target target) {
target.request();
}
// Example instantiation
Adaptee adaptee = new Adaptee();
Target target = new [Adapter](/page/Adapter)(adaptee);
clientCode(target); // Outputs: "Adaptee's specific request"
// Target interface
interface Target {
void request();
}
// Adaptee class
class Adaptee {
void specificRequest() {
// Specific implementation
print("Adaptee's specific request");
}
}
// Adapter (object variant using composition)
class Adapter implements Target {
private Adaptee adaptee;
Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
void request() {
// Translate to adaptee's method
adaptee.specificRequest();
}
}
// Client usage
void clientCode(Target target) {
target.request();
}
// Example instantiation
Adaptee adaptee = new Adaptee();
Target target = new [Adapter](/page/Adapter)(adaptee);
clientCode(target); // Outputs: "Adaptee's specific request"
Bridge Pattern
The Bridge pattern is a structural design pattern that decouples an abstraction from its implementation, enabling the two to vary independently without affecting clients.[11] This separation addresses the problem of permanent binding between abstraction and implementation in inheritance hierarchies, where extending functionality in multiple dimensions—such as adding new features to an abstraction while supporting different platforms—leads to an explosion of subclasses, making the system rigid and hard to maintain.[12] By using composition over inheritance, the pattern promotes flexibility, aligning with principles like interface segregation by defining a stable implementor interface that isolates varying implementations.[11] The structure consists of four main components: the Abstraction, which defines the high-level interface and maintains a reference to an Implementor object; the Refined Abstraction, which extends the Abstraction to provide variations in behavior; the Implementor, which defines the interface for implementation classes; and the Concrete Implementor, which provides specific implementations of the Implementor interface.[12] The Abstraction delegates operations to the Implementor, allowing clients to work with the abstraction without knowing the concrete implementation details.[11] A representative example involves a graphics application with shapes (such as circles and squares) as the abstraction and different drawing APIs (such as OpenGL and DirectX) as the implementation. The Shape abstraction delegates drawing to a Renderer implementor, enabling new shapes to be added without modifying the renderers, and vice versa.[13] The following pseudocode illustrates this delegation:abstract class Shape {
protected Renderer renderer;
public Shape(Renderer renderer) {
this.renderer = renderer;
}
abstract void draw();
}
class Circle extends Shape {
public void draw() {
renderer.renderCircle();
}
}
interface Renderer {
void renderCircle();
}
class OpenGLRenderer implements Renderer {
public void renderCircle() {
// OpenGL-specific code to draw circle
}
}
class DirectXRenderer implements Renderer {
public void renderCircle() {
// DirectX-specific code to draw circle
}
}
abstract class Shape {
protected Renderer renderer;
public Shape(Renderer renderer) {
this.renderer = renderer;
}
abstract void draw();
}
class Circle extends Shape {
public void draw() {
renderer.renderCircle();
}
}
interface Renderer {
void renderCircle();
}
class OpenGLRenderer implements Renderer {
public void renderCircle() {
// OpenGL-specific code to draw circle
}
}
class DirectXRenderer implements Renderer {
public void renderCircle() {
// DirectX-specific code to draw circle
}
}
Composite Pattern
The Composite pattern is a structural design pattern that composes objects into tree-like structures to represent part-whole hierarchies, allowing clients to treat individual objects and compositions uniformly without distinguishing between them. It addresses the challenge of managing complex hierarchical data where operations must traverse both primitive elements (leaves) and container elements (composites) in a consistent manner, avoiding the need for clients to handle structural differences explicitly. This uniformity simplifies client code and promotes flexibility in recursive processing of hierarchies.[2] The pattern's structure revolves around three key roles: the Component, which defines a common interface for all objects in the hierarchy; the Leaf, which implements primitive operations without children; and the Composite, which maintains a collection of child Components and delegates operations to them for recursive traversal. The Component interface typically includes methods for the core operation shared across the hierarchy, while child management operations (such as adding or removing children) are either included in the Component for transparency or restricted to the Composite for safety. This design leverages object composition to build dynamic, extensible trees.[2] A representative example is modeling a file system, where files serve as Leaf components with basic operations like displaying size, and directories act as Composite components that aggregate files and subdirectories, enabling uniform traversal such as calculating total directory size. The following pseudocode illustrates the structure in a generic object-oriented language:interface Component {
void operation(); // Common operation for leaves and composites
}
class Leaf implements Component {
private String name;
private int size;
void operation() {
// Perform leaf-specific action, e.g., return size
print("Leaf: " + name + ", size: " + size);
}
}
class Composite implements Component {
private String name;
private List<Component> children = new ArrayList<>();
void add(Component child) {
children.add(child);
}
void remove(Component child) {
children.remove(child);
}
void operation() {
print("Composite: " + name);
for each child in children {
child.operation(); // Recursively traverse children
}
}
}
interface Component {
void operation(); // Common operation for leaves and composites
}
class Leaf implements Component {
private String name;
private int size;
void operation() {
// Perform leaf-specific action, e.g., return size
print("Leaf: " + name + ", size: " + size);
}
}
class Composite implements Component {
private String name;
private List<Component> children = new ArrayList<>();
void add(Component child) {
children.add(child);
}
void remove(Component child) {
children.remove(child);
}
void operation() {
print("Composite: " + name);
for each child in children {
child.operation(); // Recursively traverse children
}
}
}
operation() on any Component—whether a single file or an entire directory tree—without type-specific logic.[2]
Variants of the Composite pattern balance transparency and safety: the safe variant declares child management methods only in the Composite class, requiring clients to cast or check types, which enhances type safety but reduces uniformity; conversely, the transparent variant includes these methods in the Component interface, allowing seamless client access but necessitating null checks in Leaf implementations to avoid errors. The choice depends on whether uniformity or compile-time safety is prioritized, with the transparent approach often favored for its simplicity in recursive client code despite runtime overhead.[2]
The pattern's primary advantage is simplifying client interactions with hierarchies by enforcing a uniform interface, which aligns with the principle of object composition for building flexible structures. However, it can lead to overly general designs, as Leaves may implement irrelevant child management methods in transparent variants, potentially complicating the interface and introducing unnecessary complexity.[2]
Decorator Pattern
The Decorator pattern is a structural design pattern that enables the dynamic attachment of new behaviors to individual objects without altering their underlying structure or relying on extensive subclassing. It addresses the problem of extending functionality in a flexible manner, particularly when subclassing would lead to an explosion of subclasses—for instance, when adding multiple optional features like borders, scrollbars, or colors to graphical user interface components, where each combination would otherwise require a dedicated subclass. This approach promotes reusability and avoids modifying the original classes, adhering to the open-closed principle by allowing extension without source code changes. The pattern's structure consists of a base Component interface that defines the core operations, a ConcreteComponent that implements the basic behavior, an abstract Decorator class that holds a reference to a Component and delegates calls to it while optionally adding behavior, and one or more Concrete Decorator classes that implement specific enhancements by wrapping the component and modifying the operation before or after delegation. Clients interact with objects through the Component interface, enabling transparent wrapping with multiple decorators in a chain, where each decorator forwards requests to the inner component. This composition-based design leverages object references to build layers of functionality dynamically. A classic example involves enhancing visual components in a graphical user interface, such as adding scrollbars or borders to windows without creating subclasses for every permutation. Consider a baseWindow component with an operation() method that draws the window; a BorderDecorator might wrap it to add a frame, and a ScrollbarDecorator could then wrap the bordered version to include scrolling. Pseudocode illustrates this delegation:
class ConcreteDecoratorA {
Component component;
void operation() {
component.operation(); // Delegate to inner component
// Add extra behavior, e.g., draw border
}
}
class ConcreteDecoratorA {
Component component;
void operation() {
component.operation(); // Delegate to inner component
// Add extra behavior, e.g., draw border
}
}
operation() executes the core drawing and appends the decorator's addition, allowing runtime stacking of features like borders followed by scrollbars.
In relation to inheritance, the Decorator pattern favors composition over inheritance to achieve behavioral extension, as subclassing locks behaviors at compile time and can complicate maintenance with numerous variants, whereas composition permits runtime flexibility in assembling behaviors. This provides advantages such as the ability to add or remove responsibilities dynamically and support for transparent multiple enhancements, but it also introduces disadvantages, including the potential creation of many small objects that increase memory overhead and complicate debugging due to opaque delegation chains. Unlike the Composite pattern, which treats individual objects and compositions uniformly in tree structures, Decorator applies linear wrapping to single objects for behavior addition. In contrast to the Proxy pattern, which primarily controls access or provides indirect references without altering core functionality, Decorator explicitly adds new behaviors.
Facade Pattern
The Facade pattern provides a simplified, unified interface to a complex subsystem, such as a library or framework, enabling clients to access its functionality without grappling with intricate internal details or numerous dependencies.[14] This structural design pattern, one of the 23 patterns cataloged by the Gang of Four in their foundational 1994 book, shields clients from the subsystem's complexity by offering only the essential operations they require. The primary problem the Facade pattern addresses is the tight coupling that arises when clients must directly interact with a subsystem's many interdependent classes, often involving multiple initialization steps and configuration details; this leads to brittle, hard-to-maintain code that violates principles like the Single Responsibility Principle.[14] By introducing a facade as an entry point, the pattern reduces these dependencies, allowing subsystem changes without impacting clients and promoting modular design. In terms of structure, the pattern comprises a Facade class that serves as the single interface coordinating interactions among subsystem classes, which are independent components handling specific functionalities but oblivious to the facade's presence. Clients invoke high-level methods on the facade, which in turn delegates to the appropriate subsystem operations, often sequencing them to achieve a cohesive result; optional additional facades can further segregate unrelated subsystem features for even greater simplicity.[14] A representative example is a home theater system integrating devices like a DVD player, projector, amplifier, screen, theater lights, and popcorn popper, where coordinating a movie playback requires synchronizing numerous steps across these components. The HomeTheaterFacade class encapsulates this complexity with straightforward methods, such aswatchMovie(), which orchestrates the devices in sequence without requiring the client (e.g., a user) to manage each one individually.[15] This mirrors real-world scenarios like API wrappers for multimedia libraries, where the facade hides boilerplate setup.
The following pseudocode illustrates the facade's coordination in the home theater example:
class HomeTheaterFacade {
private final DVDPlayer dvd;
private final Projector projector;
private final TheaterLights lights;
private final Screen screen;
private final PopcornPopper popcornPopper;
private final Amplifier amp;
public HomeTheaterFacade(DVDPlayer dvd, Projector projector, /* other components */) {
this.dvd = dvd;
this.projector = projector;
// initialize others
}
public void watchMovie(String movie) {
popcornPopper.on();
popcornPopper.pop();
lights.dim(10);
screen.down();
projector.on();
projector.setInput(dvd);
amp.on();
amp.setDvd(dvd);
amp.setVolume(5);
dvd.on();
dvd.play(movie);
}
public void endMovie() {
lights.on();
screen.up();
projector.off();
amp.off();
dvd.off();
popcornPopper.off();
}
}
class HomeTheaterFacade {
private final DVDPlayer dvd;
private final Projector projector;
private final TheaterLights lights;
private final Screen screen;
private final PopcornPopper popcornPopper;
private final Amplifier amp;
public HomeTheaterFacade(DVDPlayer dvd, Projector projector, /* other components */) {
this.dvd = dvd;
this.projector = projector;
// initialize others
}
public void watchMovie(String movie) {
popcornPopper.on();
popcornPopper.pop();
lights.dim(10);
screen.down();
projector.on();
projector.setInput(dvd);
amp.on();
amp.setDvd(dvd);
amp.setVolume(5);
dvd.on();
dvd.play(movie);
}
public void endMovie() {
lights.on();
screen.up();
projector.off();
amp.off();
dvd.off();
popcornPopper.off();
}
}
facade.watchMovie("Inception") to activate the full sequence, demonstrating how the facade abstracts away the subsystem's orchestration.[15]
The advantages of the Facade pattern include fostering loose coupling between clients and the subsystem, which eases testing by allowing mocks of the facade without subsystem involvement, and enhances overall maintainability by localizing subsystem interactions.[14][15] However, a key disadvantage is the risk of the facade becoming a god object—overly centralized and tightly bound to every subsystem class—if it accumulates too many responsibilities, potentially hindering independent evolution of components and introducing single points of failure.[14]
Unlike the Adapter pattern, which focuses on converting one incompatible interface to match another, the Facade simplifies interactions with an entire subsystem as a cohesive unit.[14] Similarly, it differs from the Proxy pattern by concealing the intricacies of a group of collaborating objects rather than controlling access to a solitary one.[14]
Flyweight Pattern
The Flyweight pattern addresses the challenge of managing a large number of similar objects in applications where creating unique instances for each would lead to excessive memory consumption, such as in simulations or user interfaces with numerous repeated elements.[2] By promoting the sharing of common data across instances, it allows systems to handle vast quantities of fine-grained objects efficiently without duplicating redundant information.[2] The pattern's structure consists of a Flyweight interface that defines operations requiring extrinsic state as parameters; ConcreteFlyweight classes that implement this interface, storing only the intrinsic (shared) state and being immutable to enable safe sharing; a FlyweightFactory that maintains a registry or pool of existing flyweights, creating new ones only when necessary and returning references to shared instances; and a Client component that computes and provides extrinsic state while interacting with the factory to retrieve appropriate flyweights.[2] The core distinction lies between intrinsic state, which is invariant and stored within the flyweight for sharing (e.g., fixed attributes like texture or color), and extrinsic state, which varies by context and is passed into operations at runtime (e.g., position or velocity), often managed through object composition with a separate context object.[2] A representative example is in a text editor application, where individual characters are represented as flyweights sharing intrinsic state such as font type, size, and glyph shape, while extrinsic state like screen position or color is computed and passed by the client during rendering.[2] This approach avoids duplicating formatting data for each character instance, significantly reducing memory overhead in documents with thousands of repeated elements. The following pseudocode illustrates a basic operation using shared intrinsic state:interface Flyweight {
void operation(ExtrinsicState extrinsicState);
}
class ConcreteFlyweight implements Flyweight {
private IntrinsicState intrinsicState; // Shared, immutable
public ConcreteFlyweight(IntrinsicState intrinsicState) {
this.intrinsicState = intrinsicState;
}
public void operation(ExtrinsicState extrinsicState) {
// Logic combining intrinsicState and extrinsicState, e.g., rendering
System.out.println("Rendering with intrinsic: " + intrinsicState +
" and extrinsic: " + extrinsicState);
}
}
class FlyweightFactory {
private Map<String, Flyweight> flyweights = new HashMap<>();
public Flyweight getFlyweight(String key) {
if (!flyweights.containsKey(key)) {
flyweights.put(key, new ConcreteFlyweight(new IntrinsicState(key)));
}
return flyweights.get(key);
}
}
interface Flyweight {
void operation(ExtrinsicState extrinsicState);
}
class ConcreteFlyweight implements Flyweight {
private IntrinsicState intrinsicState; // Shared, immutable
public ConcreteFlyweight(IntrinsicState intrinsicState) {
this.intrinsicState = intrinsicState;
}
public void operation(ExtrinsicState extrinsicState) {
// Logic combining intrinsicState and extrinsicState, e.g., rendering
System.out.println("Rendering with intrinsic: " + intrinsicState +
" and extrinsic: " + extrinsicState);
}
}
class FlyweightFactory {
private Map<String, Flyweight> flyweights = new HashMap<>();
public Flyweight getFlyweight(String key) {
if (!flyweights.containsKey(key)) {
flyweights.put(key, new ConcreteFlyweight(new IntrinsicState(key)));
}
return flyweights.get(key);
}
}
flyweight.operation(extrinsic) for each use case.[2]
The primary advantage of the Flyweight pattern is substantial memory efficiency, as sharing intrinsic state can reduce object storage requirements by orders of magnitude in scenarios with high object multiplicity, such as graphical simulations or large datasets.[2] However, it introduces disadvantages including increased design complexity for partitioning state correctly and potential runtime overhead from frequent extrinsic state computations or lookups, which may trade memory savings for additional CPU cycles.[2]
Proxy Pattern
The Proxy pattern is a structural design pattern that provides a surrogate or placeholder for another object to control access to it, allowing indirect interaction without altering the subject's interface.[2] This pattern addresses the need to add functionality such as lazy loading, remote access, security checks, or resource management around a resource-intensive or sensitive object, preventing direct client exposure and avoiding widespread code modifications.[16] By interposing the proxy between the client and the real subject, it centralizes control logic, promoting separation of concerns in object-oriented systems.[2] The pattern manifests in several variants, each tailored to specific access control needs. The virtual proxy enables lazy initialization, where the real object is created only when required, deferring costly operations like loading large files until necessary.[16] The remote proxy facilitates distributed systems by representing an object in a different address space, handling network communication transparently to the client.[2] The protection proxy enforces access rights, verifying permissions before delegating requests to the subject, such as checking user credentials.[16] Finally, the smart proxy adds auxiliary behaviors like reference counting or caching, managing the subject's lifecycle without client awareness.[2] In its structure, the Proxy pattern involves three key participants: the subject, which defines the common interface for both the proxy and the real subject; the real subject, which implements the actual operations; and the proxy, which implements the same interface, holds a reference to the real subject, and forwards requests after performing preliminary actions.[16] This composition ensures the client interacts seamlessly with the proxy as if it were the real subject, maintaining interface stability.[2] A representative example is an image proxy for displaying graphics in a viewer application. The proxy initially loads a lightweight thumbnail to render quickly, replacing it with the full high-resolution image only upon user interaction, thus optimizing performance.[16] Pseudocode illustrates this delegation in the proxy's request method:class ImageProxy implements Image {
private RealImage realImage;
private String filename;
public ImageProxy(String filename) {
this.filename = filename;
}
public void display() {
if (realImage == null) {
realImage = new RealImage(filename); // Lazy loading
}
realImage.display();
}
}
class ImageProxy implements Image {
private RealImage realImage;
private String filename;
public ImageProxy(String filename) {
this.filename = filename;
}
public void display() {
if (realImage == null) {
realImage = new RealImage(filename); // Lazy loading
}
realImage.display();
}
}
