Interfaces: because sometimes one implementation just isn't enough
Tags: beginners, typescript, swift, kotlin
Published: 2023-01-11 13:35:10

---
title: Interfaces: because sometimes one implementation just isn't enough
published: true
date: 2023-01-11 03:36:45 UTC
tags: Beginners,TypeScript,Swift,Kotlin
canonical_url: https://techdevblog.io/interfaces-because-sometimes-one-implementation-just-isnt-enough/
---

## What are interfaces?
 ![Interfaces: because sometimes one implementation just isn't enough](https://techdevblog.io/content/images/2023/01/image_0.jpg)

An interface in programming is a type that defines a set of related behaviors that a class or struct must implement. It's like a contract that specifies what methods a class must implement, without defining how those methods should be implemented.

Interfaces are a way to specify what a class does, rather than how it does it. This allows for greater flexibility in how objects are created and used, since the implementation of an interface can be swapped out at runtime.

## Why use interfaces?

There are several advantages to using interfaces in programming:

- **Code decoupling** : interfaces allows us to decouple our code more easily and improve its maintainability. They allow us to define a set of related behaviors that can be implemented in different ways, making it easier to change the implementation of a particular behavior without affecting the rest of the code.
- **Simplifying dependency injection** : Interfaces allow for more flexibility in how objects are created and used, since the implementation of an interface can be swapped out at runtime. This makes it easier to inject dependencies into an object, which can be useful for things like unit testing.
- **Facilitated testing** : this decoupling of code and the use of dependency injection also make it easier to test, as it allows us to isolate specific parts of the code and test them in isolation. Interfaces make it easier to write automated tests, since we can create mock objects that implement the same interface as the objects we are testing. This allows us to test the behavior of an object without worrying about its implementation details.
- **Enabling more complex architectures** : Interfaces allow us to build more complex architectures by allowing us to define a set of behaviors that can be implemented by different classes in different ways. Allowing us to create flexible, modular code. This is particularly useful when building larger applications, as it allows us to divide the application into smaller, independent components that can be developed and maintained separately. This can be useful for things like implementing a plugin system or building a modular application.

## Best practices

Here are some best practices for using interfaces in your code:

- **Keep interfaces small and focused** : A good interface should define a small, focused set of related behaviors. Avoid including unrelated behaviors or methods in your interfaces.
- **Use interface names that describe their purpose** : Choose descriptive names for your interfaces that accurately describe their purpose.
- **Avoid using "I" prefixes for interface names** : While it's common to prefix interface names with an "I" in some programming languages (e.g. "IMyInterface"), this practice is not followed in all languages and can lead to confusion. It's generally better to use a descriptive name for your interface.

## Examples in a few different programming languages

Here are some examples of how to define and implement interfaces in different programming languages:

### TypeScript

```
// Define an interface
interface Shape {
  area(): number;
}

// Implement the interface
class Rectangle implements Shape {
  width: number;
  height: number;
  
  constructor(width: number, height: number) {
    this.width = width;
    this.height = height;
  }
  
  area() {
    return this.width * this.height;
  }
}

const rectangle = new Rectangle(10, 20) as Shape;
console.log(rectangle.area()); // Outputs: 200

```

### Swift

```
// Define an interface
protocol Shape {
  func area() -> Double
}

// Implement the interface
struct Rectangle: Shape {
  var width: Double
  var height: Double
  
  func area() -> Double {
    return self.width * self.height
  }
}

let rectangle = Rectangle(width: 10, height: 20)
print(rectangle.area()) // Outputs: 200

```

### Kotlin

```
// Define an interface
interface Shape {
  fun area(): Double
}

// Implement the interface
class Rectangle(val width: Double, val height: Double): Shape {
  override fun area() = width * height
}

val rectangle = Rectangle(10.0, 20.0)
println(rectangle.area()) // Outputs: 200.0

```

### C#

```
// Define an interface
public interface Shape {
  double Area();
}

// Implement the interface
public class Rectangle : Shape {
  public double Width { get; set; }
  public double Height { get; set; }
  
  public Rectangle(double width, double height) {
    Width = width;
    Height = height;
  }
  
  public double Area() {
    return Width * Height;
  }
}

Shape rectangle = new Rectangle(10, 20);
Console.WriteLine(rectangle.Area()); // Outputs: 200

```

### C++

```
// Define an interface
class Shape {
public:
  virtual double area() = 0;
};

// Implement the interface
class Rectangle: public Shape {
private:
  double width;
  double height;

public:
  Rectangle(double width, double height) {
    this->width = width;
    this->height = height;
  }
  
  double area() {
    return width * height;
  }
};

Shape* rectangle = new Rectangle(10, 20);
cout << rectangle->area() << endl; // Outputs: 200

```

### Python

```
# Define an interface
class Shape:
    def area(self) -> float:
        pass

# Implement the interface
class Rectangle(Shape):
    def __init__ (self, width: float, height: float) -> None:
        self.width = width
        self.height = height
    
    def area(self) -> float:
        return self.width * self.height

rectangle = Rectangle(10, 20)
print(rectangle.area()) # Outputs: 200.0

```

### Ocaml

```
(* Define an interface *)
module type SHAPE = sig
  type t
  val area: t -> float
end

(* Implement the interface *)
module Rectangle: SHAPE = struct
  type t = { width: float; height: float }
  let area { width; height } = width *. height
end

let rectangle = { width = 10.0; height = 20.0 }
print_float (Rectangle.area rectangle); (* Outputs: 200.0 *)

```

## Use cases

Any time we need to decouple our is a great use case for interface. Here are a few examples of use cases for interfaces in different programming languages:

### Use case: Modular application in TypeScript

In this example, we'll use interfaces to build a modular application in TypeScript. We'll define an interface called `Widget` that defines a set of methods that all widgets must implement, including `render()` and `update()`. Then, we'll create a class called `WidgetManager` that can discover and load widgets that implement the `IWidget` interface at runtime.

```
// Define the IWidget interface
interface Widget {
  render(): void;
  update(): void;
}

// Implement the IWidget interface
class MyWidget implements Widget {
  render() {
    console.log("Rendering MyWidget");
  }
  
  update() {
    console.log("Updating MyWidget");
  }
}

// WidgetManager class that can discover and load widgets at runtime
class WidgetManager {
  widgets: Widget[];
  
  constructor() {
    this.widgets = [];
  }
  
  loadWidgets() {
    // Load widget modules
    const widgetModules = [MyWidget];
    
    // Create instances of all widgets
    for (const widgetModule of widgetModules) {
      this.widgets.push(new widgetModule());
    }
  }
  
  renderWidgets() {
    for (const widget of this.widgets) {
      widget.render();
    }
  }
  
  updateWidgets() {
    for (const widget of this.widgets) {
		widget.update();
	}
  }
}

```

To use the `WidgetManager`, you can do the following:

```
const widgetManager = new WidgetManager();
widgetManager.loadWidgets();
widgetManager.renderWidgets();
widgetManager.updateWidgets();

```

### Use case: Protocol-oriented programming in Swift

In this example, we'll use interfaces (called `protocols` in Swift) to implement protocol-oriented programming in Swift. We'll define a protocol called `Shape` that defines a single method called `area()`, which returns the area of a shape. Then, we'll create two structs that implement the `Shape` protocol: `Rectangle` and `Circle. `

```
// Define the Shape protocol
protocol Shape {
  func area() -> Double
}

// Implement the Shape protocol
struct Rectangle: Shape {
  var width: Double
  var height: Double
  
  func area() -> Double {
    return self.width * self.height
  }
}

struct Circle: Shape {
  var radius: Double
  
  func area() -> Double {
    return Double.pi * self.radius * self.radius
  }
}

// Create instances of the Rectangle and Circle structs
let rectangle = Rectangle(width: 10, height: 20)
let circle = Circle(radius: 5)

// Print the area of the shapes
print(rectangle.area()) // Outputs: 200
print(circle.area()) // Outputs: 78.53981633974483

```

Using protocols in this way allows you to define a set of behaviors that can be implemented by different types in different ways, and to treat those types as a single type when it makes sense to do so. This can be useful for things like creating collections of shapes, or writing functions that work with any type that implements the `Shape` protocol.

### Use case: Plugin system in C#

In this example, we'll use interfaces to build a simple plugin system in C#. We'll define an interface called `Plugin` that defines a single method called `Execute()`, which will be used to execute the plugin. Then, we'll create a class called `PluginManager` that can discover and load plugins that implement the `Plugin` interface at runtime.

```
// Define the IPlugin interface
public interface Plugin {
  void Execute();
}

// Implement the IPlugin interface
public class MyPlugin: Plugin {
  public void Execute() {
    Console.WriteLine("MyPlugin is executing!");
  }
}

// PluginManager class that can discover and load plugins at runtime
public class PluginManager {
  public IEnumerable<Plugin> LoadPlugins() {
    // Load assemblies in the current directory
    foreach (var assembly in Directory.GetFiles(Environment.CurrentDirectory, "*.dll")) {
      // Load the assembly
      var pluginAssembly = Assembly.LoadFrom(assembly);
      
      // Find all types that implement the IPlugin interface
      foreach (var type in pluginAssembly.GetTypes().Where(t => typeof(Plugin).IsAssignableFrom(t))) {
        // Create an instance of the plugin
        yield return (Plugin)Activator.CreateInstance(type);
      }
    }
  }
}

// Use the PluginManager to load and execute plugins
var pluginManager = new PluginManager();
foreach (var plugin in pluginManager.LoadPlugins()) {
  plugin.Execute();
}

```

### Use case: Dependency injection in Python

In this example, we'll use interfaces to implement dependency injection in Python. We'll define an interface called `Logger` that defines a single method called `log()`, which can be used to log data and events, either to the console, to a logfile, or to a remote service:

```
#Define an interface
class Logger:
    def log(self, message: str) -> None:
        pass

# Implement the interface
class ConsoleLogger(Logger):
    def log(self, message: str) -> None:
        print(message)

# Use the interface for dependency injection
class UserService:
    def __init__ (self, logger: Logger):
        self.logger = logger
    
    def create_user(self, name: str) -> None:
        self.logger.log(f"Creating user {name}")
        # ...

# Inject the ConsoleLogger into the UserService
user_service = UserService(console_logger)
user_service.create_user("Alice")

```

# Conclusion

In conclusion, interfaces are the bomb! They make it easier to inject dependencies, swap out implementations at runtime, build modular application or use complex architectures, and write automated tests. Whether you're using protocols in Swift, interfaces in Java, ABCs (Abstract Base Classes) in Python, or something else, there are endless possibilities for using interfaces to improve the structure and modularity of your code.

And let's be real, who doesn't love a good interface? Go out there and interface-ate your code like it's 1999 (but with better technology)

<!--kg-card-end: markdown-->