Open In App

Proxy Method Design Pattern in Javascript

Last Updated : 02 Sep, 2024
Comments
Improve
Suggest changes
Like Article
Like
Report

The Proxy Method Design Pattern in JavaScript involves creating a proxy object that controls access to another object, allowing you to define custom behavior for operations like property access and method calls. This pattern is useful for adding features such as logging, validation, and access control in a modular way. In this article, we'll explore how the Proxy pattern works in JavaScript, its benefits, and practical examples to demonstrate its application in managing and enhancing object interactions.

Proxy-Method-Design-Pattern-in-Javascript
Proxy Method Design Pattern in Javascript

What is the Proxy Method Design Pattern?

The Proxy Method Design Pattern involves creating a surrogate or placeholder object as a middleman between a client and a target object. It allows you to intercept operations before they reach the target object, modify or enhance them, and then pass them on. This pattern is particularly useful for controlling access, caching data, and providing a simplified interface to a complex or resource-intensive object.

Components of Proxy Method Design Pattern

The Proxy Method Design Pattern typically consists of the following components:

  1. Subject Interface: Defines the common interface for the real object and the proxy, ensuring that both can be used interchangeably.
  2. Real Subject: The actual object that the proxy represents and forwards requests to. It contains the core functionality and data.
  3. Proxy: Acts as an intermediary between the client and the real subject. It controls access to the real subject, adding additional functionality such as logging, access control, or lazy initialization.
  4. Client: The entity that interacts with the proxy and, by extension, the real subject. It uses the proxy interface to request operations.

Proxy Method Design Pattern example in Javascript

Problem Statement:

Imagine a scenario where you have an online document editor application that allows users to view and edit documents. However, accessing and editing certain documents might require permission due to their sensitivity or size. Loading large documents might be slow, and unauthorized access should be prevented. You need a mechanism to control access to these documents and optimize performance by loading them only when necessary.

How the Proxy Pattern Solves the Problem?

The Proxy Method Design Pattern can be applied here to create a proxy for the document object. The proxy will handle access control and lazy loading of the document content. This means that the document will only be loaded when the user has the necessary permissions and when they actually need to view or edit it. The proxy also helps in reducing the load on the system by deferring the document loading until it is absolutely required.

proxy
Class Diagram of Proxy Method Design Pattern in Javascript

Below is the code for the approach discussed above:

1. Subject Interface

Defines the operations that both the real document and the proxy will implement.

JavaScript
// Subject Interface
class Document {
  open() {}
  edit(content) {}
}

2. Real Subject

The actual document object that contains the real content and the methods to interact with it.

JavaScript
// Real Subject
class RealDocument extends Document {
  constructor(filename) {
    super();
    this.filename = filename;
    this.content = this.loadFromDisk();
  }

  loadFromDisk() {
    console.log(`Loading document from disk: ${this.filename}`);
    return "Document content..."; // Simulate loading content
  }

  open() {
    console.log(`Opening document: ${this.filename}`);
    return this.content;
  }

  edit(newContent) {
    console.log(`Editing document: ${this.filename}`);
    this.content = newContent;
  }
}

3. Proxy

The proxy object that controls access to the real document. It handles permission checks and lazy loading of the document content.

JavaScript
// Proxy
class DocumentProxy extends Document {
  constructor(filename, user) {
    super();
    this.filename = filename;
    this.user = user;
    this.realDocument = null;
  }

  checkAccess() {
    console.log(`Checking access for user: ${this.user}`);
    return this.user === "admin"; // Example permission check
  }

  open() {
    if (this.checkAccess()) {
      if (!this.realDocument) {
        this.realDocument = new RealDocument(this.filename);
      }
      return this.realDocument.open();
    } else {
      console.log("Access Denied: Unauthorized user");
      return null;
    }
  }

  edit(newContent) {
    if (this.checkAccess()) {
      if (!this.realDocument) {
        this.realDocument = new RealDocument(this.filename);
      }
      this.realDocument.edit(newContent);
    } else {
      console.log("Access Denied: Unauthorized user");
    }
  }
}

4. Client

The client interacts with the proxy, unaware of whether it is dealing with a proxy or the real document.

JavaScript
// Client
const user = "admin";
const docProxy = new DocumentProxy("sample.txt", user);

// Open document
console.log(docProxy.open());

// Edit document
docProxy.edit("New content...");

// Open again to see the changes
console.log(docProxy.open());

Below is the complete combined code:

JavaScript
// Subject Interface
class Document {
  open() {}
  edit(content) {}
}

