5 min read

Software Design Principles - Interfacing and Separation of Concerns


When building software, especially when business needs changes consistently, as an engineer, you need to not only make it work, but make future-proof. You don’t want you future self and your team hate you for making their jobs ten times harder when you could have designed the system to eliminate all of the stress.

As your application grows, time-to-release will begin to exponentiate and you’re start to piss a lot of people off when you have to tell them that simple feature a will take about a month to update due to the poor design and architecture approach.

In my example, I will go over how software design can be applied to a real life scenario and how you can use interfaces (sub classes) to take advantage of class inheritance without having override subclasses later down the road.

This design pattern is called the Strategy pattern.

Pretend that you have a Vehicle Factory and you need to create blueprints on what properties and behaviors these vehicles have. When we think of “vehicles”, the first thought that comes to our mind is a car. Cars usually have four doors, 4-5 seats, an gas engine that runs and so fourth.

Some developers would quickly start off by creating a Car class with methods and variables related to a car. For example, startEngine() and numberOfDoors.

But what about a motorcycle? Or a jet ski? An airplane?

Adding a new method like addThrottle() , pullClutch() or popWheelie() may add flexibility for a motorcycle but then that means non-cycled vehicles like cars, trucks, aircraft and boats will also inherit methods that will not make sense. The Vehicle super class will quickly become cluttered and bloated making it very hard to manage and maintain over time. Especially when you have more junior engineers on your team.

To fix this problem, we need to determine which properties and methods are constant and variant. Starting with constants, let’s identity what doesn’t change amongst all vehicles.

Here’s the list that comes to mind

  • VIN
  • Owner
  • Make
  • Model
  • Year
  • Color

That’s it.

We know for a fact that all vehicles will have the characteristics listed above. Anything outside of this will be based on the class of vehicle the object belongs to.

Here’s what the Vehicle super class look like.

class Vehicle 
{
	constructor(
		VIN, 
		Owner,
		Make,
		Model,
		Year,
		Color
	)
	{
		this.VIN = VIN
		this.Owner = Owner
		this.Make = Make
		this.Model = Model
		this.Year = Year
		this.Colr = Color
	}

	getDetails()
	{
		return `
		${VIN} 
		${Owner} 
		${Year} 
		${Make} 
		${Model} 
		${Color}
		`
	}
}

When creating a Car we need to create a subclass that will interface the Vehicle super class. In the Car class, we can add methods like startEngine() and initializeCarPlay(). The Car subclass will look something like this.

class Car extends Vehicle 
{ 
	constructor(VIN, Owner, Make, Model, Year, Color) 
	{ 
		super(VIN, Owner, Make, Model, Year, Color); 
	} 
	
	lockDoors() 
	{ 
		return "Doors locked"; 
	} 
	
} 

const myHondaAccord = new Car("1HG2352", "John Doe", "Honda", "Accord", 2020, "Black");

This pattern allows cars to easily inherit new vehicle properties and methods and additional methods just for cars without other vehicle types from inheriting that. Meaning an EletricScooter, will still have the Vehicle properties while ensuring that only vehicles (Cars) are given the method to lockDoors().

Wait!

You got a new stakeholder request, and they want this shit done ASAP.

Time to now see another example how software design principles can help you make changes easily.

You now need to add CarPlay to all cars, but only cars, not motorcycles or watercraft. We can do this by simply adding it to the Car sub class. Now all cars have CarPlay capability.

class Car extends Vehicle 
{ 
	constructor(VIN, Owner, Make, Model, Year, Color) 
	{ 
		super(VIN, Owner, Make, Model, Year, Color); 
	} 
	
	lockDoors() 
	{ 
		return "Doors locked"; 
	} 
	
	initCarPlay() 
	{ 
		return "CarPlay initialized"; 
	} 
} 

Yay.

Imagine if we added the initCarPlay method to the Vehicle super class, we would have to override each vehicle that is not a car to not use CarPlay. Imagine if you had 1000 vehicles? Even if we never tell the new vehicle to use that method, it’s still sloppy, causes confusion and opens doors for vulnerabilities down the road.

Spending hours, or days on what many non-technical folks in theory would call a “small update”, will start to make your stakeholders lose confident in the delivery. This also blocks other developers from getting things done too.

Let's Connect

If you want to get in touch with me about something or just to say hi, feel free to reach out.