Programming

Adapter vs Strategy Pattern: How to Decide for Outputs

Learn Adapter vs Strategy pattern for multiple outputs like console, file, email. Decision criteria: intent, interface compatibility, runtime selection, coupling. Design patterns examples in C# and Java.

1 answer 1 view

Adapter vs Strategy — how to decide for multiple output implementations?

I have an app that “draws” shapes given a shape name and color and must support different output methods (e.g., write to console, write to a file). Are these output implementations instances of the Adapter pattern (wrapping console/file APIs to a common Draw(name, color) interface) or the Strategy pattern (different drawing algorithms selected at runtime)? The same applies to sending email via different third‑party libraries — is that an Adapter or a Strategy?

Which pattern should I choose and why? What practical criteria and indicators (intent, interface compatibility, runtime selection, behavioral vs structural role, coupling) distinguish Adapter from Strategy, with short examples?

For multiple output implementations like drawing shapes to a console or file, pick the Strategy pattern from design patterns—it lets you swap drawing behaviors at runtime without changing your core app code. Use the Adapter pattern when wrapping third-party email libraries with mismatched interfaces to fit your SendEmail() method. The choice boils down to whether you’re dealing with interchangeable algorithms (Strategy) or making incompatible APIs compatible (Adapter), as developers on Stack Overflow point out.

Contents

What is the Adapter Pattern?

You reach for the Adapter pattern when a third-party library or legacy code has an interface that doesn’t match what your app expects. It acts like a translator, wrapping the awkward API in a compatible shell so your code calls Draw(name, color) or SendEmail(), oblivious to the mess underneath.

Take a file writer library with methods like writeBytes(byte[] data). Your app wants a simple outputText(string text). The adapter converts between them—no changes to the library or your core logic. DEV Community nails it: adapters make incompatible interfaces work together without adding new behavior, just bridging the gap.

This design patterns staple is structural, focusing on composition over inheritance. You define a target interface (e.g., IOutput), then create adapters that delegate to the adaptee (the console or file API).

What is the Strategy Pattern?

The Strategy pattern shines when you have a family of algorithms or behaviors—like rendering shapes differently—and want to pick one at runtime. Your app stays lean: it holds a strategy reference and calls Execute(), swapping implementations on the fly.

Refactoring Guru describes it perfectly: strategies are independent objects unaware of each other, letting you define interchangeable behaviors. For drawing, one strategy prints ASCII art to console, another saves SVG to file. No if-else chains polluting your code.

It’s behavioral, emphasizing runtime flexibility. Clients inject the strategy via constructor or setter, decoupling the “what” (draw shape) from the “how” (console vs. file).

Adapter vs Strategy: Core Differences

Adapter vs Strategy trips up devs because both involve interfaces and delegation. But peek under the hood:

Aspect Adapter Pattern Strategy Pattern
Intent Convert incompatible interfaces Select among compatible, interchangeable behaviors
Change Trigger Third-party or legacy API mismatch Need to swap algorithms dynamically
Interface Adaptee interface differs from target All strategies share the same interface
Runtime? Usually fixed at construction Swappable at runtime
Role Structural (composes classes) Behavioral (encapsulates algorithms)
Coupling High to adaptee, low to client Low overall—strategies are pluggable

A Stack Overflow thread clarifies: if you pick the implementation at compile-time via a factory, it’s leaning Adapter; runtime selection screams Strategy.

For your shapes app, console/file outputs share a Draw() interface but differ in logic—that’s Strategy. Email libs? Their APIs clash (one uses sendMessage(), another postEmail())—Adapter wraps them.

Practical Criteria to Decide Adapter vs Strategy

Here’s how you decide Adapter vs Strategy for multiple outputs:

  1. Intent Check: Bridging gaps? Adapter. Varying behaviors? Strategy. Console/file drawing varies how you render—Strategy. Third-party email varies what the API expects—Adapter.

  2. Interface Compatibility: Do implementations naturally fit one interface? Yes → Strategy. No, need translation? Adapter. Coderanch forum asks: does the pattern modify a class’s structure? Adapter does via indirection.

  3. Runtime Selection: Need to switch post-startup (user picks output)? Strategy. Fixed wiring? Adapter works.

  4. Behavioral vs Structural Role: Algorithms differ → Behavioral (Strategy). Composition for compatibility → Structural (Adapter).

  5. Coupling Indicators: Tight to one external API? Adapter. Loose, with multiple swappable options? Strategy.

