LO 2 Apply Java Advanced Class Design and Object Oriented Design Principles
LO 2 Apply Java Advanced Class Design and Object Oriented Design Principles
Principles
Abstraction
As per dictionary, abstraction is the quality of dealing with ideas rather than
events. For example, when you consider the case of e-mail, complex details
such as what happens as soon as you send an e-mail, the protocol your e-mail
server uses are hidden from the user. Therefore, to send an e-mail you just need
to type the content, mention the address of the receiver, and click send.
Abstract Class
• Abstract classes may or may not contain abstract methods, i.e., methods
without body ( public void get(); )
• But, if a class has at least one abstract method, then the class must be
declared abstract.
• If a class is declared abstract, it cannot be instantiated.
• To use an abstract class, you have to inherit it from another class, provide
implementations to the abstract methods in it.
• If you inherit an abstract class, you have to provide implementations to all
the abstract methods in it.
EXAMPLE
Now you can try to instantiate the Employee class in the following way −
/* File name : AbstractDemo.java */
public class AbstractDemo {
public static void main(String [] args) {
/* Following is not allowed and would raise error */
Employee e = new Employee("George W.", "Houston, TX", 43);
System.out.println("\n Call mailCheck using Employee
reference--");
e.mailCheck();
}
}
When you compile the above class, it gives you the following error −
Employee.java:46: Employee is abstract; cannot be instantiated
Employee e = new Employee("George W.", "Houston, TX", 43);
^
1 error
We can inherit the properties of Employee class just like concrete class in the
following way −
EXAMPLE
Constructing an Employee
Constructing an Employee
Call mailCheck using Salary reference --
Within mailCheck of Salary class
Mailing check to Mohd Mohtashim with salary 3600.0
Abstract Methods
If you want a class to contain a particular method but you want the actual
implementation of that method to be determined by child classes, you can
declare the method in the parent class as an abstract.
• abstract keyword is used to declare the method as abstract.
• You have to place the abstract keyword before the method name in the
method declaration.
• An abstract method contains a method signature, but no method body.
• Instead of curly braces, an abstract method will have a semoi colon (;) at
the end.
Suppose Salary class inherits the Employee class, then it should implement
the computePay() method as shown below −
/* File name : Salary.java */
public class Salary extends Employee {
private double salary; // Annual salary
Encapsulation
Encapsulation is one of the four fundamental OOP concepts. The other three
are inheritance, polymorphism, and abstraction.
The variables of the EncapTest class can be accessed using the following
program −
/* File name : RunEncap.java */
public class RunEncap {
Benefits of Encapsulation
Interfaces
An interface is a reference type in Java. It is similar to class. It is a collection of
abstract methods. A class implements an interface, thereby inheriting the
abstract methods of the interface.
Along with abstract methods, an interface may also contain constants, default
methods, static methods, and nested types. Method bodies exist only for default
methods and static methods.
Writing an interface is similar to writing a class. But a class describes the
attributes and behaviors of an object. And an interface contains behaviors that a
class implements.
Unless the class that implements the interface is abstract, all the methods of the
interface need to be defined in the class.
Declaring Interfaces
Implementing Interfaces
When a class implements an interface, you can think of the class as signing a
contract, agreeing to perform the specific behaviors of the interface. If a class
does not perform all the behaviors of the interface, the class must declare itself
as abstract.
Mammal eats
Mammal travels
When overriding methods defined in interfaces, there are several rules to be
followed −
Extending Interfaces
An interface can extend another interface in the same way that a class can
extend another class. The extends keyword is used to extend an interface, and
the child interface inherits the methods of the parent interface.
// Filename: Sports.java
public interface Sports {
public void setHomeTeam(String name);
public void setVisitingTeam(String name);
}
// Filename: Football.java
public interface Football extends Sports {
public void homeTeamScored(int points);
public void visitingTeamScored(int points);
public void endOfQuarter(int quarter);
}
// Filename: Hockey.java
public interface Hockey extends Sports {
public void homeGoalScored();
public void visitingGoalScored();
public void endOfPeriod(int period);
public void overtimePeriod(int ot);
}
The Hockey interface has four methods, but it inherits two from Sports; thus, a
class that implements Hockey needs to implement all six methods. Similarly, a
class that implements Football needs to define the three methods from Football
and the two methods from Sports.
A Java class can only extend one parent class. Multiple inheritance is not
allowed. Interfaces are not classes, however, and an interface can extend more
than one parent interface.
The extends keyword is used once, and the parent interfaces are declared in a
comma-separated list.
For example, if the Hockey interface extended both Sports and Event, it would
be declared as −
EXAMPLE
Tagging Interfaces
The most common use of extending interfaces occurs when the parent interface
does not contain any methods. For example, the MouseListener interface in the
java.awt.event package extended java.util.EventListener, which is defined as −
EXAMPLE
package java.util;
public interface EventListener
{}
An interface with no methods in it is referred to as a tagging interface. There are
two basic design purposes of tagging interfaces −
Creates a common parent − As with the EventListener interface, which is
extended by dozens of other interfaces in the Java API, you can use a tagging
interface to create a common parent among a group of interfaces. For example,
when an interface extends EventListener, the JVM knows that this particular
interface is going to be used in an event delegation scenario.
Adds a data type to a class − This situation is where the term, tagging comes
from. A class that implements a tagging interface does not need to define any
methods (since the interface does not have any), but the class becomes an
interface type through polymorphism.
They’re a similar concept to design patterns, the main difference being that
design principles are more abstract and generalized. They are high-level pieces
of advice, often applicable to many different programming languages or even
different paradigms.
The SRP, LSP, Open/Closed, and DIP principles are often bundled together and
called SOLID principles.
However, it’s important to apply common sense when using this principle. If you
use the same piece of code to do two different things initially, that doesn’t mean
those two things will always need to be dealt with in the same way.
This usually happens if structures are actually dissimilar, despite the same code
being used to handle them. The code can also be ‘over-dried’, making it
essentially unreadable because methods are called form unrelated,
incomprehensible places.
A good architecture can amortize this, but the problem can crop up in practice
nonetheless.
Violations of the DRY Principle are often referred to as WET solutions. WET can
be an abbreviation for multiple things:
• We Enjoy Typing
• Waste Everyone’s Time
• Write Every Time
• Write Everything Twice
The Keep it Simple and Stupid (KISS) principle is a reminder to keep your code
simple and readable for humans. If your method handles multiple use-cases,
split them into smaller functions. If it performs multiple functionalities, make
multiple methods instead.
The core of this principle is that for most cases, unless efficiency
is extremely crucial, another stack call isn’t going to severely affect the
performance of your program. In fact, some compilers or runtime environments
will even simplify a method call into an inline execution.
On the other hand, unreadable and long methods will be very hard to maintain
for human programmers, bugs will be harder to find, and you might find yourself
violating DRY as well because if a function does two things, you can’t call it to do
just one of them, so you’ll make another method.
All in all, if you find yourself tangled up in your own code and unsure what each
part does, it’s time for reevaluation.
It’s almost certain that the design could be tweaked to make it more readable.
And if you are having trouble as the one who designed it while it’s all still fresh in
your mind, think about how somebody who sees it for the first time in the future
will perform.
The Single Responsibility Principle (SRP) states that there should never be two
functionalities in one class. Sometimes, it’s paraphrased as:
“A class should only have one, and only one, reason to be changed.”
Where a “reason to be changed” is the responsibility of the class. If there is more
than one responsibility, there are more reasons to change that class at some
point.
This means that in the event of a functionality needing an update, there shouldn’t
be multiple separate functionalities in that same class that may be affected.
This principle makes it easier to deal with bugs, to implement changes without
confusing co-dependencies, and to inherit from a class without having to
implement or inherit methods your class doesn’t need.
While it may seem that this encourages you to rely on dependencies a lot, this
sort of modularity is much more important. Some level of dependency between
classes is inevitable, which is why we also have principles and patterns to deal
with that.
For an example, say our application should retrieve some product information
from the database, then process it and finally display it to the end-user.
We could use a single class to handle the database call, process the information
and push the information to the presentation layer. Though, bundling these
functionalities makes our code unreadable and illogical.
The Open/Closed principle states that classes or objects and methods should be
open for extension, but closed for modifications.
What this means in essence is that you should design your classes and modules
with possible future updates in mind, so they should have a generic design that
you won’t need to change the class itself in order to extend their behavior.
You can add more fields or methods, but in such a way that you don’t need to
rewrite old methods, delete old fields and modify the old code in order to make it
work again. Thinking ahead will help you write stable code, before and after an
update of requirements.
• Car is a Vehicle
• TeachingAssistaint is a CollegeEmployee
It’s important to note that these relationships don’t go in both directions. The fact
that Car is a Vehicle might not mean that Vehicle is a Car – it can be
a Motorcycle, Bicycle, Truck…
The reason this can be misleading is a common mistake people make when
thinking about it in natural language. For example, if I asked you if Square has an
“is relationship” with Rectangle, you might automatically say yes.
After all, we know from geometry that a square is a special case of rectangle. But
depending on how your structures are implemented, this might not be the case:
public class Rectangle {
protected double a;
protected double b;
@Override
public void setB(double b) {
this.a = b;
this.b = b;
}
}
You’ll notice that setters here actually set both a and b. Some of you may already
guess the problem. Let’s say we initialized our Square and applied polymorphism
to contain it within a Rectangle variable:
Rectangle rec = new Square(5);
And let’s say that sometime later in the program, maybe in an entirely separate
function, another programmer who had nothing to do with implementing these
classes, decides that they want to resize their rectangle. They may try something
like this:
rec.setA(6);
rec.setB(3);
They’ll get completely unexpected behavior and it might be difficult to trace back
what the problem is.
So when we’re inheriting we have to keep in mind the behavior of our classes
and are they really functionally interchangeable within the code, rather than just
the concepts being similar outside of the context of their usage in the program.
The Interface Segregation Principle (ISP) states that the client should never be
forced to depend on an interface they aren’t using in its entirety. This means that
an interface should have a minimum set of methods necessary for the
functionality it ensures, and should be limited to only one functionality.
For example, a Pizza interface shouldn’t be required to implement
an addPepperoni() method, because this doesn’t have to be available for every
type of pizza. For the sake of this tutorial, let’s assume that all pizzas have a
sauce and need to be baked and there’s not a single exception.
This is when we can define an interface:
public interface Pizza {
void addSauce();
void bake();
}
And then, let’s implement this through a couple of classes:
public class VegetarianPizza implements Pizza {
public void addMushrooms() {System.out.println("Adding mushrooms");}
@Override
public void addSauce() {System.out.println("Adding sauce");}
@Override
public void bake() {System.out.println("Baking the vegetarian
pizza");}
}
@Override
public void addSauce() {System.out.println("Adding sauce");}
@Override
public void bake() {System.out.println("Baking the pepperoni
pizza");}
}
The VegetarianPizza has mushrooms whereas the PepperoniPizza has
pepperoni. Both, of course, need sauce and need to be baked, which is also
defined in the interface.
If the addMushrooms() or addPepperoni() methods were located in the
interface, both classes would have to implement them even though they don’t
need both, but rather only one each.
We should strip interfaces of all but absolutely necessary functionalities.
The Dependency Inversion Principle and Inversion of Control (IoC) are used
interchangeably by some people, although it is not technically true.
Dependency Inversion guides us towards decoupling by using dependency
injection through an Inversion of Control Container. Another name of IoC
Containers could very well be Dependency Injection Containers, though the old
name sticks around.
One should often prefer composition over inheritance when designing their
systems. In Java, this means that we should more often define interfaces and
implement them, rather than defining classes and extending them.
We’ve already mentioned the Car is a Vehicle as a common guiding principle
people use to determine whether classes should inherit one another or not.
Despite being tricky to think about and tending to violate The Liskov Substitution
Principle, this way of thinking is extremely problematic when it comes to reusing
and repurposing code later in development.
@Override
public void fly() {}
@Override
public void land() {}
@Override
public void drive() {}
You may figure out a way to change the whole architecture to fit around this
new FlyingCar class, but depending on how deep in the development you are
that can be a costly process.
Given this problem, we could try and avoid this whole mess by basing our
generalities on common functionality instead of inherent similarity. This is the
way a lot of built-in Java mechanisms have been developed.
If your class is going to implement all of the functionalities and your child class
can be used as a substitute for your parent class, use inheritance.
If you class is going to implement some specific functionalities, use composition.
We use Runnable, Comparable, etc. instead of using some abstract classes
implementing their methods because it’s cleaner, it makes code more reusable,
and it makes it easy to create a new class that conforms to what we need in
order to use previously made functionalities.
This also resolves the problem of dependencies destroying important
functionalities and causing a chain reaction throughout our code. Instead of
having a big problem when we need to make our code work for a new type of
thing, we can simply make that new thing conform to previously set standards
and work just as well as the old thing.
In our vehicle example, we could just implement
interfaces Flyable and Drivable instead of introducing abstraction and
inheritance.
Our Airplane and Spaceship could implement Flyable,
our Car and Truck could implement Drivable, and our new FlyingCar could
implement both.
No changes in the class structure needed, no major DRY violations, no
confusion of colleagues. If you happen to need exact same functionality in
multiple classes, you can implement it using a default method in your interface,
to avoid violating DRY.
Completed
System.out.println( value );
};
Your Result
1010
Your grade is passed
Your Answer:
import java.util.function.DoubleConsumer; import
java.util.function.LongToIntFunction; public class
Unit2LO2Activity1JosephAustero { public static void main(String[] args) {
DoubleConsumer dc = new DoubleConsumer() { public void accept(double
value) { System.out.println( value ); } }; DoubleConsumer dc1 = (double value) ->
{ System.out.println( value ); }; dc.accept(22.83); dc1.accept(33.5);
LongToIntFunction ltif = new LongToIntFunction() { public int applyAsInt(long
value){ System.out.println( value ); return 0; } }; LongToIntFunction ltif1 = (long
value) -> { System.out.println( value ); return 0; }; ltif.applyAsInt(55);
ltif1.applyAsInt(-11); } }