We can see in the diagram that there are 3 subsystems: ClientSubsystem, EnterpriseSubsystem and BackendSubsystem. While this implementation is working perfectly, imagine down the line you needed to make changes to the EnterpriseSubsystem. Since BackendSubsystem is dependent on EnterpriseSubsystem, changes will also be needed on BackendSubsystem to make it compatible to EnterpriseSubsystem. We may even need to make changes to ClientSubsystem based on those changes. An alternative is to to use a High Level Dependency. High Level Dependency involves having concrete classes be dependent on flexible high level classes (such as interfaces and abstract classes). As seen in the diagram, if we only make changes to the EnterpriseSubsystem concrete class and not touch the interface that EnterpriseSubsystem inherits from, the other subsystems do not need alterations. With high level, the code base is generalized such that we can decouple the method calls in each subsystem. The dependency is on generalized behaviors instead of the implementation.

Composing Objects Principle

Aggregation allows concrete classes to delegate to other concrete classes while providing a looser level of coupling than inheritance.

This principle uses generalization, abstraction and polymorphism as means of indirection. In this principle, we argue that inheritance has the cost of coupling and should be replaced with aggregation. The coupling in inheritances can be seen below:

The child class at the bottom is dependent on the top parent class. This makes the entire system tightly coupled. Composition allows the user to dynamically add methods to the classes, without having to make the classes be tightly dependent. Common design patterns to implement this principle are Composite Design Pattern and Decorator Design Pattern. This allows concrete classes to delegate to other concrete classes while providing a looser level of coupling than inheritance. Another advantage of this principle is that the behavior of objects can be dynamically changed during runtime, while inheritance restricts changes to the compile time only.

However, a major disadvantage of this principle is that aggregation might cause very similar implementation of similar classes, while inheritance has the benefit of sharing code. This means that we need to allocate time and budget to provide implementation of all behavior. Hence, whether to use inheritance or aggregation depends on the specific use cases. Do you have a set of related classes or unrelated classes? What is a common behavior between them? Do you need special classes to handle specialized cases or do you just need a different implementation of the same behavior?

Interface Segregation Principle

No client should be forced to depend on methods it does not use.

This principle splits interfaces that are very large into smaller and more specific ones so that clients will only have to know about the methods that are of interest to them. To explain this principle better, let’s imagine a grocery store. You need a system so that the grocery can get money quickly in an organized fashion, while clients wish to be served quickly. A solution to this is to create a system. Let’s call this ICashier. The ICashier can scan items, take payments and alot time for taking breaks. Grocery store managers are happy because money can be entered quickly onto this system using bar code scanners while clients are happy to be able to purchase items without having to stand in line for a long time. Let’s look at 2 implementations below: