Share
Explore

Dynamic Weather Reporter: Mastering Interfaces, Abstraction, and Dependency Injection in Java

Complete All Activities:
Document them in a Word Document. Save as StudentName_StudentID.docx

image.png

The SOLID principles are a set of five design principles aimed at making software designs more understandable, flexible, and maintainable.

SOLID is a set of thinking rules or HEURISTICS to inform your thinking as to how to write software.

Let's see how they apply to the Weather Application Lab:

1. **Single Responsibility Principle (SRP):**
- **Concept:** A class should have only one reason to change, meaning it should have only one job or responsibility. - **Application in Lab:** - `WeatherService` focuses solely on fetching weather data. - `BaseWeatherFormatter` and its subclasses are responsible only for formatting weather data. - This separation ensures that changes in weather data retrieval do not affect the formatting logic, and vice versa.
2. **Open/Closed Principle (OCP):**
- **Concept:** Software entities should be open for extension, but closed for modification. - **Application in Lab:** - `WeatherService` is an interface, allowing new implementations like `LocalWeatherService` or `RemoteWeatherService` without modifying existing code. - The `BaseWeatherFormatter` is an abstract class, and new formatting styles can be added by extending this class without altering its existing structure.
3. **Liskov Substitution Principle (LSP):**
- **Concept:** Objects of a superclass shall be replaceable with objects of its subclasses without breaking the application. - **Application in Lab:** - Instances of `WeatherService` can be replaced interchangeably between `LocalWeatherService` and `RemoteWeatherService` in `WeatherApp`. - Similarly, `WeatherApp` can use any subclass of `BaseWeatherFormatter` (like `SimpleWeatherFormatter` or `DetailedWeatherFormatter`) interchangeably.
4. **Interface Segregation Principle (ISP):**
- **Concept:** No client should be forced to depend on methods it does not use. - **Application in Lab:** - The `WeatherService` interface has only one method `getWeatherForecast`, ensuring that its implementers are not forced to implement unrelated methods. - If there were different types of weather data needed, separate interfaces could be created rather than overloading a single interface with multiple methods.
5. **Dependency Inversion Principle (DIP):** - **Concept:** High-level modules should not depend on low-level modules. Both should depend on abstractions. - **Application in Lab:** - `WeatherApp` depends on the `WeatherService` interface (an abstraction), not on concrete implementations like `LocalWeatherService` or `RemoteWeatherService`. - The use of dependency injection in `WeatherApp` allows it to be independent of the specific weather service and formatter implementations.

The SOLID principles guide the design of the Weather Application Lab towards a structure that is modular, flexible, and easy to maintain.
This is achieved by separating concerns, extending functionality without modification, ensuring interchangeability of subclasses, avoiding unused dependencies, and inverting dependencies from concrete implementations to abstractions.

