Introduction to LLD 101

Ishan Aggarwal
6 min readJan 29, 2023

--

Introduction to low level design and OOP

High Level Design

  • Overview
  • Not going into details
  • Abstract Idea
  • Bird Eye View of the system

Different Infra layers that are going to work together for efficient working of a software system.

At the end Databases, cache servers, app servers etc. are nothing but computers running different software’s.

Low Level Design

  • Details of the software that is running.
  • Details of the code (Organization of the code to form the individual software’s)
  • Implementation of Software system.

LLD helps you to achieve the following -

  • Requirement Gathering
  • Readable/ Understandable code
  • Extendable — Easy to add new features to existing codebase.
  • Maintainable — Easy to keep the current system working. (Includes fixing bugs, updates to the codebase)

OOP

Software system should consists of entities and each entity controls its attributes and also has a defined behavior associated with it.

class Student {
private int id;
private String name;

public Student(int id, String name) {
this.id = id;
this.name = name;
}

public void printStudent() {
System.out.println("id: " + id + ", name: " + name);
}
}

Pillars/ principle of OOP

  • Abstraction
  • Abstraction is a model of real-world object limited to specific context. Representation of essential features without including the implementation level details. We can simply say it is concept of making something (i.e. What and not how).
  • Encapsulation
  • It is the ability of an Entity (object) to hide parts of its state and behavior from other objects, exposing only a limited interface to the rest of the program. (Stores the attributes and behaviors together of an idea.)
  • To encapsulate something means to make it private and thus accessible from within the methods of its own class.
  • Protected is less restrictive, and makes a member of the class available to subclasses as well.
  • Protects the attributes and behavior from illegitimate access from other classes.
  • Inheritance
  • It is the ability to build new classes on top of existing classes.
  • Some of the benefits of inheritance is code reuse, extend original and put extra functionality.

Scenario: Let’s say we are required to have a run() method in Animal class. But tortoise can’t run.

Issues:

  • We cannot hide a method in subclass if declared in superclass. Need to implement even if they don’t make sense in the sub-class.
  • Let’s say we add a new method to superclass and provide a default implementation. It may happen that not all the sub-classes might need that implementation and in order to do this — we might start throwing an exception from sub-class. But then there will be tight coupling between 2 classes.
  • Update in a piece of code caused issues in another piece of code. You should NOT enforce your children/clients to implement methods that they don’t want to.
  • We might create a superclass for that behavior. Add example of 3 attributes: Flying and Swimming and Running => 2^n subclasses are possible to support all the combinations of above attributes (class explosion).
  • Interfaces
  • Comes to rescue in such scenarios. We should have an interface defined for each sub behavior. Then Child classes can implement multiple interfaces while they can only extend 1 class at a time.
  • Interfaces promotes loose coupling.
  • Add a RunnableAnimal, DomesticAnimal interface and show how code will change. Still issue in refactoring that we will need to add this interface to multiple classes.
  • Polymorphism
  • It is the ability of a program to detect the real class of an object and call the corresponding implementation even when the real type is unknown in current context.
  • Compile time polymorphism (Method overloading)
  • Runtime polymorphism (Method overriding/ Dynamic binding)

Relationship between objects

Association:

It is a type of relationship in which Object of one class uses or interacts with Object of another class. Association is represented by something like a field/ attributes in a class. Association is also termed as “has-A relationship”.

Composition:

Composition is a “whole-part” relationship between two objects. (Strong dependency). One of which is composed of one or more instances of the other. A component can only exist as a part of the container. For example, University (Container) — — -> Department (Component)

So, basically the the object Department is constructed inside the constructor of University object. As soon as the University object is destroyed, the Department object will also get destroyed. Such relationship is called as composition.

Aggregation: Aggregation is less strict variant of composition where one object merely contains a reference of another. The outer container does not control the life cycle of the inner component. The inner component can exist without the parent container and can be linked to several containers at the same time. For example, Department (Container) — — -> Professor (Component)

So, basically the object Professor is constructed outside and then its reference is passed to the constructor of Department class. In that case, even if the Department object is destroyed, the inner object Professor can still exits. The reason for such use-case could be that a Professor can actually teach different subjects in different departments.

Generalization:

Generalization is the process of taking out common properties and functionalities from two or more classes and combining them together into another class which acts as the parent class of those classes or what we may say the generalized class of those specialized classes. All the subclasses are a type of superclass. So we can say that subclass “is-A” superclass. Therefore Generalization is termed as “is-A relationship

Terminology

Class Blueprint/ structure of an idea.

Object Real instance of a class that takes Memory. Each object is completely separate from other objects.

Access Modifiers

Public: Anyone outside can access this member from anywhere. Private: Can be accessed by no one from outside. Not even child classes. Accessible from the same class only. Protected: Can be accessed by any class that extends the original class. (Child class in same package or different package) Default: Can be accessed by any class with in the package or child classes with in the same package.

Default Constructor If we don’t define our own constructor, then a default constructor is created without any arguments. Set every attribute of a class to its default value unless we have given a different default value. Created only if we don’t define our own constructor. It’s public in nature.

Overloaded Constructor It initializes the object with the default values provided in the constructor arguments.

Copy Constructor — We already have an object of our class. — We want to create a new object using the existing object that has the same values as of the existing object.

Shallow copy — Created a new Object from some other object but behind the scenes the new object still refers to a few attributes of the old object. New and old objects still share some data (attributes).

Deep copy — No shared data between new and old object.

Java Pass by value or Pass by reference

For Primitive types, parameters are pass-by-value. — For Object types, the object reference is pass-by-value.

Pass By Value Example (Primitive Data Types)

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class PrimitivesUnitTest {
@Test
public void whenModifyingPrimitives_thenOriginalValuesNotModified() {
int x = 1;
int y = 2;
// Before Modification
assertEquals(x, 1);
assertEquals(y, 2);
modify(x, y);
// After Modification
assertEquals(x, 1);
assertEquals(y, 2);
}
public static void modify(int x1, int y1) {
x1 = 5;
y1 = 10;
}
}

Pass By Reference Example (Object Data Types)

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class NonPrimitivesUnitTest {
@Test
public void whenModifyingObjects_thenOriginalObjectChanged() {
Foo a = new Foo(1);
Foo b = new Foo(1);
// Before Modification
assertEquals(a.num, 1);
assertEquals(b.num, 1);
modify(a, b);
// After Modification
assertEquals(a.num, 2);
assertEquals(b.num, 1);
}
public static void modify(Foo a1, Foo b1) {
a1.num++;
b1 = new Foo(10);
b1.num++;
System.out.println(b1.num == 11);
}
}
class Foo {
public int num;
public Foo(int num) {
this.num = num;
}
}

Thank you so much for reading this article. If you found this helpful, please do share it with others and on social media.

Follow me for regular updates on Low and High Level System Design Problems.

--

--

Ishan Aggarwal

Consulting Principal MTS @ Oracle Cloud Infrastructures | Works on designing highly Scalable and Distributed Systems