Run this checklist: for drawing, behaviors vary + runtime pick + low coupling = Strategy. Emails: interface mismatch + external libs = Adapter.

When to Use Adapter Pattern

Grab Adapter pattern for:

  • Integrating adapter pattern java libs like old SMTP clients with your IMailSender.
  • Legacy code revival without rewrites.
  • Testing mocks wrapping real APIs.

It keeps your app stable amid external changes. But don’t overuse—if everything needs adapting, rethink your interfaces.

When to Use Strategy Pattern

Deploy Strategy pattern when:

  • Multiple strategy pattern java algos, like sorting or output formats.
  • Configurable behaviors, e.g., user selects console/file via UI.
  • Plugins: load new strategies dynamically.

It scales beautifully for your shapes: add PDF output? New strategy class, done.

Example: Drawing Shapes with Multiple Outputs

Your app draws circles in red. Strategies handle outputs.

csharp
// Strategy interface
public interface IDrawStrategy {
    void Draw(string shape, string color);
}

// Console strategy
public class ConsoleDrawStrategy : IDrawStrategy {
    public void Draw(string shape, string color) {
        Console.WriteLine($"Drawing {shape} in {color} (ASCII art here)");
    }
}

// File strategy
public class FileDrawStrategy : IDrawStrategy {
    private string filename;
    public FileDrawStrategy(string filename) { this.filename = filename; }
    public void Draw(string shape, string color) {
        File.WriteAllText(filename, $"<svg><circle fill='{color}'/></svg>");
    }
}

// Client
public class ShapeDrawer {
    private IDrawStrategy strategy;
    public ShapeDrawer(IDrawStrategy strategy) { this.strategy = strategy; }
    public void DrawShape(string shape, string color) { strategy.Draw(shape, color); }
}

// Usage
var drawer = new ShapeDrawer(new FileDrawStrategy("output.svg"));
drawer.DrawShape("circle", "red");  // Runtime switch!

Pure Strategy pattern—behaviors pluggable, no console/file specifics in ShapeDrawer.

Example: Email Providers with Third-Party Libraries

Third-party libs: SendGrid (client.send(msg)), Mailgun (mg.messages().send()). Adapter unifies.

csharp
// Target interface
public interface IEmailSender {
    void SendEmail(string to, string subject, string body);
}

// SendGrid Adapter
public class SendGridAdapter : IEmailSender {
    private SendGridClient client;
    public SendGridAdapter(string apiKey) { client = new SendGridClient(apiKey); }
    public void SendEmail(string to, string subject, string body) {
        var msg = new SendGridMessage() { From = new EmailAddress("you@ex.com") };
        msg.AddTo(new EmailAddress(to));
        msg.Subject = subject;
        msg.PlainTextContent = body;
        client.SendEmailAsync(msg);  // Adapts to your interface
    }
}

// Mailgun Adapter (similar wrapping)

// Client
public class NotificationService {
    private IEmailSender sender;
    public NotificationService(IEmailSender sender) { this.sender = sender; }
    public void NotifyUser(string email, string alert) {
        sender.SendEmail(email, "Alert", alert);
    }
}

Adapters handle API mismatches. Inject SendGridAdapter or MailgunAdapter at startup—leans Strategy-ish, but core is adaptation.

Combining Adapter and Strategy

Often, you blend them. Wrap third-party email with adapters (IEmailSender), then treat adapters as strategies in a EmailStrategyContext. Medium on RecyclerView shows Adapter for data-view, Strategy for layouts—same vibe.

For your app: Strategies compose adapters. Runtime pick from adapted providers.

Common Pitfalls and Anti-Patterns

  • Using Adapter for behaviors: Creates rigid wrappers.
  • Strategy with incompatible strategies: Forces mini-adapters.
  • God objects: Inject context everywhere.
  • No DI container: Hardcodes strategies.

LMU notes warn: Strategy avoids windowing system ties—keep strategies pure.

Test by swapping: Strategy passes easily; Adapter fails if interfaces clash.

Sources

Conclusion

For drawing shapes across console or file, Strategy pattern wins with its runtime flexibility and swappable behaviors. Third-party email mismatches scream Adapter pattern to unify interfaces first. Weigh intent (behavior vs compatibility), runtime needs, and coupling—your shapes app gets Strategy, emails start with Adapter, maybe layered. These design patterns keep code clean as you scale outputs. Experiment with the examples; they’ll click fast.

Authors
Verified by moderation
Moderation
Adapter vs Strategy Pattern: How to Decide for Outputs