This commit is contained in:
me
2026-04-09 17:33:38 +03:00
parent 1cc1dcfb8e
commit 8754d3cfee
103 changed files with 10897 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
---
# title: Courses
weight: 2
---
<!--* [Introduction to Programming in Java](prog-intro/)
* [Paradgims of Programming](paradgims/)-->

View File

@@ -0,0 +1,5 @@
---
title: Paradgims of Programming
weight: 3
---

View File

@@ -0,0 +1,5 @@
---
title: Introduction to Programming in Java
weight: 3
---

View File

@@ -0,0 +1,4 @@
---
title: Homeworks
weight: 4
---

View File

@@ -0,0 +1,12 @@
// ParseIntExample.java
public class ParseIntExample {
public static void main(String[] args) {
String s = "123";
int sum = 321;
//! sum = sum + s;
sum = sum + Integer.parseInt(s);
System.out.println(sum);
}
}

View File

@@ -0,0 +1,17 @@
// Sum.java
public class Sum {
public static void main(String[] args) {
System.out.println("number of arguments: " + args.length);
int sum = 0;
for (String argument : args) {
sum = sum + argument;
System.out.println(argument);
System.out.println(sum);
}
System.out.println(sum);
}
}

View File

@@ -0,0 +1,245 @@
---
title: Sum
weight: 5
---
# Task
1. You need to create a `Sum` class which will sum integers from command line arguments and output the sum to console.
2. Examples:
```sh
java Sum 1 2 3
```
Expected output: `6`.
```sh
java Sum 1 2 -3
```
Expected output: `0`.
```sh
java Sum "1 2 3"
```
Expected output: `6`.
```sh
java Sum "1 2" " 3"
```
Expected output: `6`.
```sh
java Sum " "
```
Expected output: `0`.
3. Arguments can be:
- digits,
- signes `+` and `-`,
- [space symbols](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Character.html#isWhitespace(char))
4. You can assume that `int` type is sufficient for in-between calculations and result.
5. Before doing the task make sure to read docs for classes [`String`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/String.html) and [`Integer`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Integer.html).
6. For debugging use [`System.err`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/System.html#err), because it will be ingnored by the testing program.
---
# Solution
After reading about [`String`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/String.html), [`Integer`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Integer.html), [`System.err`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/System.html#err) we now know about some usefull methods:
- [`Integer.parseInt(String s, int radix)`](https://docs.oracle.com/javase/8/docs/api/java/lang/Integer.html#parseInt-java.lang.String-) which parses the string argument as a signed integer in the radix specified by the second argument. So it basically converts a number from the `String` data type to `int`.
- [`Character.isWhitespace(char ch)`](https://docs.oracle.com/javase/8/docs/api/java/lang/Character.html#isWhitespace-char-) which checks if a character `ch` is a space symbol or not.
Now let's start coding. Firstly let's define the structure of our program. I will be putting the name of the file at the first line comment in a file and its path if its nessesary.
```java
// Sum.java
public class Sum {
public static void main(String[] args) {
// ...
}
}
```
Okay that's done. What do we do next? Let's look at `String[] args` argument to our `main` method. It represents an array of command line arguments which we need to sum. So know we made our task a little bit easier. Now we can just say that we need to find sum of elements of array `args`.
How are we going to do it though? First let's understand what we can do with this array. Let's modify our class a little bit.
```java
// Sum.java
public class Sum {
public static void main(String[] args) {
System.out.println(args.length);
}
}
```
We've added `System.out.println(args.length)` which takes the field `length` from our `args` object and prints it to the console.
Let's try it. First compile our class using
```sh
$ javac Sum.java
```
And then we can do some manual testing.
```sh
$ java Sum 1 2 3
3
```
We got `3` as an output as expected. We gave our program 3 command line arguments: `1`, `2` and `3`.
Here are all of the examples
```sh
$ java Sum 1 2 -3
3
```
```sh
$ java Sum "1 2 3"
1
```
> [!NOTE]
> Notice, that we got 1 instead of 3. That's because we put our arguments in `""` so this becomes a single string argument.
```sh
$ java Sum "1 2" " 3"
2
```
```sh
$ java Sum " "
1
```
> [!NOTE]
> Here program gives us 1 instead of 0, because despite not having any numbers in the arguments a single whitespace is still an argument.
Now let's try not obly to count our arguments but to list them as well. Let's modify our program a little bit more.
```java
// Sum.java
public class Sum {
public static void main(String[] args) {
System.out.println("number of arguments: " + args.length);
for (String argument : args) {
System.out.println(argument);
}
}
}
```
Here I used `for` loop to do printing ***for*** every `String` element in `args`.
Let's try this with our examples. And don't forget to recompile using `javac Sum.java`.
```sh
$ java Sum 1 2 3
number of arguments: 3
1
2
3
```
```sh
$ java Sum 1 2 -3
number of arguments: 3
1
2
-3
```
```sh
$ java Sum "1 2 3"
number of arguments: 1
1 2 3
```
> [!NOTE]
> Again, notice only ***one*** string argument.
```sh
$ java Sum "1 2" " 3"
number of arguments: 2
1 2
3
```
```sh
$ java Sum " "
number of arguments: 1
```
Okay, now that we know how to ***iterate*** (or do something for every element), we can calculate the *sum* of all the numbers in the `args` array. To do that we need to create a new variable `sum` of *integer* type and assign it the **value** of `0`. Then for every number in `args` we will add it to `sum`, and so by the end of array we will have the sum of all numbers stored in variable `sum`. This is what the code will look like
```java
// Sum.java
public class Sum {
public static void main(String[] args) {
System.out.println("number of arguments: " + args.length);
int sum = 0;
for (String argument : args) {
sum = sum + argument;
System.out.println(argument);
System.out.println(sum);
}
System.out.println(sum);
}
}
```
Seems ok. But let's test it.
```sh
$ javac Sum.java
Sum.java:9: error: incompatible types: String cannot be converted to int
sum = sum + argument;
^
1 error
```
We got a compilation error that says `String cannot be converted to int`. It means that when we try to add 2 variables `sum` and `argument` their types don't match. The type of `sum` is integer and string for `argument`. It seems pretty logical because what do we expect when adding for example `1` and `apple`?..
> [!IMPORTANT]
> So we need to *cast* `argument` to integer data type so that we can add it to `sum`.
We can do so using [`Integer.parseInt()`](https://docs.oracle.com/javase/8/docs/api/java/lang/Integer.html#parseInt-java.lang.String-) method.
It takes a `String s` and returns an `int`, which a string `s` represents. For example this code will work as expected
```java
// ParseIntExample.java
public class ParseIntExample {
public static void main(String[] args) {
String s = "123";
int sum = 321;
//! sum = sum + s;
sum = sum + Integer.parseInt(s);
System.out.println(sum);
}
}
```
The commented out line (`//!`) would have given us an exception.

View File

@@ -0,0 +1,4 @@
---
title: Lectures
weight: 4
---

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 46 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 48 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 53 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

View File

@@ -0,0 +1,145 @@
---
title: Lecture 1. Introduction
weight: 5
---
# Why do we choose Java?
According to **TIOBE Programming Community Index** Java is one of the most demanded languages in the programming field.
![TIOBE Programming Community Index](assets/tiobe.png)
*Img. 1 - TIOBE Programming Community Index*
Also, Java has a lot of advantages for beginners such as:
* It's fairly easy
* It has a broad spectre of usage
* Server
* Desktop
* Mobile devices
* Smart-cards
# What does Java consist of?
Java consists of multiple main components. The first one being the **Java compiler**. The process of converting *human-readable* text to *machine code* is called **compilation**.
![Compilation Process Simplified](assets/compilation-process-simplified.svg)
*Img.2 - Simplified Compilation Process*
The behaviour of the Java compiler is described by the [Java Language Specification](https://docs.oracle.com/javase/specs/) or JLS.
Let's talk about compilers a little bit. For example if we will take *C++*. If we take C++ compiler for *x86-64* platform and *Windows* operating system, and launch the compiler on source code it will turn it directly into assembly code.
![C++ code compilation under x86-64](assets/cpp-x86-64-compilation.svg)
*Img. X -- C++ code compilation process under x86-64 architecture.*
But what if I want to run my program on *Linux* instead of Windows. I will need to take a different compiler under different operating system and recompile my code using a new compiler. It's not very convenient.
Java tries to protect us from that. By converting the *Java source code* into *Java byte code*. And then the *byte code* will be ran on the *Java virtual machine* which will run our program on the native processor.
![Java code compilation](assets/java-code-compilation.svg)
*Img. X -- Java code compilation*
This approach allows to change only **JVM** according to our platform (*x86_64, ARM, ...*) and operating system (*Windows, MacOS, GNU-Linux, ...*) while *byte code* stays the same. We can only write our code once, than compile it and run under everywhere.
As the motto says:
> Write once -- ~debug~ run everywhere.
The third component of Java is the standart library which is included in the JVM.
There are multiple redactions of Java-platforms:
* Standart edition
- *For regular aplications*
* Enterprise edition
- *For server aplications*
* Micro-edition
- *For mobile aplications*
- *Isn't in use nowadays 🪦*
* Java Card
- *Sim- and smart-cards*
There also were multiple versions of Java throughout its history.
- JDK 1.0 (Jan 1996)
- J2SE 1.2 (Dec 1998)
* *Collections Framework*
- J2SE 5.0 (Sep 2004)
* *Generics*
- Java SE 8 (Mar 2014)
* *Streams and Lambdas*
- Java SE 9 (Sep 2017)
* *Modules*
- Java SE 10 (Mar 2018)
* `var`
- Java 11 (Sep 2018)
* `jshell`
- Java 17 [LTS-old] (Sep 2021)
* *Previous stable version*
* *Many little changes*
- Java 21 [LTS] (Sep 2023)
* *Current stable version*
- Java 25 (Sep 2025)
* *Next version*
Java comes in two parts: **JDK - Java Development Kit** and **JVM - Java Virtual Machine**.
There is also a **JRE - Java Runtime Environment**. For example if we want to run our code somewhere on the server we don't need to compile it there because we have our byte code and we just need JRE to run it.
Some of the most popular JVMs right now are:
- OpenJDK
- Eclipse
- Azul Systems
- Excelsior JET
The disadvantage of such system is in the connection between JVM and a native processing unit. In case of C++ compiler that we reviewed earlier the source code is compiled directly into machine-code but in case with Java it is compiled into byte-code. And so the problem is to develop such a JVM that would quickly turn our byte-code into machine-code. Anyway it takes extra time. That's why it mostly will be slower than direct compilation into machine-code. So ultimately while we have the advantage of compiling out code only once, we have the disadvantage of turning byte-code into machine-code slower.
None the less, there is a way to speed up this process which is called JIT - Just In Time compilation. How does it work? While our program is running some of the instructions or functions turns directly into processor commands.
# What is garbage collection?
For example we have `int` which is represented with 4 bytes of data which is directly stored in memory.
![`int` in memory](assets/int-in-memory.svg)
*Img. X -- `int` in memory*
But what if we have a `String`. How many memory cells does this string take? We don't know. We will say that our `String` that has length of 5 symbols is stored at `0x12347865`. We defined an address where this string is located in memory. And somewhere in the memory of our programm will be a large buffer where the 5 cells will be stored.
![`String` in memory](assets/string-in-memory.svg)
*Img. X -- `String` in memory*
But what do we do with that buffer? We won't need it forever and so sometimes we need to clear that buffer. In case with C/C++ whoever created the memory for that string is in charge of clearing it. There are also languages with garbage collection such as Java and Python. The purpose of the **Garbage Collector** is to automatically find variables that we no longer need and clear their memory.
Suppose we have some code like this.
```java
if (someCondition) {
x = [1, 3, 7] // first link
// some code here
y = x // second link
// some code here
} // no links
```
After the `if`-statement we no longer need `x` or `y`. Every variable in this case `x` and `y` is the link to our array (`[1, 3, 7]`). After we left the `if`-statement the amount of links to the array is zero, so we can safely delete the array. This approach is implemented in Python. The problem is that if we have to objects linked to one another and there are no external links, they will not be deleted by garbage collector since there link counter is 1.
![Edge case for link couting](assets/edge-case-for-link-counting.svg)
*Img. X -- Edge case for link counting*
The advanced way of implementing the garbage collection is *traversing the graph of links* which is implemented in Java, C# and Go.
# What other advantages does Java have?
- It's easy (in terms of syntax)
- It's secure and stable
- It supports Unicode
- It supports multithreading
- It has backwards compatibility
# How should Java code look like?
You can go and learn about it [here](https://google.github.io/styleguide/javaguide.html).

View File

@@ -0,0 +1,670 @@
---
# title: Courses
weight: 3
---
# Prerequisites
Before diving in, make sure you're comfortable with the following:
- **Java** — solid understanding of the language
- **Object-oriented programming** — classes, methods and interfaces
- **Databases** — tables, primary keys, foreign keys, relationships, etc.
- **SQL** — ability to write basic SQL statements
---
# What is a Spring Framework?
**Spring** is a popular framework for building Java applications. It has a lot of modules, each designed to handle a specific task. They are combined into a few different layers.
![Spring layers](assets/spring-layers.svg)
*Img. 1 — Spring layers*
| **Layer** | **Purpose** |
|-----------|-------------|
| *Core* | Handling dependency injection, managing objects |
| *Web* | Building web applications |
| *Data* | Working with databases |
| *AOP* | Aspect oriented programming |
| *Test* | Testing spring components |
---
While the Spring Framework is powerful, using it often involves a lot of configuration. For example, if you want to build a web app you might need to setup a web server, configure routing and manage dependencies manually. That's when **Spring Boot** comes in.
> [!NOTE]
> You can think of Spring Boot as a layer on top of the Spring Framework that takes care of all of the setup. *Spring Boot* simplifies Spring development by providing sensible defaults and ready-to-use features.
By the way, the Spring Framework is just one part of a larger family of projects in the **Spring ecosystem**.
![Spring ecosystem](assets/spring-ecosystem.svg)
*Img. 2 — Spring ecosystem*
| **Module Name** | **Purpose** |
|------------------------|-------------|
| *Spring Data* | Simplifying database access |
| *Spring Security* | Adding authentication and authorization |
| *Spring Batch* | Batch processing |
| *Spring Cloud* | Building microservices and distributed systems |
| *Spring Integration* | Simplifying messaging and integration between systems |
---
# Initialize Spring Boot Project
To initialize a new Spring Boot project, go to [start.spring.io](https://start.spring.io/) and select the options that suit you.
![Spring Options](assets/spring-project-init.png)
*Img. 3 — Spring Boot options*
After unpacking the `zip` archive, you'll have this template project:
```bash
.
├── HELP.md
├── mvnw
├── mvnw.cmd
├── pom.xml
├── src
│ ├── main
│ │ ├── java
│ │ │ └── tech
│ │ │ └── codejava
│ │ │ └── store
│ │ │ └── StoreApplication.java
│ │ └── resources
│ │ └── application.properties
│ └── test
│ └── java
│ └── tech
│ └── codejava
│ └── store
│ └── StoreApplicationTests.java
└── target
├── classes
│ ├── application.properties
│ └── tech
│ └── codejava
│ └── store
│ └── StoreApplication.class
├── generated-sources
│ └── annotations
├── generated-test-sources
│ └── test-annotations
├── maven-status
│ └── maven-compiler-plugin
│ ├── compile
│ │ └── default-compile
│ │ ├── createdFiles.lst
│ │ └── inputFiles.lst
│ └── testCompile
│ └── default-testCompile
│ ├── createdFiles.lst
│ └── inputFiles.lst
├── surefire-reports
│ ├── TEST-tech.codejava.store.StoreApplicationTests.xml
│ └── tech.codejava.store.StoreApplicationTests.txt
└── test-classes
└── tech
└── codejava
└── store
└── StoreApplicationTests.class
```
The "heart" of our project is `pom.xml`:
```xml
<?xml version="1.0" encoding="UTF-8" ?>
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.2</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>tech.codejava</groupId>
<artifactId>store</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>store</name>
<description>Store</description>
<url />
<licenses>
<license />
</licenses>
<developers>
<developer />
</developers>
<scm>
<connection />
<developerConnection />
<tag />
<url />
</scm>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
```
Maven uses this file to download dependencies and build our project.
In the `src` folder we have the actual code:
```bash
src
├── main
│ ├── java
│ │ └── tech
│ │ └── codejava
│ │ └── store
│ │ └── StoreApplication.java
│ └── resources
│ └── application.properties
└── test
└── java
└── tech
└── codejava
└── store
└── StoreApplicationTests.java
```
`StoreApplication.java` is the entry point to our application:
```java
package tech.codejava.store;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class StoreApplication {
public static void main(String[] args) {
SpringApplication.run(StoreApplication.class, args);
}
}
```
In the `main` method we have a call to `SpringApplication.run`.
Running `mvn clean install` from the root of our project gives us this result *(output partially reduced)*:
```bash
...
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.542 s -- in tech.codejava.store.StoreApplicationTests
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO]
[INFO] --- jar:3.4.2:jar (default-jar) @ store ---
[INFO] Building jar: /home/fymio/store/target/store-0.0.1-SNAPSHOT.jar
[INFO]
[INFO] --- spring-boot:4.0.2:repackage (repackage) @ store ---
...
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 14.787 s
[INFO] Finished at: 2026-02-19T13:16:47+03:00
[INFO] ------------------------------------------------------------------------
```
Our application built without errors.
---
# Dependency Management
Dependencies are third-party libraries or frameworks we use in our application. For example, to build a web application we need an embedded web server like *Tomcat*, libraries for handling web requests, building APIs, processing JSON data, logging and so on.
In Spring Boot applications, instead of adding multiple individual libraries, we can use a **starter dependency**.
![Spring Boot Starter Web](assets/spring-boot-starter-web.svg)
*Img. 5 — Spring Boot Starter Web*
To use this dependency, copy the following into your `pom.xml`:
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>4.1.0-M1</version>
</dependency>
```
So the `dependencies` section would look like this:
```xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- <version>4.1.0-M1</version> -->
</dependency>
</dependencies>
```
> [!IMPORTANT]
> Notice that the version is commented out. It's a better practice to let Spring Boot decide what version of the dependency to use, as it ensures compatibility across your project.
---
# Controllers
**Spring MVC** stands for *Model View Controller*.
- **Model** is where our application's data lives. It represents the business logic and is usually connected to a database or other data sources. In Spring Boot, the model can be a simple Java class.
- **View** is what the user sees. It's the HTML, CSS or JavaScript rendered in the browser. In Spring MVC, views can be static files or dynamically generated.
- **Controller** is like a traffic controller. It handles incoming requests from the user, interacts with the model to get data and then tells the view what to display.
Let's add a new Java class called `HomeController` at `src/main/java/tech/codejava/store/HomeController.java`:
```java
package tech.codejava.store;
public class HomeController {}
```
To make this a controller, decorate it with the `@Controller` annotation:
```java
package tech.codejava.store;
import org.springframework.stereotype.Controller;
@Controller
public class HomeController {}
```
Now let's add an `index` method. When we send a request to the root of our website, we want this method to be called:
```java
package tech.codejava.store;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class HomeController {
@RequestMapping("/") // this represents the root of our website
public String index() {
return "index.html"; // this returns the view
}
}
```
Now we need to create the view. Add `index.html` at `src/main/resources/static/index.html`:
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>View</title>
</head>
<body>
<h1>Hello world!</h1>
</body>
</html>
```
Let's build and run our application using `mvn spring-boot:run`. From the logs:
```bash
2026-02-19T14:55:23.948+03:00 INFO 36752 --- [store] [ main] o.s.boot.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
```
Our app is up and running at [localhost:8080](http://localhost:8080/).
![Hello world!](assets/hello-world.png)
*Img. 7 — Our app is up and running!*
---
# Configuring Application Properties
Let's take a look at `src/main/resources/application.properties`:
```properties
spring.application.name=store
```
To use this property in our code, we can use the `@Value` annotation. Let's update `HomeController` to print the application name:
```java
package tech.codejava.store;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class HomeController {
@Value("${spring.application.name}")
private String appName;
@RequestMapping("/") // this represents the root of our website
public String index() {
System.out.println("application name = " + appName);
return "index.html"; // this returns the view
}
}
```
After running the application, we can see `store` printed in the terminal:
```bash
...
2026-02-19T15:32:37.507+03:00 INFO 41536 --- [store] [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2026-02-19T15:32:37.509+03:00 INFO 41536 --- [store] [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 2 ms
application name = store
...
```
---
# Dependency Injection
Imagine we're building an E-Commerce application that handles placing orders. When an order is placed, the customer's payment needs to be processed — so `OrderService` depends on a payment service like `StripePaymentService`. We can say that `OrderService` is *dependent on* (or *coupled to*) `StripePaymentService`.
![Depends on/Coupled to relation](assets/depends-on-coupled-to.svg)
*Img. 8 — Depends On/Coupled To relation*
Let's talk about the issues that arise when one class is **tightly coupled** to another.
1. **Inflexibility**`OrderService` can only use `StripePaymentService`. If tomorrow we decide to switch to a different payment provider like PayPal, we would have to modify `OrderService`. Once we change it, it has to be recompiled and retested, which could impact other classes that depend on it.
2. **Untestability** — We cannot test `OrderService` in isolation, because `OrderService` is tightly coupled with `StripePaymentService` and we can't test its logic separately from it.
> [!NOTE]
> The problem here isn't that `OrderService` *depends* on `StripePaymentService` — dependencies are normal in any application. The issue is about *how* the dependency is created and managed.
**Analogy:** Think of a restaurant. A restaurant needs a chef — that's a perfectly normal dependency. If the current chef becomes unavailable, the restaurant can hire another one.
![Restaurant — Chef dependency](assets/restaurant-chef-dependency.svg)
*Img. X — Restaurant — Chef dependency (Normal)*
Now what if we replace "chef" with a specific person: John? Our restaurant is now dependent on *John specifically*. If John becomes unavailable, we can't replace him — the restaurant is in trouble. This is an example of **tight** or **bad coupling**.
![Restaurant — John dependency](assets/restaurant-john-dependency.svg)
*Img. X — Restaurant — John dependency (Bad coupling)*
We don't want `OrderService` to be tightly coupled to a specific payment service like Stripe. Instead, we want it to depend on a `PaymentService` *interface*, which could be Stripe, PayPal, or any other provider. To achieve this we can use the *interface* to decouple `OrderService` from `StripePaymentService`.
![Payment Service as `interface`](assets/payment-service-as-interface.svg)
*Img. X — `PaymentService` as `interface`*
If `OrderService` depends on a `PaymentService` interface, it doesn't know anything about Stripe, PayPal, or any other payment provider. As long as these providers implement `PaymentService`, they can be used to handle payments — and `OrderService` won't care which one is being used.
**Benefits:**
1. If we replace `StripePaymentService` with `PayPalPaymentService`, the `OrderService` class is not affected.
2. We don't need to modify or recompile `OrderService`.
3. We can test `OrderService` in isolation, without relying on the specific payment provider like Stripe.
With this setup, we simply give `OrderService` a particular implementation of `PaymentService`. This is called **dependency injection** — we *inject* the dependency into a class.
![Dependency Injection example](assets/dependency-injection.svg)
*Img. X — Dependency Injection example*
Let's see how it works in our project. Create `OrderService` at `src/main/java/tech/codejava/store/OrderService.java`:
```java
package tech.codejava.store;
public class OrderService {
public void placeOrder() {}
}
```
> [!NOTE]
> In a real project we would need to provide something like `Order order` to this method, but for teaching purposes we won't do that.
Now create `StripePaymentService` in the same directory:
```java
package tech.codejava.store;
public class StripePaymentService {
public void processPayment(double amount) {
System.out.println("=== STRIPE ===");
System.out.println("amount: " + amount);
}
}
```
Let's implement `placeOrder` in `OrderService` using `StripePaymentService`:
```java
package tech.codejava.store;
public class OrderService {
public void placeOrder() {
var paymentService = new StripePaymentService();
paymentService.processPayment(10);
}
}
```
> [!IMPORTANT]
> This is our *before* setup — before we introduced the interface. In this implementation, `OrderService` is **tightly coupled** to `StripePaymentService`. We cannot test `OrderService` in isolation, and switching to another payment provider would require modifying `OrderService`.
Let's fix this. Create a `PaymentService` interface in the same directory:
```java
package tech.codejava.store;
public interface PaymentService {
void processPayment(double amount);
}
```
Modify `StripePaymentService` to implement `PaymentService`:
```java
package tech.codejava.store;
public class StripePaymentService implements PaymentService {
@Override
public void processPayment(double amount) {
System.out.println("=== STRIPE ===");
System.out.println("amount: " + amount);
}
}
```
The recommended way to inject a dependency into a class is via its **constructor**. Let's define one in `OrderService`:
```java
package tech.codejava.store;
public class OrderService {
private PaymentService paymentService;
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
public void placeOrder() {
paymentService.processPayment(10);
}
}
```
Now let's see this in action. Modify `StoreApplication`:
```java
package tech.codejava.store;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class StoreApplication {
public static void main(String[] args) {
// SpringApplication.run(StoreApplication.class, args);
var orderService = new OrderService(new StripePaymentService());
orderService.placeOrder();
}
}
```
Running the application *(output intentionally reduced)*:
```bash
...
=== STRIPE ===
amount: 10.0
...
```
Now let's create a `PayPalPaymentService` in the same directory:
```java
package tech.codejava.store;
public class PayPalPaymentService implements PaymentService {
@Override
public void processPayment(double amount) {
System.out.println("=== PayPal ===");
System.out.println("amount: " + amount);
}
}
```
Now we can switch from `StripePaymentService` to `PayPalPaymentService` in `StoreApplication` — without touching `OrderService` at all:
```java
package tech.codejava.store;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class StoreApplication {
public static void main(String[] args) {
// SpringApplication.run(StoreApplication.class, args);
// var orderService = new OrderService(new StripePaymentService());
var orderService = new OrderService(new PayPalPaymentService());
orderService.placeOrder();
}
}
```
```bash
...
=== PayPal ===
amount: 10.0
...
```
Notice that we didn't change `OrderService`. In *object-oriented programming* this is known as the **Open/Closed Principle**:
> A class should be open for extension and closed for modification.
In other words: we should be able to add new functionality to a class without changing its existing code. This reduces the risk of introducing bugs and breaking other parts of the application.
---
## Setter Injection
Another way to inject a dependency is via a **setter**. In `OrderService`, let's define one:
```java
package tech.codejava.store;
public class OrderService {
private PaymentService paymentService;
public void setPaymentService(PaymentService paymentService) {
this.paymentService = paymentService;
}
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
public void placeOrder() {
paymentService.processPayment(10);
}
}
```
We can use it like this in `StoreApplication`:
```java
package tech.codejava.store;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class StoreApplication {
public static void main(String[] args) {
// SpringApplication.run(StoreApplication.class, args);
// var orderService = new OrderService(new StripePaymentService());
var orderService = new OrderService(new PayPalPaymentService());
orderService.setPaymentService(new PayPalPaymentService());
orderService.placeOrder();
}
}
```
> [!IMPORTANT]
> If you remove the constructor from `OrderService` and forget to call the setter, the application will crash with a `NullPointerException`. Use setter injection only for **optional** dependencies — ones that `OrderService` can function without.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 72 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 60 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 23 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 26 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 54 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 103 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 KiB