0% found this document useful (0 votes)
147 views

LO 2 Apply Java Advanced Class Design and Object Oriented Design Principles

The document discusses key object-oriented programming concepts in Java including abstraction, abstract classes, interfaces, encapsulation, and inheritance. It provides examples of implementing each concept in Java code. For abstraction, it shows how to create an abstract Employee class and concrete Salary class that inherits from it. For interfaces, it describes the basic syntax for defining an interface. For encapsulation, it demonstrates hiding class fields and accessing them through getters and setters.

Uploaded by

LJames Sacueza
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
147 views

LO 2 Apply Java Advanced Class Design and Object Oriented Design Principles

The document discusses key object-oriented programming concepts in Java including abstraction, abstract classes, interfaces, encapsulation, and inheritance. It provides examples of implementing each concept in Java code. For abstraction, it shows how to create an abstract Employee class and concrete Salary class that inherits from it. For interfaces, it describes the basic syntax for defining an interface. For encapsulation, it demonstrates hiding class fields and accessing them through getters and setters.

Uploaded by

LJames Sacueza
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 24

LO 2 : Apply Java Advanced Class Design and Object Oriented Design

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.

Likewise in Object-oriented programming, abstraction is a process of hiding the


implementation details from the user, only the functionality will be provided to the
user. In other words, the user will have the information on what the object does
instead of how it does it.

In Java, abstraction is achieved using Abstract classes and interfaces.

Abstract Class

A class which contains the abstract keyword in its declaration is known as


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

This section provides you an example of the abstract class. To create an


abstract class, just use the abstract keyword before the class keyword, in the
class declaration.
/* File name : Employee.java */
public abstract class Employee {
private String name;
private String address;
private int number;

public Employee(String name, String address, int number) {


System.out.println("Constructing an Employee");
this.name = name;
this.address = address;
this.number = number;
}

public double computePay() {


System.out.println("Inside Employee computePay");
return 0.0;
}

public void mailCheck() {


System.out.println("Mailing a check to " + this.name + " " +
this.address);
}

public String toString() {


return name + " " + address + " " + number;
}

public String getName() {


return name;
}

public String getAddress() {


return address;
}

public void setAddress(String newAddress) {


address = newAddress;
}

public int getNumber() {


return number;
}
}
You can observe that except abstract methods the Employee class is same as
normal class in Java. The class is now abstract, but it still has three fields, seven
methods, and one constructor.

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

Inheriting the Abstract Class

We can inherit the properties of Employee class just like concrete class in the
following way −
EXAMPLE

/* File name : Salary.java */


