ISD Lecture 1
ISD Lecture 1
Submitted to:
Submitted By:
Subsection: A2 Group: 04
Contents
1 Importance of OOP Principles 2
2 OOP Features 9
2.1 OOP vs Procedural Programming . . . . . . . . . . . . . . . . . . . . . . . . 9
2.2 Features Introduced in OOP . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.2.1 Encapsulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.2.2 Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.2.3 Polymorphism . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
5 DRY 21
5.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
5.2 Rule of 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
5.3 Cases where DRY principle is used . . . . . . . . . . . . . . . . . . . . . . . 22
5.4 Evil Switch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
5.5 Resolving Evil Switch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
7 Introduction to SOLID 25
7.1 SOLID means Supple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
7.2 What people confuse SOLID with? . . . . . . . . . . . . . . . . . . . . . . . 26
7.2.1 Not a Library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
7.2.2 Not a Framework . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
7.2.3 Not a Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
7.2.4 Not a Goal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
7.3 SOLID code is Different than typical code . . . . . . . . . . . . . . . . . . . 27
7.4 SOLID helps to avoid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
7.5 The five SOLID Principles . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
1
1 Importance of OOP Principles
/* This section is written by roll 1805037 */
Abstraction: By using encapsulation and defining classes with well-defined interfaces, OOP
provides a high degree of abstraction. This means that the user of an object does not need
to know how the object works internally; they only need to know how to use it.It focuses
on the essential features of an object and ignoring the details that are not relevant to the
current context.An example of abstraction in OOP could be a car.
2
A car is an object that has many features such as wheels, engine, transmission, etc.
However, when we use a car, we don’t need to know all the details of how it works. We only
need to know how to drive it - such as how to start the engine, how to accelerate, brake, and
steer.The details of how these methods work will be hidden from the user, allowing them to
focus only on the essential features of the car.
Manageability: OOP supports modularity which allows to break large ,complex program
into smaller ,more manageable components.Each object in an OOP program is responsible
for a specific task, and the objects interact with each other to accomplish larger tasks. Then
we merge and integrate different components and manage the whole process of combination
of different modules,logics and codes.
For example,in building a software application for a car rental company,we create an
object for each component of the car rental process, such as customer information, rental
rates, rental contracts, vehicle inventory, and billing. Each object would be responsible for
its own specific task, and the objects would interact with each other to complete the rental
process.
This approach makes the code more manageable because each object can be developed
and tested independently.If we need to update a specific aspect of the car rental process, you
can focus on that specific object without affecting the rest of the program.
Reusability: OOP allows developers to create reusable code components, such as classes
and objects, that can be used in different parts of the codebase. This helps reduce the amount
of code that needs to be written, leading to faster development and less errors..OOP ensures
reusability by providing mechanisms such as inheritance and polymorphism.A class(subclass)
can inherit the properties and behaviour of superclass class through using inheritance .
This means that the subclass automatically has access to all the fields and methods of the
superclass, and can add its own fields and methods as well. This makes it easier to reuse
code, as we can create a superclass with common functionality and then create subclasses
that add or modify that functionality as needed. In polymorphism , a single method can be
used to operate on multiple different objects. This is achieved through method overriding
and method overloading.
3
Figure 2: Example of Reusable in OOP
In the above code,by using OOP and inheritance, we have created a hierarchy of classes
that allows us to reuse the area() method of the Rectangle class to calculate the area of
both rectangles and squares, and we can also calculate the area of circles using a separate
class. We can use the same classes in different parts of our program or in different programs
altogether, making our code more reusable..
Readability: OOP can ensure readability by providing a clear and organized structure
for code, promoting clear and descriptive naming, separation of concerns, encapsulation,
code reuse, and abstraction. By following these principles, developers can create code that is
easier to read, understand, and maintain over time.Through using encapsulation, the internal
workings of a class are hidden from the rest of the program. This can make code more
readable by allowing developers to treat objects as ”black boxes” and focus on the public
interface of the class, without having to understand every detail of how it works internally.
4
By abstracting away low-level details and providing high-level interfaces, developers can focus
on the overall functionality of a program without getting bogged down in implementation
details.
Suppose we want to simulate a library. We can write all the code in a single class, but this
would quickly become complex and difficult to read. Instead, we can create several classes,
each with its own set of properties and methods like book class(deals with books),patron
class(deals with library patrons) and library class(deals with maintaining books).
5
6
Figure 3: Example of Readability in OOP
Productivity: Productivity means that more output is produced with the same amount
of inputs.The efficiency and and increase in production level is achieved by OOP. .OOP
can improve productivity and efficiency by promoting the creation of reusable, maintain-
able,readable and testable code components that can save time and effort and reducing the
likelihood of bugs and errors during the development process resulting in improved software-
development productivity over traditional procedure-based programming techniques The
overall production cost will be low and can gain huge profit with small effort,less error,efficiently
manageable code.
Maintainability: Maintainable code is code that is organized so that it is easy to find and
fix errors and improve performance.OOP is easily to maintain and helpful for big fixing in
future after product release. Since the design is modular, part of the system can be updated
7
in case of issues without a need to make large-scale changes.Usually the notion of adding
features (extensibility or enhancement) is included in the idea of software maintenance as
well.These help organize code into independent subprograms that helps to fix a specific
subprogram without affecting other part or module in the whole application.OOP provides
a well-defined interface for unit testing, which helps during software maintenance since unit
tests can be rerun after refactoring code to determine if problems have occurred.
8
In conclusion,OOP has become a popular programming paradigm because it helps de-
velopers to write modular, maintainable, and scalable code that can be easily adapted to
changing requirements providing a better way to organize and manage complex software
systems.
2 OOP Features
/* This section is written by roll 1805039 */
However, when we wish to write more complex code, we find procedural programming
languages to have an array of disadvantages. Coding in a language like C is tedious because
you have to explicitly tell the machine which steps to go through. For the same reason, code
becomes harder to read and is likely to repeat itself. Classes offer a degree of abstraction
that helps the programmer take a more high-level approach and write modular code that’s
cleaner and more readable.
• Encapsulation
• Inheritance
• Polymorphism
2.2.1 Encapsulation
Encapsulation, in general, is nothing but a fancy word for packaging or enclosing things of
interest into one entity. Used most commonly in the realms of object-oriented programming,
encapsulation refers to the packaging of data and functions that represent an embodiable
(real world) entity into a programmable enclosure (commonly classes/objects).
The data (variables) here represent the attributes and properties that define the unit,
whereas the functions symbolize its possible behaviors and operations. So, in other words,
encapsulation is the binding of data and methods into this one entity to define itself and the
scope of its operations.
9
Additionally, this bundling of data and methods into individual entities hides complexity
by providing abstracted interfaces (the objects) to program and interact with; instead of
directly with lower-level constituents. More importantly, this protects the object’s internal
state from undesirable external access and modification through a concept popularly known
as information hiding. This proves to be a critical aspect for applications dealing with
sensitive data and also prevents failures in one part of the code to bleed into the rest. So in
brief :
• Encapsulation refers to the bundling of fields and methods inside a single class.
• Prevents outer classes from accessing and changing fields and methods of a class
Example In Java :
In the above example, we have a private field age. Since it is private, it cannot be accessed
from outside the class.
Making age private allowed us to restrict unauthorized access from outside the class.
This is data hiding.
10
2.2.2 Inheritance
As with the other principles of OOP, inheritance is meant to optimize the work of program-
mers. The role that inheritance plays in this optimization is in allowing software engineers
to create class hierarchies, where classes and objects inherit properties and behaviors from
their parent (or super) class.
A class that inherits from a parent (or super) class is called a subclass or child class, and
objects that receive properties and behaviors from a parent through inheritance are referred
to as child objects. In summary:
• We don’t need to write the same code multiple times, again and again
• Rather we can use inherit a version of the given properties of one class into the other
just by extending it.
Example In Java :
11
In this example, we have a base class Teacher and a subclass BiologyTeacher. Since class Bi-
ologyTeacher extends the properties from the base class, we need not declare these properties
and method in the subclass.
2.2.3 Polymorphism
Polymorphism in OOP is inseparable and an essential concept of every object-oriented pro-
gramming language. An object or reference basically can take multiple forms in different
instances. As the word suggests, ‘poly’ means ‘many’ and ‘morph’ points at ‘forms’; thus,
polymorphism as a whole would mean ‘a property of having many forms.’
The object-oriented programming language processes classes and objects by a single in-
terface. It implements the concepts of function overloading, overriding, and virtual functions.
Also, it is typically used for instrumenting inheritance in programming.
Polymorphism is one of the significant OOP concepts. Using Polymorphism, we can have
various or multiple forms of objects, variables, or methods. There can be varied implemen-
tations of the same method as per the class’ need using Polymorphism.
The concept of polymorphism offers great scalability and boosts the code’s readability.
We can define polymorphism to have your unique implementation for the same method within
the Parent class. Generally, it can be obtained using inheritance which implies that the class
must belong to the same hierarchy tree. It denotes an object’s ability to adopt several forms
in various instances. It uses the concept of function overriding, function overloading, and
virtual functions.
• Presents the same interface for several different underlying data types
In this example below a superclass called Animal that has a method called animalSound().
Subclasses of Animals are Cow and Hen. They also have their own implementation of an
animal sound (the cow moooes, and the hen chirps etc.)
12
Example In Java :
13
3 Software Engineering Design Guidelines
/* This section is written by roll 1805040 */
As we saw in section 2, Object Oriented Programming has some useful features that come
in handy in software engineering. But these features alone cannot make any code readable,
reusable and easily maintainable. For example, we can create classes in OOP languages.
Each class can have many variables and methods; there is no bound on them. But, when the
number of variables and methods become too huge, it becomes very difficult to keep track
of them, and, as such, the code becomes hard to read, debug and maintain. Similarly, we
can perform nested branching. However, if the branch becomes too deep, then it becomes
difficult to keep track of the branching conditions and as such, if there is any bug, it is very
hard to debug them. To ensure the readability, re-usability and easy maintenance of the
codebase, there are some basic guidelines that one should follow during writing codes for a
software. These are not any hard-and-fast rules, these are more like thumb-rules that make
the developers’ lives easier. In this section, we will take a look at how design guidelines help
us write clean code.
14
Figure 5: Example of Inheritance
In this example, the class Cube extends Rectangle to use its getArea() method.
15
Figure 6: Example of Composition
In this figure, we can achieve the same result as figure 5 by using composition. Here
we have kept an object of the Rectangle2 class in the Cube2 class to use the getArea()
method.
Now, let us create two classes named Summer and Exp, as shown in the following figure.
16
Figure 7: Summer and Exp classes
Next, let us suppose that we want to find the square of the sum of two numbers. To do
this, we require both the getSum() and getExp() methods. We cannot use inheritance to
achieve that, since multiple inheritance is not allowed in Java. But we can easily do that if
we use composition, as shown in the following figure.
17
Figure 8: Example of Composition (2)
As we can see here, using composition is better than using inheritance. There are several
other benefits of using composition over inheritance, including:
• In inheritance, the super class (which we are inheriting from) is defined and cannot
be changed in runtime. Thus, classes and objects created through inheritance are
tightly coupled and changing the parent or superclass in an inheritance relationship
risks breaking the code. Whereas, classes and objects created through composition
are loosely coupled, meaning that we can more easily change the component parts
without breaking the code. Hence, composition offers much more flexibility than
inheritance.
• We can only inherit from one class (in Java and C#), whereas composition allows us
to use functionality from multiple classes.
• Inheritance cannot extend final class, whereas composition allows code reuse even
from final classes.
• In inheritance we need parent class in order to test a child class but composition allows
us to test the implementation of the classes we are using independent of parent or child
classes.
18
This is why, in most cases composition should be used over inheritance. This is a very
common design guideline.
Abstract Method: Methods for which only the signature is given, but not implementa-
tion.
Interface: Interfaces typically can have only abstract methods. Since Java - 8, interfaces
can have default and static methods too. But they cannot have non-abstract non-
static methods, so that classes that implement interfaces are forced to implement the
abstract methods; otherwise they will become abstract classes. Interfaces can have only
static and final variables. Since interfaces mostly have abstract methods, they cannot be
instantiated.
Abstract Class: Abstract class can have both abstract and non-abstract methods. It
can also have final, non-final, static and non-static variables. Since abstract classes will
have at least one abstract method, they cannot be instantiated as well.
When to use which: If we need to have both abstract and non-abstract methods,
we have to use abstract classes. Otherwise, it is better to use interfaces, since a class can
implement multiple interfaces but cannot extend more than one abstract classes.
Let, we are writing an application which has a database accessor layer. We have imple-
mented a ServiceClass which calls a DBClient class. The DBClient class is a concrete
class, programmed to access Postgres DB. Here, the DBClient is a heavy duty class
with all helper methods required to access the DB. Now, if the client decides to switch to
a NoSQL database like MongoDB instead of Postgres DB, or add it as a secondary
database for some specific purposes, then we would have to rewrite the DBClient class
which would be very complicated, and in some cases may break the code.
19
To avoid these situations, these modules should have an abstract super-type like an
interface, as shown in the following figure.
20
4 Importance of Design Principles
/* This section is written by roll 1805041 */
4.1 Overview
In software development, the main challenge isn’t just generating output. Rather the main
challenge is to make one’s code readable for others.
In a typical software project, one has to deal with tens and hundreds of classes. Remem-
bering which class performs what work and which method of which classes, when there is
no structured pattern to your written code, is no easy feat. One must remember that their
code must be easy to read and edit, as software development is done as a team effort, not
just by oneself. Moreover, your code must be eligible for reviews by reviewers.
• Readability: Good code should be easy to read and understand. It should use con-
sistent naming conventions, appropriate comments, and clear and concise logic.
• Modularity: Good code should be modular, with each function or class responsible
for a specific task. This makes the code easier to test, maintain, and modify.
• Scalability: Good code should be scalable, able to handle increasing amounts of data
or traffic without becoming slower or less reliable. It should be designed to be easily
extensible and maintainable.
• Testability: Good code should be testable, with a clear separation between the code
being tested and any external dependencies. It should be designed to be easily testable
using automated unit tests, integration tests, and end-to-end tests.
Some design principles that help coders achieve these are described in the following sections.
5 DRY
/* This section is written by roll 1805041 */
5.1 Introduction
DRY stands for Don’t Repeat Yourself . It is a design principle stating that every piece
of knowledge or functionality in a software system should have a single representation in the
codebase, and that repetition should be eliminated whenever possible.
21
It is the opposite of WET which stands for Write Everything Twice.
The DRY principle is often seen as a way to promote code reusability, maintainability,
and scalability, as it reduces the amount of code that needs to be written, tested, and main-
tained, and also makes it easier to make changes to the codebase without introducing bugs
or inconsistencies.
5.2 Rule of 3
To follow the DRY principle, developers should identify common code patterns or functions
that are used throughout the codebase and factor them out into reusable functions, libraries,
or modules.
In order to do this, whenever a developer copy-pastes any segment of code, he should first
opt for creating abstractions and reusable code instead of copy-pasting. If the code is still
copy-pasted, he should pin the idea in his head that he has done something wrong. When
he goes to copy that code for a third time, he should at once change his code structure to
avoid that copy-paste at all costs.
This is called the Rule of 3 (copy-paste is only okay until the second time, then it must be
resolved any way possible).
By using the rule of 3, developers can reduce code duplication, improve code maintainability,
and make their software systems more scalable and efficient.
• Copy-pasting: If the same code block is used in multiple places in a software system.
The DRY principle suggests that this code should be moved to a single function or
method and called from each location.
• Hardcoded values: Hardcoding values such as file paths, URLs, or database connec-
tion strings etc. The DRY principle suggests that these values should be stored in a
single location, such as a configuration file or database, and accessed from there.
• Repetitive logic: If the same logic is repeated in multiple functions or methods i.e,
evil switch, if-else blocks etc.
22
Now what if there was another function getBonus like:
In both cases, we can see that the switch condition statements are same.
Now what if there were 20 functions with the same switch conditions? And what if there
were a 100 switch conditions?
Copy-pasting does seem like an easy solution at first glance, but how can you guarantee that
you have correctly copy-pasted all the switch cases? And imagine, you need to add a new
switch case, how can you guarantee you will not miss any of the 20 places you need to add
the new case in?
This kind of switch functions which appear in multiple places in the codebase are known as
evil switch. Evil switches break the DRY principle.
23
5.5 Resolving Evil Switch
Evil switches can easily be resolved to uphold DRY design principle using strategy design
pattern.
6.1 KISS
KISS stands for Keep It Simple, Stupid . Some also says Keep It Stupid Simple
which means it has to be simple enough that even stupids can understand it.
Sometimes we write a code where everything is fine and it is working perfectly, but the
code reviewer might reject the code because it is not simple enough. Sometimes using recent
features and compact operations like nested ternary to code more concisely can also lead
to this. All this is because these codes are difficult to consume, so they are discouraged in
software design fields.
So, the main idea here is, if the code is not simple, then we have to try to make it sim-
ple. Maybe an example can help us to understand it more clearly. Suppose, we have a
2D array of m*n elements, from where we have to find one. In this case, a very trivial
approach is to go with a nested loop which will give us the time complexity of O(m*n).
Another better approach regarding time complexity will be to use a hashmap or dictionary
data structure which will use a key value maaping. Here we will iterate over n hashtable
and each hashtable might have m elements in it. Time complexity is reduced to O(m+n),
definitely a better and smarter approach. But other than time complexity, there is also a
thin difference between these two approaches - the loop one is easy to understand than the
hashmap one. Now comes the KISS awakening. So, what should we do? The trade off
here is to understand the context first and then use the better strategy. If our data is being
kept in a 10000 * 100 array, then definitely we have to use the hashmap because it is very
obvious. But, if we know that we need a array of 50 * 4 elements, then going for hashing is
unnecessary and complex. We have to understand the context and data behaviour carefully
and then go for the better and simpler approach.
Another idea which also implements KISS is to keep our functions very small. Today our
codes have functions of more than 100-120 lines, but this is highly ¿ discouraged. Critically
saying, a function should not have more than 5-10 lines of code. The very basic elements
in our code is using Ifs and Fors. So we can go like If(condition1) -> function1, else ->
function2, just 4 lines of code.
So, the base line of KISS is we have to understand the context first, and then we have
to implement it in a simpler way.
24
6.2 YAGNI
YAGNI stands for You Ain’t Gonna Need It.
We have learnt a good number of design patterns over time, we know about numerous
Creational Design Pattern, Structural Design Pattern, and Behavioral Design Patterns. So,
after learning all these design patterns, coding using them, understanding their benefits, we
will try to use them very often. But here comes YAGNI, we have to ask ourselves, do we
really need to use a particular pattern here or we are just showing it off?
YAGNI ensures that we are only writing codes that we need. Sometimes it is a wise
decision to think what is going to happen in the future, but this is not the case in Software
Development. We will implement only that part which we need right now, we don’t need to
worry about the near future. Suppose, we are working on a project which will be used be
used by a very limited number of people, and there is no need for future extensions of the
project. YAGNI says that in a project like that, we don’t need microservice architecture
or anything for future modification. We just need to implement it simply and deploy it.
So, YAGNI is all about doing what is obviously needed, no preassumptions, no future
thinking, no overcommit. We will write only what we need right now.
7 Introduction to SOLID
/* This section is written by roll 1805058 */
The SOLID Principles are five principles of Object-Oriented Class Design. They are a
set of rules and best practices to follow while designing a class structure. These principles
were first introduced by the famous Computer Scientist Robert J. Martin (a.k.a Uncle
Bob) in his paper in 2000. But the SOLID acronym was introduced later by Michael
Feathers.
25
7.2 What people confuse SOLID with?
7.2.1 Not a Library
A library is a collection of pre-written code that developers can use to perform specific tasks
or functions in their software applications. Libraries can be written in various programming
languages. Such as DLL in C#, jar file in JAVA. SOLID principles do not provide pre-
written code or libraries.
26
7.3 SOLID code is Different than typical code
In any typical code there can be an entry point class A, which calls class B, then B calls class
C, C calls class D1. But when a change is required that B should call class D2, then lots
of code lines need to be changed manually. This type of code is rigid which SOLID doesn’t
accept. SOLID uses interfaces to improve the code structure.
In the code which maintains SOLID principles, C will call Interface D, which implements
D1, D2 and many more if needed. Now without any change in the codes of C, the modi-
fication can be made. It can be noticed that in a SOLID maintained code, we can hardly
understand which class calls which one as they use interfaces. This is a major difference
from any typical application. This is another feature of SOLID.
A Notification Handler Application receives messages, gives each message an ID and stores
them in a file system as text files. When the messages are needed the message ID is used
to retrieve the message from the file system. The application is fully running and it uses
27
classes including the FILEStorage class.
Now if we want to change the application to store messages in a database instead of file
system, we’ll have to modify a lot of code in the callee MSG class without the help of inter-
face. SOLID tells us to use IStorage which is an Interface that implements FILEStorage,
DatabaseStorage, NoSQLStorage and whatever that can be needed in future. For using
interface, now even if we want to change the file system to a database system, MSG class
need not be modified. Also seeing the code it cannot be understood which class calls which
one.
28
7.4 SOLID helps to avoid
• Rigidity: Rigidity refers to a software system’s inability to adapt to changes. A rigid
system is one where making changes to the code requires extensive modifications to
multiple parts of the system. This can be caused by poor design or lack of modularity,
making it difficult to maintain and extend the system. A rigid system is often a sign of
violating the Open/Closed Principle, where the system is not open for extension and
closed for modification.
• Fragility: A fragile system is one where small changes can cause unexpected problems
in other parts of the system. This can be caused by tight coupling between different
components of the system or a lack of testing. A fragile system can make it difficult
to add new features or fix bugs without introducing new problems. A fragile system
is often a sign of violating the Single Responsibility Principle, where a class has too
many responsibilities and is tightly coupled to other classes.
• Immobility: Immobility refers to a software system’s inability to reuse components
in other parts of the system or in other projects. An immobile system is one where
it’s difficult to extract and reuse components without bringing along unnecessary de-
pendencies or complex configurations. This can be caused by a lack of modularity or
a lack of abstraction. An immobile system can make it difficult to share code or use
existing libraries, leading to duplication of code and increased development time.
• Viscosity: Viscosity refers to a software system’s tendency to favor a ”bad” solution
over a ”good” one. A viscous system is one where it’s easier to take a shortcut or
apply a quick fix rather than following best practices or refactoring the code. This
can be caused by a lack of understanding of the system’s design or by pressure to
deliver quickly. A viscous system can make it difficult to maintain the code and lead
to technical debt over time.
• Needless Complexity Needless complexity refers to complexity that doesn’t provide
any benefit to the system or its users. This can be caused by over-engineering, lack
of simplicity or abstraction, or making the system do more than what is required.
Needless complexity can make the system harder to understand, maintain or use, and
can negatively impact development time, quality, and productivity.
29
changing its existing code. This reduces the risk of introducing new bugs and makes
the code more reusable.
By following these principles, developers can create software systems that are more main-
tainable, flexible, and extensible. This can help to reduce the cost and time required for
software development and improve the quality of the resulting software system. The SOLID
principles are not rules that must be followed strictly, but rather guidelines that help devel-
opers make informed decisions about their software design.
More about these principles will be explained in detail in the next lecture.
THE END
30