10. Discuss the role of the Adapter pattern in integrating incompatible interfaces and provide an example from your experience.

Advanced

10. Discuss the role of the Adapter pattern in integrating incompatible interfaces and provide an example from your experience.

Overview

The Adapter pattern plays a crucial role in software engineering by allowing incompatible interfaces to work together. This pattern acts as a bridge between two incompatible interfaces, enabling classes to communicate without modifying their existing code. It is particularly important in scenarios where introducing source code changes is impractical, costly, or impossible due to the interfaces being external or legacy systems.

Key Concepts

  1. Interface Incompatibility: The core issue the Adapter pattern addresses is the incompatibility between two or more interfaces.
  2. Reusability: By enabling communication between incompatible interfaces, the Adapter pattern increases the reusability of existing classes or interfaces.
  3. Decoupling: The Adapter pattern helps in decoupling the client from the implementation of an interface, promoting loose coupling and enhancing code maintainability.

Common Interview Questions

Basic Level

  1. What is the Adapter pattern and why is it used?
  2. Can you explain the class adapter and object adapter variations?

Intermediate Level

  1. How does the Adapter pattern differ from the Decorator pattern?

Advanced Level

  1. Describe a scenario where you used the Adapter pattern to solve a complex integration issue.

Detailed Answers

1. What is the Adapter pattern and why is it used?

Answer: The Adapter pattern is a structural design pattern that allows objects with incompatible interfaces to collaborate. It acts as a wrapper or intermediary that translates calls from one interface to another without changing their existing code. This pattern is used to ensure that new implementations or external systems can work with legacy code, thereby facilitating system integration and extending the usability of existing components or services.

Key Points:
- Facilitates communication between different interfaces.
- Increases the reusability of existing or external classes.
- Enhances system integration without modifying source code.

Example:

// Assume we have an ITarget interface that the client uses.
public interface ITarget
{
    void Request();
}

// And an Adaptee class with a different interface.
class Adaptee
{
    public void SpecificRequest()
    {
        Console.WriteLine("Called SpecificRequest()");
    }
}

// The Adapter makes Adaptee's interface compatible with ITarget's.
class Adapter : ITarget
{
    private readonly Adaptee _adaptee;

    public Adapter(Adaptee adaptee)
    {
        _adaptee = adaptee;
    }

    public void Request()
    {
        // Possibly do some other work
        // and then call SpecificRequest
        _adaptee.SpecificRequest();
    }
}

// Usage
class Client
{
    public void Main()
    {
        Adaptee adaptee = new Adaptee();
        ITarget target = new Adapter(adaptee);
        target.Request();
    }
}

2. Can you explain the class adapter and object adapter variations?

Answer: The Adapter pattern can be implemented in two ways: class adapter and object adapter. The class adapter uses inheritance to adapt the interface, whereas the object adapter uses composition.

Key Points:
- Class Adapter: Uses multiple inheritances (not supported in C#) to adapt one interface to another. It inherits privately from the Adaptee and implements the target interface.
- Object Adapter: Relies on object composition. It implements the target interface and holds an instance of the Adaptee class.

Example:

// Object Adapter example, as C# does not support multiple inheritance for class adapters.

public interface ITarget
{
    void Request();
}

class Adaptee
{
    public void SpecificRequest()
    {
        Console.WriteLine("Called SpecificRequest()");
    }
}

class Adapter : ITarget
{
    private Adaptee _adaptee;

    public Adapter(Adaptee adaptee)
    {
        _adaptee = adaptee;
    }

    public void Request()
    {
        _adaptee.SpecificRequest();
    }
}

3. How does the Adapter pattern differ from the Decorator pattern?

Answer: While both the Adapter and Decorator patterns are structural design patterns involving object composition, their intents differ. The Adapter pattern is primarily used to make existing interfaces compatible with each other, whereas the Decorator pattern is used to add new functionality to objects dynamically without altering their structure.

Key Points:
- Adapter Pattern: Focuses on resolving incompatibilities between interfaces.
- Decorator Pattern: Aims to add responsibilities to objects dynamically.

Example:

// Decorator pattern example for comparison.
public interface IComponent
{
    void Operation();
}

class Component : IComponent
{
    public void Operation()
    {
        Console.WriteLine("Component.Operation()");
    }
}

class Decorator : IComponent
{
    private IComponent _component;

    public Decorator(IComponent component)
    {
        _component = component;
    }

    public void Operation()
    {
        _component.Operation();
        AddedBehavior();
    }

    void AddedBehavior()
    {
        Console.WriteLine("Decorator.AddedBehavior()");
    }
}

// Usage
class Client
{
    public void Main()
    {
        IComponent component = new Decorator(new Component());
        component.Operation();
    }
}

4. Describe a scenario where you used the Adapter pattern to solve a complex integration issue.

Answer: In a project where we needed to integrate a third-party payment gateway with our existing payment processing system, the interfaces were completely incompatible. The third-party library expected requests in a different format and provided responses that our system couldn't directly process. By implementing an Adapter, we were able to translate the requests and responses between our system and the third-party library, enabling seamless integration without modifying the existing code base.

Key Points:
- Addressed interface incompatibility between our system and the third-party library.
- Enabled seamless integration without altering existing code.
- Facilitated communication and data exchange between the two systems.

Example:

// Simplified code example for integrating a third-party payment gateway.
public interface IPaymentProcessor
{
    void ProcessPayment(decimal amount);
}

class ThirdPartyPaymentGateway
{
    public void InitiatePayment(double payment)
    {
        Console.WriteLine($"Initiating payment of {payment}");
    }
}

class PaymentAdapter : IPaymentProcessor
{
    private readonly ThirdPartyPaymentGateway _paymentGateway;

    public PaymentAdapter(ThirdPartyPaymentGateway paymentGateway)
    {
        _paymentGateway = paymentGateway;
    }

    public void ProcessPayment(decimal amount)
    {
        _paymentGateway.InitiatePayment((double)amount);
    }
}

// Usage
class PaymentService
{
    public void ExecutePayment(IPaymentProcessor paymentProcessor, decimal amount)
    {
        paymentProcessor.ProcessPayment(amount);
    }
}