public class Salary extends Employee {
private double salary; // Annual salary

public Salary(String name, String address, int number, double


salary) {
super(name, address, number);
setSalary(salary);
}

public void mailCheck() {


System.out.println("Within mailCheck of Salary class ");
System.out.println("Mailing check to " + getName() + " with
salary " + salary);
}

public double getSalary() {


return salary;
}

public void setSalary(double newSalary) {


if(newSalary >= 0.0) {
salary = newSalary;
}
}

public double computePay() {


System.out.println("Computing salary pay for " + getName());
return salary/52;
}
}
Here, you cannot instantiate the Employee class, but you can instantiate the
Salary Class, and using this instance you can access all the three fields and
seven methods of Employee class as shown below.
/* File name : AbstractDemo.java */
public class AbstractDemo {

public static void main(String [] args) {


Salary s = new Salary("Mohd Mohtashim", "Ambehta, UP", 3,
3600.00);
Employee e = new Salary("John Adams", "Boston, MA", 2,
2400.00);
System.out.println("Call mailCheck using Salary reference --
");
s.mailCheck();
System.out.println("\n Call mailCheck using Employee
reference--");
e.mailCheck();
}
}
This produces the following result −
OUTPUT

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

Call mailCheck using Employee reference--


Within mailCheck of Salary class
Mailing check to John Adams with salary 2400.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.

Following is an example of the abstract method.


EXAMPLE

public abstract class Employee {


private String name;
private String address;
private int number;

public abstract double computePay();


// Remainder of class definition
}
Declaring a method as abstract has two consequences −

• The class containing it must be declared as abstract.


• Any class inheriting the current class must either override the abstract
method or declare itself as abstract.

Note − Eventually, a descendant class has to implement the abstract method;


otherwise, you would have a hierarchy of abstract classes that cannot be
instantiated.

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

public double computePay() {


System.out.println("Computing salary pay for " + getName());
return salary/52;
}
// Remainder of class definition
}

Encapsulation
Encapsulation is one of the four fundamental OOP concepts. The other three
are inheritance, polymorphism, and abstraction.

Encapsulation in Java is a mechanism of wrapping the data (variables) and code


acting on the data (methods) together as a single unit. In encapsulation, the
variables of a class will be hidden from other classes, and can be accessed only
through the methods of their current class. Therefore, it is also known as data
hiding.

To achieve encapsulation in Java −

• Declare the variables of a class as private.


• Provide public setter and getter methods to modify and view the variables
values.
EXAMPLE

Following is an example that demonstrates how to achieve Encapsulation in


Java −
/* File name : EncapTest.java */
public class EncapTest {
private String name;
private String idNum;
private int age;

public int getAge() {


return age;
}

public String getName() {


return name;
}

public String getIdNum() {


return idNum;
}

public void setAge( int newAge) {


age = newAge;
}

public void setName(String newName) {


name = newName;
}
public void setIdNum( String newId) {
idNum = newId;
}
}
The public setXXX() and getXXX() methods are the access points of the instance
variables of the EncapTest class. Normally, these methods are referred as
getters and setters. Therefore, any class that wants to access the variables
should access them through these getters and setters.

The variables of the EncapTest class can be accessed using the following
program −
/* File name : RunEncap.java */
public class RunEncap {

public static void main(String args[]) {


EncapTest encap = new EncapTest();
encap.setName("James");
encap.setAge(20);
encap.setIdNum("12343ms");

System.out.print("Name : " + encap.getName() + " Age : " +


encap.getAge());
}
}
This will produce the following result −
OUTPUT

Name : James Age : 20

Benefits of Encapsulation

• The fields of a class can be made read-only or write-only.


• A class can have total control over what is stored in its fields.

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.

An interface is similar to a class in the following ways −

• An interface can contain any number of methods.


• An interface is written in a file with a .java extension, with the name of the
interface matching the name of the file.
• The byte code of an interface appears in a .class file.
• Interfaces appear in packages, and their corresponding bytecode file must
be in a directory structure that matches the package name.

However, an interface is different from a class in several ways, including −

• You cannot instantiate an interface.


• An interface does not contain any constructors.
• All of the methods in an interface are abstract.
• An interface cannot contain instance fields. The only fields that can appear
in an interface must be declared both static and final.
• An interface is not extended by a class; it is implemented by a class.
• An interface can extend multiple interfaces.

Declaring Interfaces

The interface keyword is used to declare an interface. Here is a simple example


to declare an interface −
EXAMPLE

Following is an example of an interface −


/* File name : NameOfInterface.java */
import java.lang.*;
// Any number of import statements

public interface NameOfInterface {


// Any number of final, static fields
// Any number of abstract method declarations\
}
Interfaces have the following properties −
• An interface is implicitly abstract. You do not need to use
the abstract keyword while declaring an interface.
• Each method in an interface is also implicitly abstract, so the abstract
keyword is not needed.
• Methods in an interface are implicitly public.
EXAMPLE

/* File name : Animal.java */


interface Animal {
public void eat();
public void travel();
}

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.

A class uses the implements keyword to implement an interface. The


implements keyword appears in the class declaration following the extends
portion of the declaration.
EXAMPLE

/* File name : MammalInt.java */


public class MammalInt implements Animal {

public void eat() {


System.out.println("Mammal eats");
}

public void travel() {


System.out.println("Mammal travels");
}

public int noOfLegs() {


return 0;
}

public static void main(String args[]) {


MammalInt m = new MammalInt();
m.eat();
m.travel();
}
}
This will produce the following result −
OUTPUT

Mammal eats
Mammal travels
When overriding methods defined in interfaces, there are several rules to be
followed −

• Checked exceptions should not be declared on implementation methods


other than the ones declared by the interface method or subclasses of
those declared by the interface method.
• The signature of the interface method and the same return type or subtype
should be maintained when overriding the methods.
• An implementation class itself can be abstract and if so, interface methods
need not be implemented.

When implementation interfaces, there are several rules −

• A class can implement more than one interface at a time.


• A class can extend only one class, but implement many interfaces.
• An interface can extend another interface, in a similar way as a class can
extend another class.

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.

The following Sports interface is extended by Hockey and Football interfaces.


EXAMPLE

// 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.

Extending Multiple Interfaces

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

public interface Hockey extends Sports, Event

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.

Design principles are generalized pieces of advice or proven good coding


practices that are used as rules of thumb when making design choices.

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.

Object Oriented Design Principles


Design patterns are also abstractions or generalized good practices, but they
provide much more concrete and practical low-level advice, and are related to
entire classes of problems rather than just generalized coding practices.
Some of the most important design principles in the object oriented paradigm are
listed in this article, but this is by no means an exhaustive list.

• Don’t Repeat Yourself (DRY) Principle


• Keep It Simple and Stupid (KISS) Principle
• The Single Responsibility Principle (SRP)
• The Open/Closed Principle
• Liskov Substitution Principle (LSP)
• The Interface Segregation Principle (ISP)
• The Dependency Inversion Principle (DIP)
• The Composition Over Inheritance Principle

The SRP, LSP, Open/Closed, and DIP principles are often bundled together and
called SOLID principles.

Don’t Repeat Yourself (DRY) Principle


The Don’t Repeat Yourself (DRY) principle is a common principle across
programming paradigms, but it is especially important in OOP. According to the
principle:
Every piece of knowledge or logic must have a single, unambiguous
representation within a system.
When it comes to OOP, this means utilizing abstract classes, interfaces, and
public constants. Whenever there’s a functionality common across classes, it
either might make sense to abstract them away into a common parent class or
use interfaces to couple their functionality:
public class Animal {
public void eatFood() {
System.out.println("Eating food...");
}
}

public class Cat extends Animal {


public void meow() {
System.out.println("Meow! *purrs*");
}
}

public class Dog extends Animal {


public void woof() {
System.out.println("Woof! *wags tail*");
}
}
Both a Cat and a Dog need to eat food, but they speak differently. Since eating
food is a common functionality for them, we can abstract it into a parent class
such as Animal and then have them extend the class.
Now, instead of both classes implementing the same functionality of eating food,
each can focus on their own unique logic.
Cat cat = new Cat();
cat.eatFood();
cat.meow();

Dog dog = new Dog();


dog.eatFood();
dog.woof();
The output would be:
Eating food...
Meow! *purrs*
Eating food...
Woof! *wags tail*
Whenever there’s a constant that’s used multiple times, it’s good practice to
define it as a public constant:
static final int GENERATION_SIZE = 5000;
static final int REPRODUCTION_SIZE = 200;
static final int MAX_ITERATIONS = 1000;
static final float MUTATION_SIZE = 0.1f;
static final int TOURNAMENT_SIZE = 40;
For an example, we’ll be using these constants several times, and eventually
we’ll be changing their values manually to optimize a genetic algorithm. It would
be easy to make a mistake if we had to update each of these values at multiple
places.

Also, we don’t want to make a mistake and programmatically change these


values during execution, so we’re also introducing the final modifier.
Note: Due to the naming convention in Java, these should be capitalized with
words separated by an underscore (“_”).

The purpose of this principle is to ensure easy maintenance of code, because


when a functionality or a constant changes you have to edit the code only in one
place. This not only makes the job easier, but ensures that mistakes won’t
happen in the future. You may forget to edit the code in multiple places, or
somebody else who’s not as familiar with your project may not know that you’ve
repeated code and may end up editing it in just one place.

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

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

WET solutions aren’t always bad, as repetition is sometimes advisable in


inherently dissimilar classes, or in order to make code more readable, less inter-
dependent, etc.

Keep It Simple and Stupid (KISS) Principle

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)

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.

What we’d do instead is defining a class, such as ProductService that would


fetch the product from the database, a ProductController to process the info
and then we’d display it in a presentation layer – either an HTML page or another
class/GUI.

The Open/Closed Principle

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.

This principle is important in order to ensure backwards compatibility and


prevent regressions – a bug which happens when your programs features or
efficiency breaks after an update.

Liskov Substitution Principle (LSP)

According to the Liskov Substitution Principle (LSP), derived classes should be


able to substitute their base classes without the behavior of your code changing.
This principle is closely related to The Interface Segregation Principle and The
Single Responsibility Principle, meaning that a violation of either of those is likely
to be (or become) a violation of LSP as well. This is because if a class does
more than one thing, subclasses extending it are less likely to meaningfully
implement those two or more functionalities.
A common way people think about object relationships (which can be a bit
misleading at times) is that there needs to be an is relationship between classes.
For example:

• 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;

public Rectangle(double a, double b) {


this.a = a;
this.b = b;
}

public void setA(double a) {


this.a = a;
}

public void setB(double b) {


this.b = b;
}

public double calculateArea() {


return a*b;
}
}
Now let’s try inheriting from it for our Square within the same package:
public class Square extends Rectangle {
public Square(double a) {
super(a, a);
}
@Override
public void setA(double a) {
this.a = a;
this.b = a;
}

@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.

If they try to use rec.calculateArea() the result won’t be 18 as they might


expect from a rectangle with sides of lengths 6 and 3.
The result would instead be 9 because their rectangle is actually a square and
has two equal sides – of length 3.
You may say that this is exactly the behavior you wanted because that’s how a
square works, but it’s nonetheless not the expected behavior from a rectangle.

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)

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");}
}

