A1 Group1 Notes

Download as pdf or txt
Download as pdf or txt
You are on page 1of 46

CSE - 325

Information System Design

Notes: Lecture – 1

By

1805001 – Md. Mehrab Haque


1805003 – Rabib Jahin Ibn Momin
1805009 – MD. Zarzees Uddin Shah
1805015 – Ahmed Mahir Sultan Rumi
1805021 – Abdus Samee
1805024 – Zannatul Naim
Why do we need OOP instead of structural
language?

By 1805001 – Md. Mehrab Haque


[0:00 – 26:00]

Object-oriented programming (OOP) and structural programming


are two different programming paradigms that have their own
strengths and weaknesses. Here are some reasons why OOP may
be preferred over structural programming:

1. Modularity: OOP allows you to break down complex


problems into smaller, more manageable pieces called
objects. These objects can be reused across different parts of
the program, making it easier to maintain and modify the
code. In contrast, structural programming tends to create
large, monolithic programs that are difficult to modify.

2. Encapsulation: OOP allows you to hide the complexity of an


object's internal workings from other parts of the program,
providing a clear separation between the interface and
implementation. This makes it easier to maintain the code,
as changes to the internal workings of an object do not affect
other parts of the program.

3. Inheritance: OOP allows you to create new classes based on


existing ones, inheriting their properties and methods. This
makes it easier to reuse code and create new classes that are
closely related to existing ones.
4. Polymorphism: OOP allows you to write code that can work
with objects of different types, as long as they implement the
same interface. This makes it easier to write generic code
that can work with a wide range of objects.

5. Abstraction: OOP allows you to focus on the essential


features of an object and ignore its implementation details.
This makes it easier to write code that is easy to understand
and maintain.

Overall, OOP can provide a more modular, maintainable, and


reusable codebase than structural programming, especially for
complex applications. However, the choice of programming
paradigm depends on the specific requirements of the project and
the preferences of the developer.

What are the challenges of OOP compared to structured


language ?

Object-oriented programming (OOP) and structured


programming are two distinct paradigms of programming, and
each has its own set of challenges. Here are some of the challenges
of OOP compared to structured programming:

1. Complexity: Object-oriented programs can be more complex


than structured programs because they involve creating and
managing objects, classes, and inheritance hierarchies.

2. Design: Object-oriented programming requires careful


design and planning. Developers need to create well-defined
classes and hierarchies to ensure that the program is
modular, maintainable, and extensible.

3. Abstraction: Object-oriented programming relies heavily on


abstraction, which can be difficult for some developers to
understand and implement effectively. It requires
understanding how to create and use abstract data types,
which can be challenging.

4. Performance: Object-oriented programming can be less


efficient than structured programming because of the
overhead associated with creating and managing objects.
However, modern OOP languages like Java and C++ have
made significant improvements in performance.

5. Learning curve: Object-oriented programming can have a


steeper learning curve than structured programming.
Developers need to understand concepts like polymorphism,
encapsulation, and inheritance, which can take time to
master.

6. Tooling: Object-oriented programming often requires


specialized tools, such as integrated development
environments (IDEs), to be used effectively. These tools can
be more complex and expensive than those used for
structured programming.
OOP Features:

By 1805021 - Abdus Samee


[26:00 - 48:00]

Object Oriented Programming(OOP) is a programming paradigm


that uses objects to represent real-world entities and encapsulates
data and behaviour within these objects. Three of its prominent
features are as follows:

Encapsulation: Encapsulation is one of the key features of object-


oriented programming (OOP) that involves hiding the internal
details of an object and exposing only the necessary interface or
public methods to the outside world. Encapsulation provides
several benefits, including data protection, improved code
maintainability, and enhanced security.
The idea behind encapsulation is to group related data and
functionality into a single unit, called a class. A class is a template
or blueprint for creating objects that define the properties and
methods that the objects will have. Encapsulation allows us to
define these properties and methods as private, which means they
can only be accessed by other methods within the same class. By
doing so, we can protect the data and ensure that it is not
modified in unexpected ways.
Encapsulation also allows us to define public methods, which
are the methods that are exposed to the outside world. These
public methods provide a controlled interface for interacting with
the object and manipulating its data. The data is hidden from the
outside world and can only be accessed or modified through these
public methods. This provides a level of abstraction and simplifies
the code, making it easier to understand and maintain.
Another benefit of encapsulation is that it allows us to
change the internal implementation details of the class without
affecting the outside world. If we make changes to the private
methods or properties of a class, we can be sure that the public
methods that access these properties will continue to work as
expected. This means that we can change the internal
implementation details of a class without breaking the code that
uses it.

