A1 Group1 Notes
A1 Group1 Notes
A1 Group1 Notes
Notes: Lecture – 1
By
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.
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.
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.
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.
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.
What is DRY:
Formal Definition:
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.
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.
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.
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.
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:
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.
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 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.
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.
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.
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.
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.
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.
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.
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.
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
"To create understandable, readable, and testable code that many developers can
collaboratively work on."