Principles of Package Design

Shadman Jamil
5 min readJan 27, 2024

The design adds value faster than it adds costs

As we know, software/application development grows and more complex over time, and the need for organizing classes into packages becomes more important. But how do we decide what lives in a package?

We will use package design principles to create packages, which will be correct in terms of cohesion and coupling, which will also make your code easy to understand, modular, and maintainable at the same time. It will also help you understand how to reuse, share, and distribute your code components.

Before moving to package design principles, we need to understand SOLID principles, which are highly required to improve your class designing skills.

SOLID Principles for Class Design

In this section, we will walk you through the five SOLID principles that will help you understand and improve your classes' design.

Make this clear before deep diving: the SOLID principles for class design are there to prepare your code base for future changes and reuse.

The principles of package design in software engineering revolve around creating modular, maintainable, and reusable code components. Here are some key principles:

1. Single Responsibility Principle (SRP)

A class should have one and only one reason to change, meaning that a class should have only one job.

For example, consider an application that takes a collection of shapes (circles, and squares) and calculates the sum of the area of all the shapes in the collection. We have to create 3 different classes Circle, Square, and Area Calculator.

This principle is one of the most commonly used design principles in object-oriented programming. You can apply it to classes, software components, and microservices.

2. Open/Closed Principle (OCP)

Objects or entities (classes, modules, functions, etc.) should be open for extension but closed for modification.

This means that a class should be extendable without modifying the class itself. It uses interfaces instead of super-classes to allow different implementations, which you can easily substitute without changing the code that uses them. The interfaces are closed for modifications, and you can provide new implementations to extend the functionality.

This principle is the most important principle of object-oriented design

3. Liskov Substitution Principle (LSP)

This applies to inheritance hierarchies, such that a subclass or derived class should be substitutable for its base or parent class.

It extends the Open/Closed principle and enables you to replace objects of a parent class with objects of a subclass without breaking the application. This requires all subclasses to behave in the same way as the parent class. To achieve that, your subclasses need to follow these rules:

  • Don’t implement any stricter validation rules on input parameters than implemented by the parent class.
  • Apply at least the same rules to all output parameters as applied by the parent class.

4. Interface Segregation Principle (ISP)

This states that clients should not be forced to depend upon interface members they do not use. In other words, do not force any client to implement an interface that is irrelevant to them.

By following this principle, you prevent bloated interfaces that define methods for multiple responsibilities. As explained in the Single Responsibility Principle, you should avoid classes and interfaces with multiple responsibilities because they often change and make your software hard to maintain.

This is only achievable if you define your interfaces to fit a specific client or task.

5. Dependency Inversion Principle (DIP)

This states that we should depend on abstractions (interfaces and abstract classes) instead of concrete implementations (classes). The abstractions should not depend on details; instead, the details should depend on abstractions.

The general idea of this principle is as simple as it is important: High-level modules, which provide complex logic, should be easily reusable and unaffected by changes in low-level modules, which provide utility features. To achieve that, you need to introduce an abstraction that decouples the high-level and low-level modules from each other.

Based on this idea, the Dependency Inversion Principle consists of two parts:

  1. High-level modules should not depend on low-level modules. Both should depend on abstractions.
  2. Abstractions should be independent of details. Details should depend on abstractions.

Package Design

In this part, we will discuss the best practices of package design and cover both package cohesion principles and package coupling principles.

1. Package Cohesion

Cohesion principles show you which classes should be put together in a package when to split packages, and if a combination of classes may be considered a “package” in the first place.

Over the years, you struggle to organize your code in the best way possible. While doing so, you’ll gradually develop a strong sense of “belonging together”. It will help you decide whether two pieces of code belong together.

This instinct keeps evolving forever: when you’re writing statements and putting them into functions when you write classes for those functions, and when you combine those classes into packages.

A. Release/Reuse Equivalence Principle (REP)

This principle states that the unit of release should be the unit of reuse. This means that code that is intended to be reused should be released as a separate unit, such as a module or library.

B. Common Reuse Principle (CRP)

This principle states that the design of the code should be designed for reuse, focusing on loose coupling and well-defined interfaces, allowing for seamless integration and collaboration across multiple applications.

C. Common Closure Principle (CCP)

This principle states that classes or modules that change for similar reasons should be grouped. This principle helps to ensure that changes to the code are localized to a specific area, making it easier to manage and maintain the code.

2. Package Coupling

Coupling principles help you choose the right dependencies and prevent wrong directions in the dependency graph of your packages.

Most classes can not survive on their own, they have some dependency and most likely even multiple dependencies. Maybe they need an instance of another class to delegate some of the work. Or they produce instances of another class. In other words, many classes depend on other classes, which couples them with each other.

A. Acyclic Dependencies Principle (ADP)

This principle states that a software system should be organized in a way that there are no circular dependencies between modules or components. This means that modules should only depend on modules that are lower in the hierarchy, and there should be no direct or indirect dependencies between modules at the same level or higher in the hierarchy.

B. Stable Dependencies Principle (SDP)

This principle states that high-level modules should depend on stable modules, and low-level modules should depend on less stable modules. In simpler terms, this principle advocates for a dependency structure where more stable modules reside at higher levels of the hierarchy, while less stable modules are positioned lower.

C. Stable Abstractions Principle (SAP)

This principle states that a component should be as abstract as it is stable. In simpler terms, this principle advocates for a balance between abstraction and stability within a software system. It suggests that more stable components should be made more abstract, while less stable components should be less abstract.

Conclusion

By applying the SOLID principles, promoting high cohesion within packages, and maintaining low coupling between packages, developers can create software systems that are well-structured, easy to understand, maintain, and reuse. These principles serve as a guide for creating software that is robust, flexible, adaptable, and able to meet the evolving needs of users.

--

--

Shadman Jamil

Tech Savvy, Software Architect, Mentor, and Entrepreneur