We see that the private variable x inside the Parent class cannot
be accessed from the Test class, but the public method getX() can
be accessed. This way the Parent class encapsulated the variable
x.

● Inheritance: Inheritance is a key concept in object-oriented


programming that allows us to create new classes based on
existing classes. The new class, known as the subclass or
derived class, inherits properties and methods from the
existing class, known as the superclass or base class.
Inheritance allows us to reuse code and create a hierarchy of
related classes, which can simplify the development process
and improve code readability.
The subclass inherits all the properties and methods of
the superclass and can also add its own properties and
methods. This means that the subclass can extend and
modify the behavior of the superclass to suit its specific
needs. Inheritance provides a powerful mechanism for
creating new classes that share common attributes and
behavior.
Inheritance can also help to improve code readability by
creating a hierarchy of related classes. However, inheritance
can also lead to code duplication and tight coupling between
classes. If we have a large hierarchy of classes with many
levels of inheritance, it can become difficult to maintain and
debug the code. In some cases, it may be more appropriate to
use other techniques such as composition or interfaces
instead of inheritance.

In the above code, the child class inherited the parent class
and can access the public method getX() inside of its method
printValues(). It also added its own property y which it used
on its own.

● Polymorphism: Polymorphism is the ability of an object to


take on many forms. It allows objects of different classes to
be treated as if they were of the same class. This can be
achieved through methods overloading and method
overriding.
Compile-time polymorphism, also known as method
overloading, occurs when we have multiple methods with the
same name but different parameters in a class. The compiler
determines which method to call based on the number and
types of arguments passed to the method.
Run-time polymorphism, also known as method
overriding, occurs when a subclass provides its own
implementation of a method that is already defined in the
superclass. When we call the method on an object of the
subclass, the overridden method in the subclass is called
instead of the method in the superclass.
Polymorphism can also be achieved through interfaces,
which define a set of methods that a class must implement.
Any class that implements an interface can be treated as if it
were an object of that interface. This allows us to write code
that can work with different types of objects that implement
the same interface.
In the left code snippet, we can see two methods with the name
print but with different arguments. This is called method
overloading. In the right code snippet, we see a Child class
inheriting those methods from the Parent class but having
different implementation. This is called method overriding. Both
are a form of polymorphism.
According to expert opinion, the importance of encapsulation is
70%, inheritance is 10%, and polymorphism is 20%. It is also
suggested that enterprise applications can even be built without
the use of inheritance. This is achieved by the use of composition.
Inheritance and Composition in OOP

By 1805015 – Ahmed Mahir Sultan Rumi


[48:00 – 1:13:00]

Inheritance in OOP:
Inheritance is a fundamental concept in Object-Oriented
Programming (OOP) that allows a new class to be based on an
existing class. Inheritance enables a new class, called the subclass,
to inherit the properties and behaviors of an existing class, called
the superclass.

The subclass can then add its own unique properties and
behaviors, or it can override the superclass's properties and
behaviors to customize its functionality. Inheritance is useful
because it allows you to reuse code and avoid duplicating similar
code in multiple classes. However, inheritance is not the main
reason we use OOP. We know the main features of OOP namely,
Encapsulation, Inheritance and Polymorphism. If we want to
show the importance of these three features through percentage,
this is the result according to experts.

Encapsulation Inheritance Polymorphism


70% 10% 20%

Even if we remove inheritance from OOP, we will be fine. In face


it is advised by the experts to choose composition over
inheritance because inheritance has some inherent problems
that cannot be fixed easily.

Problems with inheritance:


Inheritance can be a powerful tool in object-oriented
programming, but it also has some potential problems that
developers need to be aware of. Here are some common problems
with inheritance in OOP:
 Inflexibility: Inheritance can make classes inflexible, as
subclasses are restricted to using the properties and methods
of the superclass. This can make it difficult to add new
functionality or modify existing behavior without breaking
existing code.

 Tight Coupling: Inheritance creates a tight coupling between


the superclass and the subclass, which means that any
changes to the superclass may have an impact on the
behavior of the subclass. This can make it difficult to modify
or extend the codebase, as changes in one part of the code
may have unintended consequences elsewhere.

 Hierarchy Explosion: Inheritance can lead to a hierarchy


explosion, where the number of subclasses and levels of
inheritance becomes too large to manage effectively. This can
make the codebase difficult to understand and maintain.

 Fragility: Inheritance can be fragile, as changes to the