1. **Interface - WeatherService** ```java public interface WeatherService { String getWeatherForecast(String location); }
Interface → A Java Class which contains a REQUIREMENT to write a method but not specifying how.
When you apply an Interface to a class: You as the programming must deliver that METHOD.

2. **Implementing Interfaces - LocalWeatherService and RemoteWeatherService** ```java public class LocalWeatherService implements WeatherService { @Override public String getWeatherForecast(String location) { return "Local Weather for " + location + ": Sunny"; } }
public class RemoteWeatherService implements WeatherService { @Override public String getWeatherForecast(String location) { return "Remote Weather for " + location + ": Rainy"; } } ```
3. **Abstraction - BaseWeatherFormatter and its implementations** ```java public abstract class BaseWeatherFormatter { public abstract String formatWeather(String weatherData); }
public class SimpleWeatherFormatter extends BaseWeatherFormatter { @Override public String formatWeather(String weatherData) { return "Simple Format: " + weatherData; } }
public class DetailedWeatherFormatter extends BaseWeatherFormatter { @Override public String formatWeather(String weatherData) { return "Detailed Format: Weather data - " + weatherData; } } ```
4. **Dependency Injection - WeatherApp** ```java public class WeatherApp { private WeatherService weatherService; private BaseWeatherFormatter weatherFormatter;
public WeatherApp(WeatherService weatherService, BaseWeatherFormatter weatherFormatter) { this.weatherService = weatherService; this.weatherFormatter = weatherFormatter; }
public void displayWeather(String location) { String forecast = weatherService.getWeatherForecast(location); String formattedForecast = weatherFormatter.formatWeather(forecast); System.out.println(formattedForecast); } } ```
5. **Integration and Testing** - Students should write code to instantiate `WeatherApp` with different combinations of `WeatherService` and `BaseWeatherFormatter`. - Encourage them to write test cases for each component.
6. **Advanced Concepts:** - If using Spring, demonstrate how to configure beans and dependencies in the application context.
**Tips for Students:** - Pay attention to the principles of SOLID design while implementing these concepts. - Experiment with different weather data formats and formatter styles for a deeper understanding. - Document your code and explain your design choices in comments.
This lab will allow students to see how interfaces, abstraction, and dependency injection play a crucial role in building flexible and maintainable applications.

Explain what dependency injection is and how and why it is being used here: 4. **Dependency Injection - WeatherApp**

```java
public class WeatherApp {
private WeatherService weatherService;
private BaseWeatherFormatter weatherFormatter;

public WeatherApp(WeatherService weatherService, BaseWeatherFormatter weatherFormatter) {
this.weatherService = weatherService;
this.weatherFormatter = weatherFormatter;
}

Dependency Injection (DI) is a design pattern used in object-oriented programming.
It involves supplying an object (the client) with its dependencies from an external source rather than the object creating them itself.
In the context of Java, these dependencies often refer to other objects.
In the WeatherApp example:
public class WeatherApp {
private WeatherService weatherService;
private BaseWeatherFormatter weatherFormatter;

public WeatherApp(WeatherService weatherService, BaseWeatherFormatter weatherFormatter) {
this.weatherService = weatherService;
this.weatherFormatter = weatherFormatter;
}
...
}

Here's how and why Dependency Injection is used:
How it's Used:
WeatherApp depends on two components: WeatherService and BaseWeatherFormatter.
Instead of WeatherApp instantiating specific implementations of these components, they are passed in as parameters in its constructor.
This means the decision about which implementations of WeatherService and BaseWeatherFormatter to use is made outside of the WeatherApp class.
Why it's Used:
Flexibility and Decoupling: By injecting dependencies, WeatherApp is not tightly coupled to specific implementations of WeatherService or BaseWeatherFormatter.
This allows for greater flexibility, as different implementations can be passed without modifying the WeatherApp's code.
Ease of Testing: It makes the application easier to test. For example, you can pass mock implementations of WeatherService and BaseWeatherFormatter when testing WeatherApp, without needing to alter its internal logic.
Maintainability: Changes to the logic of weather data retrieval or formatting do not require changes to the WeatherApp class. This separation of concerns leads to cleaner, more maintainable code.
Scalability: DI makes it easier to scale the application since new functionalities can be added with new implementations without affecting existing code.
In summary, Dependency Injection in the WeatherApp example allows the application to be more modular and flexible, enabling easy changes and testing without deeply entangled dependencies.

“So dependency injection DI works by creating compositional object references in the host class?”

Yes, that's a good way to describe it.
Dependency Injection (DI) is a design pattern in software engineering where a class receives its dependencies from external sources rather than creating them itself. In the context of the WeatherApp class:
How it's Used:
The WeatherApp class requires two dependencies: a WeatherService to get weather forecasts and a BaseWeatherFormatter to format these forecasts.
Rather than creating instances of these dependencies within itself (which would tightly couple it to specific implementations), WeatherApp is designed to accept any implementation of these interfaces through its constructor. This is where the injection occurs.
Why it's Used:
Flexibility and Extensibility: Since WeatherApp is not tightly coupled to specific implementations of WeatherService or BaseWeatherFormatter, it can work with any implementation that adheres to these interfaces. This makes the application more flexible and easier to extend.
Ease of Testing: It allows for easier unit testing. You can pass in mock implementations of WeatherService and BaseWeatherFormatter to test WeatherApp without relying on external systems like weather data services.
Decoupling of Concerns: It promotes a decoupling between the class and its dependencies, adhering to the SOLID principles of object-oriented design, particularly the Dependency Inversion Principle.
In summary, Dependency Injection in WeatherApp allows the class to remain independent of the specific implementations of its dependencies, leading to a design that's more modular, easier to test, and easier to maintain.


Final Working Code Solution:

Each public class should be declared in its own file, as per Java's file organization rules.

You should create separate Java files for each public class and interface.

Here's how you can organize the code:


Ech public class should be declared in its own file, as per Java's file organization rules. To resolve these errors, you should create separate Java files for each public class and interface. Here's how you can organize the code:
WeatherService.java
javaCopy code
public interface WeatherService { String getWeatherForecast(String location); }
LocalWeatherService.java
javaCopy code
public class LocalWeatherService implements WeatherService { @Override public String getWeatherForecast(String location) { return "Local Weather for " + location + ": Sunny"; } }
RemoteWeatherService.java
javaCopy code
public class RemoteWeatherService implements WeatherService { @Override public String getWeatherForecast(String location) { return "Remote Weather for " + location + ": Rainy"; } }
BaseWeatherFormatter.java
javaCopy code
public abstract class BaseWeatherFormatter { public abstract String formatWeather(String weatherData); }
SimpleWeatherFormatter.java
javaCopy code
public class SimpleWeatherFormatter extends BaseWeatherFormatter { @Override public String formatWeather(String weatherData) { return "Simple Format: " + weatherData; } }
DetailedWeatherFormatter.java
javaCopy code
public class DetailedWeatherFormatter extends BaseWeatherFormatter { @Override public String formatWeather(String weatherData) { return "Detailed Format: Weather data - " + weatherData; } }
WeatherApp.java
javaCopy code
class WeatherApp
{ private WeatherService weatherService; private BaseWeatherFormatter weatherFormatter;
public WeatherApp(WeatherService weatherService, BaseWeatherFormatter weatherFormatter) { this.weatherService = weatherService; this.weatherFormatter = weatherFormatter; } public void displayWeather(String location) { String forecast = weatherService.getWeatherForecast(location); String formattedForecast = weatherFormatter.formatWeather(forecast); System.out.println(formattedForecast); } }
Main.java
javaCopy code
public class Main { public static void main(String[] args) { // Creating instances of WeatherService implementations WeatherService localWeatherService = new LocalWeatherService(); WeatherService remoteWeatherService = new RemoteWeatherService(); // Creating instances of BaseWeatherFormatter implementations BaseWeatherFormatter simpleFormatter = new SimpleWeatherFormatter(); BaseWeatherFormatter detailedFormatter = new DetailedWeatherFormatter(); // Creating an instance of WeatherApp with dependency injection WeatherApp weatherApp = new WeatherApp(localWeatherService, simpleFormatter); // Displaying weather information System.out.println("Using Local Weather Service and Simple Formatter:"); weatherApp.displayWeather("New York"); // Switching to a different service and formatter weatherApp = new WeatherApp(remoteWeatherService, detailedFormatter); System.out.println("\nUsing Remote Weather Service and Detailed Formatter:"); weatherApp.displayWeather("Los Angeles"); } }
Each of these snippets should be in its own file named as indicated. Once organized this way, your Java application should compile and run without issues.
Want to print your doc?
This is not the way.
Try clicking the ⋯ next to your doc name or using a keyboard shortcut (
CtrlP
) instead.