// Real Subject
class RealDocument extends Document {
  constructor(filename) {
    super();
    this.filename = filename;
    this.content = this.loadFromDisk();
  }

  loadFromDisk() {
    console.log(`Loading document from disk: ${this.filename}`);
    return "Document content..."; // Simulate loading content
  }

  open() {
    console.log(`Opening document: ${this.filename}`);
    return this.content;
  }

  edit(newContent) {
    console.log(`Editing document: ${this.filename}`);
    this.content = newContent;
  }
}

// Proxy
class DocumentProxy extends Document {
  constructor(filename, user) {
    super();
    this.filename = filename;
    this.user = user;
    this.realDocument = null;
  }

  checkAccess() {
    console.log(`Checking access for user: ${this.user}`);
    return this.user === "admin"; // Example permission check
  }

  open() {
    if (this.checkAccess()) {
      if (!this.realDocument) {
        this.realDocument = new RealDocument(this.filename);
      }
      return this.realDocument.open();
    } else {
      console.log("Access Denied: Unauthorized user");
      return null;
    }
  }

  edit(newContent) {
    if (this.checkAccess()) {
      if (!this.realDocument) {
        this.realDocument = new RealDocument(this.filename);
      }
      this.realDocument.edit(newContent);
    } else {
      console.log("Access Denied: Unauthorized user");
    }
  }
}

// Client
const user = "admin";
const docProxy = new DocumentProxy("sample.txt", user);

// Open document
console.log(docProxy.open());

// Edit document
docProxy.edit("New content...");

// Open again to see the changes
console.log(docProxy.open());

Below is the explanation of the above code:

  • RealDocument: Represents the actual document with methods to open and edit the document. The content is loaded from the disk when the document is created.
  • DocumentProxy: Acts as a mediator between the client and the RealDocument. It ensures that only authorized users can access the document and defers the loading of the document until it is necessary.
  • Client: Interacts with the DocumentProxy, which controls access to the RealDocument. The client does not need to worry about the underlying complexities of permission checking or document loading.

Use Cases of Proxy Method Design Pattern

  1. Access Control: Proxy can control access to an object by checking permissions or authentication.
  2. Caching: Proxy can cache results of expensive operations and serve cached data instead of recalculating.
  3. Logging: Proxy can add logging capabilities before and after invoking methods on the RealSubject.
  4. Lazy Initialization: Proxy can delay the creation and initialization of the RealSubject until it's actually needed.

When to Use Proxy Method Design Pattern

  • Lazy Initialization (Virtual Proxy): When you want to delay the creation and initialization of expensive objects until they are actually needed. The proxy acts as a placeholder, creating the real object only when required.
  • Access Control (Protection Proxy): When you need to control access to an object, especially when different users or clients have different levels of access. The proxy can manage the access rights and enforce security rules before delegating requests to the actual object.
  • Remote Proxy: When the actual object exists in a different address space (e.g., on a different server), the proxy provides a local representative for the remote object, handling communication between the client and the remote object.
  • Logging or Monitoring: When you need to monitor or log operations without modifying the original object. The proxy can intercept requests, log them, and then pass them to the real object.
  • Smart Reference: When you need to add additional functionality when accessing an object, such as reference counting, caching, or lazy loading. The proxy can manage these additional responsibilities while still delegating core functionality to the real object.

When Not to Use Proxy Method Design Pattern

The Proxy Method Design Pattern might not be the best choice in the following situations:

  • Overhead Concerns: If performance is critical and the overhead of using a proxy (additional method calls, added layers of indirection) would significantly impact the system's efficiency, it's better to avoid this pattern.
  • Simple Objects: When dealing with simple objects that don't require additional layers of control, access, or initialization, using a proxy can unnecessarily complicate the design.
  • Direct Access Required: If clients need to interact directly with the actual object without any intermediary, perhaps due to strict performance requirements or system constraints, a proxy pattern may not be appropriate.
  • High Complexity: Introducing a proxy can add complexity to the system. If the system is already complex, adding a proxy might make it harder to maintain or understand, especially if the benefits of the proxy are marginal.
  • Real-Time Systems: In real-time systems where every millisecond counts, the additional delay introduced by a proxy can be unacceptable. Direct interaction with objects may be required to meet strict timing constraints.

Conclusion

The Proxy Method Design Pattern in JavaScript offers a flexible way to add behavior to objects indirectly. By providing a surrogate or placeholder, it enhances control over object access, caching, and additional functionality while maintaining a consistent interface with the client. By leveraging proxies, developers can achieve better separation of concerns, improved code reusability, and enhanced security in their applications



Next Article
Article Tags :

Similar Reads