superclass can have unintended consequences on the
behavior of the subclass. This can make it difficult to
maintain and modify code, as developers need to be aware of
the potential impact of their changes on other parts of the
codebase.
 Code Duplication: Inheritance can also lead to code
duplication, as developers may be tempted to create new
subclasses that are only slightly different from existing ones,
rather than refactoring the code to remove duplication.

Multiple Inheritance:
Multiple inheritance is a feature of object-oriented programming
that allows a class to inherit from more than one superclass.
While it can be a powerful tool for creating complex class
hierarchies and reducing code duplication, it also has some
potential problems. One of the most famous problems that arises
because of multiple inheritance is the diamond problem.

Diamond Problem:
The "diamond problem" is a common issue in inheritance that can
occur when a class inherits from two or more classes that share a
common superclass. It gets its name from the diamond shape that
forms when you draw a class diagram to represent the inheritance
hierarchy.
The diamond problem arises because the subclass inherits
multiple copies of the same superclass from its two or more
parent classes. This can lead to conflicts if the superclass has
methods or properties that are overridden in both parent classes.
The subclass may be unsure which implementation to use, or it
may use the wrong implementation altogether.
For example, let's say we have a class hierarchy where class A and
class B both inherit from SuperClass (in the above figure), and
class C inherits from both class A and class B: If both class A and
class B override a method in SuperClass, and class C tries to use
that method, it is unclear which implementation of the method
should be used.
To avoid the diamond problem, some programming languages
provide mechanisms for resolving conflicts, such as "virtual
inheritance" or "interface inheritance". Virtual inheritance
ensures that only one copy of the common superclass is inherited,
while interface inheritance defines a common interface that the
parent classes must implement, rather than inheriting a common
implementation.

Using composition instead of inheritance:

We can replace inheritance with composition in all cases.


However, even if we switch “completely” to composition, we will
still have some inheritance. This inheritance will be from
interfaces and interface-like classes only, though. It barely counts
as inheritance, really, since all it’s doing is restricting itself to an
API and not inheriting implementation details.
Example:
If we use composition then we can write ComplexFoo class like
this:

Thus, we just used BasicFoo object inside ComplexFoo.


Interface and abstract class:
Concept of interface and abstract class is very important in OOP.
Although C++ allows multiple inheritance but discourages it, Java
doesn’t allow multiple inheritance at all. In Java, we can extend a
single class but may implement multiple interfaces.
Interface:
In Java, an interface is a blueprint for a set of related methods
that a class can implement. An interface defines a set of methods
that a class that implements it must provide, but it doesn't provide
any implementation for those methods. An interface is declared
using the interface keyword. It may also contain constants.
Example:

Abstract class:
An abstract class, on the other hand, is a class that can't be
instantiated directly, but can be subclassed. An abstract class can
contain abstract methods (which are like the methods in an
interface - they don't provide any implementation) as well as
concrete methods (which provide implementation). An abstract
class is declared using the abstract keyword.
Example:
There are some basic understanding we must have in this topic.
1 . Why can’t we create instances of interfaces?
Ans. Interfaces do not have implementation of the functions that
are declared. Therefore, of course making instances does not
make any sense because then we cannot call a method through the
instance because it does not have any implementation.

2 . Why functions in interface do not have


implementations?
Ans. The functions in an interface in Java do not have
implementation because the purpose of an interface is to define a
contract or a set of rules that classes must follow, but not to
provide implementation details.
An interface defines a set of methods that a class that implements
it must provide, but it doesn't provide any implementation for
those methods. The implementation of these methods is left to the
classes that implement the interface. This allows the classes to
implement the methods in a way that best suits their specific
requirements and behavior.
By not providing the implementation of methods in an interface,
Java ensures that the interface remains a pure contract that
describes what a class implementing the interface should do, but
not how it should do it. This helps to achieve abstraction and
provides a way to separate the interface from the implementation,
making it easier to change or update the implementation in the
future without affecting the interface.

3. Can all the methods of an abstract class be abstract?


Ans. Yes, all the methods of an abstract class in Java can be
abstract. An abstract class is a class that cannot be instantiated
and may contain both abstract and non-abstract (concrete)
methods. However, it is not required to have both types of
methods in an abstract class. In that case, we may just use
interface instead.
Eliminating Knowledge Duplication: The
Importance of the DRY Principle in Software
Development

By 1805003 – Rabib Jahin Ibn Momin


[1:13:01 – 1:27:32]

One of the most common experiences when writing code is the


feeling of déjà vu, where you come across similar code that you've
written before. As an application grows in scale, it becomes
increasingly challenging to avoid repeating previously
implemented business logic. However, the DRY principle can help
address this issue by promoting code reuse and reducing
duplication in large codebases.

What is DRY:

Andy Hunt and Dave Thomas formally defined aprinciple in 1999


in their book The Pragmatic Programmer. That is:

Every piece of knowledge must have a single,


unambiguous, authoritative representation within a
system.

DRY is opposite to WET principle which means ‘Write Everything


Twice’ or ‘We Love Typing’. This means that code is often
duplicated or overly verbose, making it more difficult to maintain,
modify, and scale.On the other hand,DRY is one of the few ideas
that apply through all phases and levels of software development
and even in other domains of knowledge work. The core problem
it aims to solve is that of knowledge duplication.

Formal Definition:

DRY stands for "Don't Repeat Yourself", and it is a principle in


software development that aims to eliminate duplication and
promote code reuse. The principle states that every piece of
knowledge or functionality in a software system should have a
single, unambiguous representation within that system. This
means that code should be modular, maintainable, and reusable.
The DRY principle is closely related to the concept of code smell,
which refers to any characteristic in the code that may indicate a
deeper problem. Code smells are often caused by duplicating code
or logic in multiple places, which can lead to inconsistencies,
bugs, and difficult-to-maintain code.

Common Mistakes That Lead to Duplication:

There are some tendencies among the programmers/developers


that lead them to duplication of the code which violates the DRY
principle.These are:

● Copy-Pasting: Ctrl-C + Ctrl-V are the most used actions by


the developers which is dangerous sometimes.Because ,
copying and pasting code from one location to another can
easily lead to duplication, especially when the code is not
properly abstracted or modularized.When you copy/paste a
code higher than 2 times, you must know that you are doing
something wrong and eventually it will make the codebase
hard to debug.

● Overcomplicating: Sometimes, developers may try to


solve a problem in a complex way, which can lead to
duplication. For example, writing multiple functions that
perform the same task but in slightly different ways.

● Lack of Abstraction: If the code is not properly


abstracted, developers may end up duplicating code. This
can occur when functionality is not separated into reusable
functions or when data is not properly encapsulated.

● Tight Coupling: When modules or components are tightly


coupled, changes in one module can easily lead to
duplication in other modules.

● Failure to Reuse Code: Failure to reuse existing code can


lead to duplication, as developers may end up writing new
code that already exists in the system

● Using Switch/Cases Frequently: The concept is known


as

“Evil Switch”. "Evil switch" is a term used to describe a


programming anti-pattern that involves the overuse of
switch statements.The problem with it is that it can lead to
code that is difficult to read, understand, and maintain. As
the number of cases in the switch statement increases, the
code can become more complex and harder to test.
Understanding With an Example:

Suppose, we are developing an Employee Management System


where we have to calculate bonus, number of holidays, range of
salaries using the type of employee. In the organization, there are
three types of employees: Software Engineer(SE), Senior Software
Engineer (SSE), and Quality Assurance Engineer (QE).
So, to calculate the Bonus depending on the type, we have a code
segment like this:
Now, suppose we have to write a function getHolidayCount for
different employee types.Genral tendency will be to copy the
above code and change the business logic inside.
So, where is the problem? Now assume that I have copied the
same code 5-6 places in the codebase.If a new type of employee is
added in the organization, every switch - case portion has to be
edited with a new case in it which is very error-prone. Moreover,
it is not ideal to search for the same code fragment to edit which
maskes the code non-extensive.So, the code above is clearly
against the DRY principle.
Solution :

Strategy Pattern can be used to solve the problem of code


duplication and make the code extensive.
For the scenario of bonus calclation the strategy pattern can be
used to encapsulate each type of employee's bonus calculation
logic in its own class, eliminating the need for a large switch or if-
else statement that would need to be updated every time a new
employee type or bonus calculation rule is added. Each
BonusCalculator class is responsible for a specific type of
employee and implements the calculateBonus method based on
the unique bonus calculation rules for that employee type. This
means that the bonus calculation logic is not repeated in multiple
places throughout the codebase, and any changes to the bonus
calculation rules can be made in a single location, improving code
maintainability. Additionally, by using constructor injection to
pass in the bonus percentage value to each BonusCalculator
object, the bonus calculation logic is further abstracted and can be
easily modified without changing the implementation of each
BonusCalculator class.

Problems of Code Duplication:

Code duplication leads to major problem such as:


Increased Maintenance: When code is duplicated across
multiple places in your codebase, it becomes difficult to maintain
because any changes need to be made in multiple places. This can
lead to errors and inconsistencies.
Wasted Time and Effort: Duplication requires developers to
write and test the same code multiple times, which is a waste of
time and effort.
Higher Risk of Bugs: When code is duplicated, bugs can easily
be introduced if the changes are not made consistently across all
copies of the code. This can lead to hard-to-find bugs and
increased debugging time.
Increased Complexity: Duplicated code adds to the complexity
of your codebase, making it harder to understand and maintain
over time.
Reduced Readability: Code duplication can make code harder
to read, as developers must navigate through multiple copies of
the same code to understand how it works. This can lead to
confusion and mistakes.

How to Maintain DRY Principle:

Modularize Your Code: Break your code into reusable


modules or functions that can be called from multiple places in
your codebase. This way, you can avoid duplicating code by calling
the same function from multiple places instead of copying and
pasting the same code.
Use Inheritance and Polymorphism: In object-oriented
programming, you can use inheritance and polymorphism to
avoid duplicating code. By creating a parent class with common
functionality and inheriting from it in child classes, you can avoid
duplicating code.

Use Functions and Libraries: Instead of writing the same


code over and over, consider using functions or libraries that
perform a specific task. This can save you time and effort and
reduce the risk of bugs.
Use Version Control: Use version control software to track
changes to your codebase. This can help you identify areas where
code has been duplicated and make it easier to refactor as needed.

In conclusion, the DRY principle is an important concept in


software development that helps reduce complexity, increase
maintainability, and improve code quality. By avoiding code
duplication and reusing code wherever possible, developers can
create more efficient, scalable, and reliable applications. By
following best practices and using modular, reusable code,
developers can ensure that their applications are more robust,
easier to maintain, and less prone to errors. Overall, the DRY
principle is a key component of modern software development,
and it is essential for building high-quality, reliable applications
Kiss and Yagni:

By 1805024 – Zannatul Naim


[1:27:33 – 2:00:00]

KISS and YAGNI are two software development principles that have become
increasingly popular in recent years. KISS, which stands for Keep It Simple,
Stupid, is a principle that encourages developers to write simple, straightforward
code that is easy to understand and maintain. The idea is that by keeping things
simple, developers can avoid introducing unnecessary complexity into their code,
which can lead to bugs and other issues down the line. YAGNI, which stands for
You Ain't Gonna Need It, is a principle that encourages developers to avoid
writing code that isn't necessary at the moment. The idea is that by only writing
code that is needed, developers can avoid wasting time and resources on features
that may never be used. Both KISS and YAGNI principles have become popular
because they offer a simple and effective way for developers to write better code.
By focusing on simplicity and efficiency, developers can create easier software to
understand, maintain, and use. Additionally, both principles are designed to help
developers avoid common pitfalls that can lead to bugs, code bloat, and other
issues that can impact the quality and functionality of the software.

KISS Principle:

The KISS principle encourages developers to keep their code simple and easy to
understand. This means avoiding unnecessary complexity, using clear and
concise language, and breaking code down into small, manageable pieces. By
following the KISS principle, developers can make it easier for others to
understand their code and make changes as needed. Here are some examples of
how to apply the KISS principle in software development:

● Avoiding overly complex code: One of the most common ways to apply
the KISS principle is by avoiding overly complex code. This means avoiding
long and convoluted functions, using simple and clear variable names, and
avoiding complex conditional statements whenever possible.
● Breaking code down into smaller pieces: Another way to apply the
KISS principle is by breaking code down into smaller, more manageable
pieces. This means using functions and modules to separate code into
logical units, and avoiding writing long and monolithic files.

● Using clear and concise language: Finally, developers can apply the
KISS principle by using clear and concise language in their code. This
means avoiding technical jargon and using plain English wherever
possible, making it easier for others to understand what the code is doing.

For example, imagine a developer working on a web application that allows users
to upload and share photos. By following the KISS principle, the developer might
create a simple function for handling file uploads, using clear and concise
variable names, and breaking the code down into smaller, more manageable
pieces. This would make it easier for others to understand and maintain the code,
and avoid introducing unnecessary complexity to the application.

Additionally, one common misconception about the KISS principle is that it


encourages developers to sacrifice functionality in the name of simplicity.
However, this is not necessarily the case. By focusing on simplicity and efficiency,
developers can often create more functional and reliable software in the long run.

YAGNI Principle:

The YAGNI principle encourages developers to only write code that is needed,
and avoid writing code that isn't necessary at the moment. This means focusing
on creating software that meets the immediate needs of users and organizations,
rather than trying to anticipate every possible future need. By following the
YAGNI principle, developers can avoid wasting time and resources on features
that may never be used. Here are some examples of how to apply the YAGNI
principle in software development:

● Avoiding feature creep: One of the most common ways to apply the
YAGNI principle is by avoiding feature creep. Feature creep refers to the
tendency for software developers to continually add new features to a
project, even if those features are not essential to the core functionality of
the software.

● Prioritizing development efforts: Another way to apply the YAGNI


principle is by prioritizing development efforts based on immediate needs.
This means focusing on features and functionality that are essential to the
core functionality of the software, and deferring or delaying work on
features that are less critical or that may not be needed at all.

● Avoiding premature optimization: Finally, developers can apply the


YAGNI principle by avoiding premature optimization. This means avoiding
spending excessive time and resources on optimizing code before it is clear
that the code is actually causing performance problems.

For example, imagine a developer working on a project management tool. By


following the YAGNI principle, the developer might prioritize features such as
task tracking and assignment, time tracking, and team collaboration, rather than
spending time on advanced reporting or analytics features that may not be
needed at the moment. Additionally, the developer might avoid spending time on
optimizing code until it is clear that performance issues are actually impacting
users.

It's important to note that the YAGNI principle does not mean that developers
should ignore the long-term needs of users and organizations. Instead, it means
focusing on creating software that meets immediate needs, while remaining
flexible enough to adapt to changing needs and requirements over time.

Overall, the YAGNI principle is an important tool for creating software that is
efficient, effective, and meets the needs of users and organizations. By avoiding
unnecessary features and prioritizing development efforts based on immediate
needs, developers can create software that is more useful, reliable, and easier to
maintain over time.
KISS and YAGNI in practice:

The KISS and YAGNI principles can be used together in practice to create
software that is simple, efficient, and meets the immediate needs of users and
organizations. Here are some examples of how these principles can be applied in
practice:

● Simple user interface: One of the most common ways to apply the
KISS and YAGNI principles in practice is by creating a simple user
interface that is easy to understand and use. By focusing on the most
important features and avoiding unnecessary complexity, developers can
create software that is more user-friendly and efficient.
For example, imagine a developer creating a social media app. By
following the KISS principle, the developer might focus on creating a
simple user interface that emphasizes core functionality such as posting
and sharing content, rather than adding unnecessary features such as
complex privacy settings. By following the YAGNI principle, the developer
might also avoid spending time on features that are not essential to the
core functionality of the app, such as advanced analytics or machine
learning algorithms.

● Modular architecture: Another way to apply the KISS and YAGNI


principles in practice is by using a modular architecture that is easy to
understand and maintain. By breaking software down into smaller, more
manageable components, developers can create software that is more
reliable and easier to update and maintain over time.
For example, imagine a developer working on an e-commerce
website. By following the KISS principle, the developer might focus on
creating a modular architecture that separates different components of the
website, such as the shopping cart, payment processing, and order
tracking. By following the YAGNI principle, the developer might also
avoid spending time on features that are not essential to the core
functionality of the website, such as social media integration or advanced
search algorithms.
● Continuous integration and deployment: Finally, developers can
apply the KISS and YAGNI principles in practice by using continuous
integration and deployment tools that automate the software development
and deployment process. By automating tasks such as testing, building,
and deployment, developers can create software that is more reliable and
easier to maintain over time.
For example, imagine a team of developers working on a mobile app.
By following the KISS and YAGNI principles, the team might focus on
creating a simple and modular architecture that is easy to update and
maintain. They might also use continuous integration and deployment
tools such as Jenkins or Travis CI to automate tasks such as building and
testing the app so that they can focus on creating new features and
improving the core functionality of the app.

Overall, the KISS and YAGNI principles can be used together in practice to
create software that is simple, efficient, and meets the immediate needs of users
and organizations. By focusing on creating simple and modular software that
emphasizes core functionality, and by avoiding unnecessary complexity and
features, developers can create software that is more reliable, easier to maintain,
and better suited to the needs of users and organizations.

Challenges in implementing KISS and YAGNI:

While the KISS and YAGNI principles can be very effective in creating simple
and efficient software, there are some challenges in implementing these
principles in practice. Here are some of the challenges that developers may face
when trying to implement KISS and YAGNI:

● Balancing simplicity with functionality: One of the biggest


challenges in implementing KISS and YAGNI is finding the right balance
between simplicity and functionality. While it's important to keep the
software simple and easy to use, it's also important to ensure that the
software provides the necessary functionality to meet the needs of users
and organizations. Finding this balance can be difficult, and may require
careful planning and prioritization.
● Dealing with changing requirements: Another challenge in
implementing KISS and YAGNI is dealing with changing requirements
over time. As user needs and organizational requirements evolve,
developers may need to add new features and functionality to software that
was designed to be simple and focused on core functionality. This can be
challenging and may require significant changes to the software
architecture and design.

● Balancing short-term vs long-term goals: Another challenge in


implementing KISS and YAGNI is balancing short-term vs long-term
goals. While KISS and YAGNI principles can help create software that
meets the immediate needs of users and organizations, they may not
always be suitable for long-term goals such as scalability and extensibility.
Developers may need to find a balance between short-term and long-term
goals, and prioritize software design accordingly.

● Overcoming resistance to change: Finally, a challenge in


implementing KISS and YAGNI is overcoming resistance to change.
Some developers, users, and organizations may be resistant to adopting
new design and development practices, particularly if they are used to
more complex and feature-rich software. Overcoming this resistance may
require effective communication, as well as a willingness to experiment and
iterate on software design and development practices.

In summary, while the KISS and YAGNI principles can be very effective in
creating simple and efficient software, they can also be challenging to implement
in practice. Developers may need to find a balance between simplicity and
functionality, deal with changing requirements over time, balance short-term vs
long-term goals, and overcome resistance to change. However, with careful
planning, and communication, it is possible to overcome these challenges and
create software that is simple, efficient and meets the needs of users and
organizations.
Conclusion:

In conclusion, the KISS and YAGNI principles are two important design and
development practices that can help create software that is simple, efficient, and
focused on core functionality. By focusing on simplicity and avoiding unnecessary
complexity, developers can create software that is easier to use, maintain, and
scale.

However, while the KISS and YAGNI principles can be very effective in theory,
there are also challenges in implementing them in practice. Developers may need
to find a balance between simplicity and functionality, deal with changing
requirements, balance short-term vs long-term goals, and overcome resistance to
change.

Despite these challenges, the benefits of adopting the KISS and YAGNI
principles can be significant. By creating software that is simple, efficient, and
focused on core functionality, developers can reduce development time, improve
user experience, and increase the overall quality of software.

Therefore, it is important for developers to understand and apply these principles


in their software design and development practices. By doing so, they can create
software that is not only effective, but also efficient, easy to use, and focused on
meeting the needs of users and organizations.
Solid Principles:

By 1805009 – MD. Zarzees Uddin Shah


[2:00:00 – 2:30:25]

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 five principles help us understand the need for certain design patterns and
software architecture in general.

Let's look at each principle one by one. Following the SOLID acronym, they are:

 The Single Responsibility Principle


 The Open-Closed Principle
 The Liskov Substitution Principle
 The Interface Segregation Principle
 The Dependency Inversion Principle

The Single Responsibility Principle

The Single Responsibility Principle states that a class should do one thing and
therefore it should have only a single reason to change.

To state this principle more technically: Only one potential change (database logic,
logging logic, and so on) in the software’s specification should be able to affect the
specification of the class.

This means that if a class is a data container, like a Book class or a Student class, and it
has some fields regarding that entity, it should change only when we change the data
model.

Following the Single Responsibility Principle is important. First of all, because many
different teams can work on the same project and edit the same class for different
reasons, this could lead to incompatible modules.
Second, it makes version control easier. For example, say we have a persistence class
that handles database operations, and we see a change in that file in the GitHub
commits. By following the SRP, we will know that it is related to storage or database-
related stuff.

Merge conflicts are another example. They appear when different teams change the
same file. But if the SRP is followed, fewer conflicts will appear – files will have a single
reason to change, and conflicts that do exist will be easier to resolve.

Common Pitfalls and Anti-patterns

Here are some common mistakes that violate the Single Responsibility Principle. We
will look at the code for a simple bookstore invoice program as an example. Let's start by
defining a book class to use in our invoice.

This is a simple book class with some fields.

Now let's create the invoice class which will contain the logic for creating the invoice and
calculating the total price. For now, we assume that our bookstore only sells books and
nothing else.
Here is our invoice class. It also contains some fields about invoicing and 3 methods:
 calculateTotal method, which calculates the total price,
 printInvoice method, that should print the invoice to console, and
 saveToFile method, responsible for writing the invoice to a file.

Our class violates the Single Responsibility Principle in multiple ways.

The first violation is the printInvoice method, which contains our printing logic. The
SRP states that our class should only have a single reason to change, and that reason
should be a change in the invoice calculation for our class.
But in this architecture, if we wanted to change the printing format, we would need to
change the class. This is why we should not have printing logic mixed with business
logic in the same class.
There is another method that violates the SRP in our class: the saveToFile method. It
is also an extremely common mistake to mix persistence logic with business logic.
We can create new classes for our printing and persistence logic so we will no longer
need to modify the invoice class for those purposes.

We create 2 classes, InvoicePrinter and InvoicePersistence, and move the


methods.

Now our class structure obeys the Single Responsibility Principle and every class is
responsible for one aspect of our application.
Open-Closed Principle
The Open-Closed Principle requires that classes should be open for extension and
closed to modification. Modification means changing the code of an existing class,
and extension means adding new functionality.

So what this principle wants to say is: We should be able to add new functionality
without touching the existing code for the class. This is because whenever we modify the
existing code, we are taking the risk of creating potential bugs. So we should avoid
touching the tested and reliable (mostly) production code if possible.

But how are we going to add new functionality without touching the class? It is usually
done with the help of interfaces and abstract classes. Let's say we want invoices to be
saved to a database so that we can search them easily.

We create the database, connect to it, and we add a save method to


our InvoicePersistence class:

Unfortunately we did not design the classes to be easily extendable in the future. So in
order to add this feature, we have modified the InvoicePersistence class.
If our class design obeyed the Open-Closed principle we would not need to change this
class.

So, we see the design problem and decide to refactor the code to obey the principle.
We change the type of InvoicePersistence to Interface and add a save method. Each
persistence class will implement this save method.

So our class structure now looks like this:


Now our persistence logic is easily extendable. If we are asked to add another database
and have 2 different types of databases like MySQL and MongoDB, we can easily do
that.

Liskov Substitution Principle

The Liskov Substitution Principle states that subclasses should be substitutable for their base
classes.

This means that, given that class B is a subclass of class A, we should be able to pass an object of
class B to any method that expects an object of class A and the method should not give any weird
output in that case. This is the expected behavior, because when we use inheritance we assume
that the child class inherits everything that the superclass has. The child class extends the
behavior but never narrows it down. Therefore, when a class does not obey this principle, it
leads to some nasty bugs that are hard to detect.

Liskov's principle is easy to understand but hard to detect in code. So let's look at an example.

We have a simple Rectangle class, and a getArea function which returns the area of the
rectangle.
Now we decide to create another class for Squares.
Our Square class extends the Rectangle class. We set height and width to the same value
in the constructor, but we do not want any client (someone who uses our class in their
code) to change height or weight in a way that can violate the square property.

Therefore we override the setters to set both properties whenever one of them is
changed. But by doing that we have just violated the Liskov substitution principle.

Let's create a main class to perform tests on the getArea function.


A tester can came up with the testing function getAreaTest and can tell that
our getArea function fails to pass the test for square objects.
In the first test, we create a rectangle where the width is 2 and the height is 3 and
call getAreaTest. The output is 20 as expected, but things go wrong when we pass in
the square. This is because the call to setHeight function in the test is setting the width
as well and results in an unexpected output.
Interface Segregation Principle
Segregation means keeping things separated, and the Interface Segregation Principle is
about separating the interfaces. The principle states that many client-specific interfaces
are better than one general-purpose interface. Clients should not be forced to implement
a function they do no need.

This is a simple principle to understand and apply, so let's see an example.


We modeled a very simplified parking lot. Now if we want to implement a parking lot
that is free,

Our parking lot interface was composed of 2 things: Parking related logic (park car,
unpark car, get capacity) and payment related logic.

But it is too specific. Because of that, our FreeParking class was forced to implement
payment-related methods that are irrelevant. Let's separate or segregate the interfaces.
We've now separated the parking lot. With this new model, we can even go further and
split the PaidParkingLot to support different types of payment.
Now our model is much more flexible, extendable, and the clients do not need to
implement any irrelevant logic because we provide only parking-related functionality in
the parking lot interface.

Dependency Inversion Principle


The Dependency Inversion principle states that our classes should depend upon
interfaces or abstract classes instead of concrete classes and functions. If the OCP states
the goal of OO architecture, the DIP states the primary mechanism. These two principles
are indeed related and we have applied this pattern before while we were discussing the
Open-Closed Principle.

Conclusion

It is not a surprise that all the concepts of clean coding, object-oriented architecture,
design patterns and SOLID principles are somehow connected and complementary to
each other

They all serve the same purpose:

"To create understandable, readable, and testable code that many developers can
collaboratively work on."

You might also like