SOLID Principles


In Robert C. Martin’s Design Principles and Design Patterns, he details four signs of a poor architecture1:

  1. The first of which is rigidity where one change results in a cascade of subsequent changes in dependent modules. He argues that this design deficiency results in reluctance to fix non-critical fixes due to not knowing when these fixes could actually be completed.

  2. Likewise, fragility where one minor change breaks the program in unexpected ways that have no conceptual relationship with the change.

  3. Next, the inability to reuse software named immobility where modules contain so much excess that rewriting a similar module is preferred to integrating the old one.

  4. Lastly, in the face of changing requirements, when design preserving patterns are harder to employ and hacks are the easier alternative, the design is said to have high viscosity. The examples illustrated described version control systems that take hours to check a few files, then engineers will be incentivized to make changes that avoid this, even if they are not the optimal design decisions.

He offers the following heuristics to follow for a well designed software system:

Open/Closed Principle (OCP) details that software entities should be extensible without modification. This means features can be added with little forethought only through adding code and never by changing existing code. The C++ language offers a solution for this through dynamic polymorphism. Specifically with abstract interfaces, late binding is performed where the compiler resolves an overloaded method call of a polymorphic entity at runtime, avoiding a cess of scanning through if/else or switch conditional structures that need modification each time we wish to add a new feature; instead we would only add more overloaded functions through the addition of derived entities of the abstract interface.

Message *Message::create_message(RemReqType rtype)
	Message *msg;
	switch (rtype)
	case INIT_DONE:
		msg = new InitDoneMessage;
	case KEYEX:
		msg = new KeyExchange;
	case READY:
		msg = new ReadyServer;
	case BSC_MSG:
		msg = new BankingSmartContractMessage;
	case CL_QRY:
	case RTXN:
	case RTXN_CONT:
		msg = new YCSBClientQueryMessage;
	case CL_BATCH:
		msg = new ClientQueryBatch;
	case RDONE:
		msg = new DoneMessage;
	case CL_RSP:
		msg = new ClientResponseMessage;
		msg = new ExecuteMessage;
	case BATCH_REQ:
		msg = new BatchRequests;

#if VIEW_CHANGES == true
		msg = new ViewChangeMsg;
	case NEW_VIEW:
		msg = new NewViewMsg;

		msg = new CheckpointMessage;
		msg = new PBFTPrepMessage;
		msg = new PBFTCommitMessage;

		cout << "FALSE TYPE: " << rtype << "\n";
	msg->rtype = rtype;
	msg->txn_id = UINT64_MAX;
	msg->batch_id = UINT64_MAX;
	msg->return_node_id = g_node_id;
	msg->wq_time = 0;
	msg->mq_time = 0;
	msg->ntwk_time = 0;

	msg->lat_work_queue_time = 0;
	msg->lat_msg_queue_time = 0;
	msg->lat_cc_block_time = 0;
	msg->lat_cc_time = 0;
	msg->lat_process_time = 0;
	msg->lat_network_time = 0;
	msg->lat_other_time = 0;

	return msg;

Fig 1. ResilientDB Factory Function Breaking OCP

Liskov Substitution Principle (LSP) states that the substitution of a derived entity in a users program should ensure the same correctness as if it were the parent entity. Plainly, the ‘is-a’ definition of inheritance is not sufficient. In other words, the contract or interface of the base class must be honored by the derived class to not only satisfy the syntactic expectations but also the behavioral ones as its parent2. Violations of this principle usually manifest themselves as bugs; the children may throw an exception, return an incorrect value, or behave in an undefined manner.

#include <iostream>

struct Rectangle {

    int length, width;
    Rectangle(int x, const int y) : length(x), width(y) {};

    void print() {
        std::cout << "Length: " << length << " Width: " << width << std::endl;

struct Square : Rectangle {

    Square(int x) : Rectangle(x, x) {}

void adjustShape(Rectangle* rect, int x, int y) {

    rect->length = x;
    rect->width  = y;

int main(void) {

    Rectangle* rect = new Square(10);  

    adjustShape(rect, 4, 5);

    return 0;

Length: 10 Width: 10

Length: 4 Width: 5

Fig 2. Design Violating LSP. Users Interpret Shapes Differently.

Dependency Inversion Principle (DIP) is the strategy for flexible systems where entities must depend upon interfaces or abstract functions and classes, rather than upon concrete functions and classes. This allows us to migrate our software, create new products or new iterations of the same product, without having to rewrite software unnecessarily because we never change high level entities responsible for orchestration. Strategies for implementing this in C++ involve static polymorphism, template specialization, Adapter Design Pattern, and type-erasure3.

Cover photo
Fig 3. Dependency Structure of an Object Oriented Architecture Following DIP

Interface Segregation Principle (ISP) states that many single-purpose interfaces are superior to a general purpose interface. Violations involve leaving unnecessary methods blank or throwing NotImplemented exception. ISP states interfaces should be designed for each category of entities to avoid a larger less maintainable and reusable interface or avoid ambiguous intent and dependence on unrelated methods. This allows for software to be more customizable and avoid conflicts like forced recompilation and redeployment of a very large part of the design since we don’t have to rely upon or implement functionality we don’t use.


  1. Martin, R.C. (2000),”Design Principles and Design Patterns”, 

  2. Platis, D. (2020). “How to write SOLID C++”, 

  3. Chovatiya, V. (2020), “Dependency Inversion Principle in C++, SOLID as a Rock”,