public class PepperoniPizza implements Pizza {


public void addPepperoni() {System.out.println("Adding pepperoni");}

@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 (DIP)

According to the Dependency Inversion Principle (DIP), high-level and low-level


modules should be decoupled in such a way that changing (or even replacing)
low-level modules doesn’t require (much) rework of high-level modules. Given
that, both low-level and high-level modules shouldn’t depend on each other, but
rather they should depend on abstractions, such as interfaces.
Another important thing DIP states is:

Abstractions should not depend on details. Details (concrete implementations)


should depend on abstractions.
This principle is important because it decouples modules, making the system
less complex, easier to maintain and update, easier to test, and more reusable. I
can’t stress enough how much of a game changer this is, especially for unit
testing and reusability. If the code is written generically enough, it can easily find
application in another project, while code that’s too specific and interdependent
with other modules of the original project will be hard to decouple from it.

This principle is closely related to the dependency injection, which is practically


the implementation or rather, the goal of DIP. DI boils down to – if two classes
are dependent, their features should be abstracted away and they should both
depend on the abstraction, instead of on each other. This essentially should
allow us to change details of the implementation while retaining its functionality.

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.

The Composition Over Inheritance Principle

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.

The problem here is illustrated by the following example:


Spaceship and Airplane extend an abstract class FlyingVehicle,
while Car and Truck extend GroundVehicle. Each have their respective
methods which make sense for the type of vehicle, and we’d naturally group
them together with abstraction when thinking of them in these terms.
This inheritance structure is based on thinking about objects in terms of what
they are instead of what they do.
The problem with this is that new requirements can throw the whole hierarchy off
balance. In this example, what if your boss waltzed in and informed you that a
client wants a flying car now? If you inherit from FlyingVehicle, you’ll have to
implement drive() again even though that same functionality already exists,
thereby violating the DRY Principle, and vice-versa:
public class FlyingVehicle {
public void fly() {}
public void land() {}
}

public class GroundVehicle {


public void drive() {}
}

public class FlyingCar extends FlyingVehicle {

@Override
public void fly() {}

@Override
public void land() {}

public void drive() {}


}

public class FlyingCar2 extends GroundVehicle {

@Override
public void drive() {}

public void fly() {}


public void land() {}
}
Since most languages, including Java, don’t allow multiple inheritance, we can
opt to extend either one of these classes. Though, in both cases, we can’t inherit
the functionality of the other and have to rewrite it.

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

Anonymous Class and Lambda Expression


DoubleConsumer dc = new DoubleConsumer() {

public void accept(double value) {

System.out.println( value );

};

LongToIntFunction ltif = new LongToIntFunction();

Using the following code above:

1. Create anonymous class for LongToIntFunction


2. Create Lamba Expression for DoubleConsumer and LongToIntFunction
3. Save them in a file called Unit2LO2Activity1[FirstName + LastName].java
example Unit2LO2Activity1VernMontorio.java
4. pass the .java file or .zip file

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); } }

Your Uploaded File(s):

You might also like