Share
Explore

Java Assignment 1: DUE JULY 23 Making a SOLID Java Program

References:

How to use GITHUB

Starting Assignment 1: Creating a Soccer League System
Learning Outcomes for Assignment 1:
Learning how to Design a PROGRAM using Unified Model Language.
Learn good software engineering principles to see how code should be put together in an effective and understandable way.
Learn the software coding tool of GITHUB. (We will get everyone setup with GITHUB in the July 17 class).

How to get started:

STEP 1: We start by designing our program by drawing pictures.
These pictures have a special name. They are called UML Unified Modeling Language.
In UML:
We draw boxes on the paper for CLASSES.
In Java: Code is put into a Container called Classes.
What 2 kinds of MEMBERS can we have in Classes:
Data Attributes: “knowing” things
Methods: Code is put into methods. The methods are the “doing” things.
What is the purpose of writing classes in Java? To model stuff in the real world.
What stuff? All of it!
Things: In our assignment, we will be modeling a Soccer team.
Relationships:
calendar event: connect between place, activity, time, meeting room
soccer game: relationship between 2 teams and their score.
STEP 2: We start by designing our program by drawing pictures.
How we do software engineering:
We “forward engineering” our UML Diagram into Java Code.
We can also “reverse regineering” code back up into a UML Diagram.
Roundtrip engineering: Moving between the design and the implementation phases.
The purpose of this Assignment is to understand the object-oriented design in Java, focusing on class interaction via method calls, shared field composition, and SOLID principles.
For today's warm-up activity, we're going to develop a Java Application to track the scores of six soccer teams. It will be an object-oriented application, featuring a data container as an abstract data type (ADT). In creating this application, we will strive to adhere to the SOLID principles of object-oriented design.
This application will have three main classes:
Team - Represents a soccer team with a name and a score.
GamePlay - Simulates a match between two teams and updates their scores.
LeaderBoard - Tracks the scores of all teams and reports the winners.
You will find detailed comments within the code to understand the processes better.
Class 1: Team

/**
* Class representing a soccer team.
* This class is our Data Container ADT.
*/
public class Team {

private String name; // Name of the team.
private int score; // Score of the team.

/**
* Constructor for Team.
* @param name name of the team.
*/
public Team(String name) {
this.name = name;
this.score = 0; // Default score is 0.
}

/**
* Get the name of the team.
* @return name of the team.
*/
public String getName() {
return name;
}

/**
* Get the score of the team.
* @return score of the team.
*/
public int getScore() {
return score;
}

/**
* Update the score of the team.
* @param score new score to be added.
*/
public void updateScore(int score) {
this.score += score;
}
}

Class 2: GamePlay

import java.util.Random;

/**
* Class to simulate a soccer game between two teams.
*/
public class GamePlay {

private Random random = new Random(); // To simulate the scoring.

/**
* Simulate a game between two teams and update their scores.
* @param team1 first team.
* @param team2 second team.
*/
public void playGame(Team team1, Team team2) {
int team1Score = random.nextInt(5); // Random score for team 1.
int team2Score = random.nextInt(5); // Random score for team 2.

// Update the team scores.
team1.updateScore(team1Score);
team2.updateScore(team2Score);
}
}

Class 3: LeaderBoard

import java.util.Arrays;

/**
* Class to maintain a leaderboard of all teams.
*/
public class LeaderBoard {

private Team[] teams; // Array to store all teams.

/**
* Constructor for LeaderBoard.
* @param teams array of all teams.
*/
public LeaderBoard(Team[] teams) {
this.teams = teams;
}

/**
* Print the leaderboard.
*/
public void printLeaderBoard() {
// Sort the teams in descending order of scores.
Arrays.sort(teams, (team1, team2) -> team2.getScore() - team1.getScore());

System.out.println("Leaderboard:");
for (Team team : teams) {
System.out.println("Team " + team.getName() + ": " + team.getScore() + " points");
}
}
}
Let's analyze the meaning of this line:
Arrays.sort(teams, (team1, team2) -> team2.getScore() - team1.getScore());

Introduction to Java Lambda Expressions
Lambda expressions, introduced in Java 8, are a new and important feature of Java which was included in it to enable functional programming. A lambda expression is a short block of code which takes in parameters and returns a value. Lambda expressions are similar to methods, but they do not need a name and they can be implemented right in the body of a method.
Here is a simple example of a lambda expression:
javaCopy code
(x, y) -> x + y

This lambda expression takes two parameters (x and y) and returns their sum. The parameters are on the left side of the -> symbol, and the body of the lambda (which computes the result) is on the right.
What problems do Lambda expresses solve?
Prior to Java 8, implementing simple functionality often required creating anonymous inner classes, or even creating whole new classes, even for very simple operations. For example, if you wanted to create a new thread, you had to provide a Runnable as an argument to the Thread constructor, often using an anonymous inner class. With lambda expressions, the same operation can be done with much less code:
// Using an anonymous inner class
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello from the thread!");
}
}).start();

// Using a lambda expression
new Thread(() -> System.out.println("Hello from the thread!")).start();

Lambda expressions also make it much easier to work with functional interfaces. A functional interface is an interface that declares exactly one abstract method. Since a lambda expression is essentially a way to provide an implementation for a method without needing a class, they can be used anywhere a functional interface is expected. This is the basis for many features of the Java 8 functional programming model, including streams and the Optional class.
Lastly, lambda expressions can make your code easier to read and understand, because they allow you to focus on the operation being performed rather than on the details of how to instantiate a certain class or interface.
Conclusion
Lambda expressions in Java have greatly simplified the syntax and improved the readability of Java code. They have also enabled Java to make use of functional programming features, which has made the language more expressive and powerful. With the addition of lambda expressions, Java programmers can write more concise and readable code, reducing the time and effort required to implement their solutions.

This line of code is using the sort method from the Arrays class provided by the Java standard library. The sort method is an example of a sorting algorithm, which orders the elements of an array according to the provided criteria.
The Arrays.sort function is a static method that can be used to sort all kinds of arrays in Java. In this case, it is used to sort an array of Team objects. The specific version of sort used here is designed to sort objects according to a custom comparison rule.
The line:
Arrays.sort(teams, (team1, team2) -> team2.getScore() - team1.getScore());
is using a feature of Java 8 and onwards, known as lambda expressions.

Lambda expressions are a way to create anonymous methods (methods without a declared name) directly in your code.

They are often used when passing a function as a parameter to another function.
In this context, (team1, team2) -> team2.getScore() - team1.getScore() is a lambda expression that defines a Comparator for Team objects.
The lambda expression takes two Team objects as input, team1 and team2. The arrow -> separates the parameters from the body of the lambda expression. The body of the expression, team2.getScore() - team1.getScore(), is what will be executed when the lambda expression is called.

This expression is used to determine the order of team1 and team2. If the result is positive, team1 is sorted before team2. If it's negative, team1 is sorted after team2. If the result is zero, it means that team1 and team2 have the same score, and their original order is preserved.

The entire expression team2.getScore() - team1.getScore() is essentially saying, "sort the teams in descending order based on their scores". This means that teams with higher scores will appear earlier in the sorted teams array than teams with lower scores. This makes sense in the context of a leaderboard where you want teams with the highest scores at the top.

So in summary, this line of code sorts the teams array in-place, arranging the Team objects in descending order of their scores.

We're achieving encapsulation by defining the class fields as private and providing public getter and setter methods. This is consistent with the 'Open Closed Principle' (O) and 'Information Hiding' principle of SOLID.
Our GamePlay class is dependent on the Team class but not on the concrete implementation of it, following the 'Dependency Inversion Principle' (D).
The 'Single Responsibility Principle' (S) is also being followed, as each class has its own unique purpose.
Finally, our classes are not doing more than they need to (following the 'Interface Segregation Principle' (I) and the 'Liskov Substitution Principle' (L) is upheld since we do not have any subclasses in this example.
Main Application:

public class Main {
new Team("Team 4"),
new Team("Team 5"),
new Team("Team 6")

public static void main(String[] args) {
// Initialize six teams.
Team[] teams = {
new Team("Team 1"),
new Team("Team 2"),
new Team("Team 3")
};

// Initialize game play.
GamePlay gamePlay = new GamePlay();

// Play games between all teams.
for (int i = 0; i < teams.length; i++) {
for (int j = i + 1; j < teams.length; j++) {
gamePlay.playGame(teams[i], teams[j]);
}
}

// Initialize leaderboard and print it.
LeaderBoard leaderboard = new LeaderBoard(teams);
leaderboard.printLeaderBoard();
}
}



SOLID principles.

SOLID is a mnemonic acronym introduced by Robert C. Martin (also known as Uncle Bob) in his work on object-oriented design and programming. SOLID stands for five principles that help to design software that is easy to maintain, understand, and extend. These principles can make your code more flexible, robust, and reusable.
1. Single Responsibility Principle (SRP)
The Single Responsibility Principle states that "a class should have one, and only one, reason to change." This principle is about cohesion in classes and implies that a class should only serve one purpose or handle a single part of the functionality. When a class has more than one responsibility, it becomes coupled. A change to one responsibility results in a modification of the other responsibility.
For instance, a class that manages user accounts on a system should only be concerned with user account management tasks such as creating, updating, or deleting user accounts. It shouldn't be responsible for logging or displaying user account information.
2. Open-Closed Principle (OCP)
The Open-Closed Principle states that "software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification." The idea is to write our modules so that they can be extended, without requiring modification. In other words, we should be able to change the behavior of a module without changing its source code.
A classic technique that aligns with the OCP is to use abstract base classes (or interfaces in Java), which can then be implemented by any number of concrete classes.
3. Liskov Substitution Principle (LSP)
The Liskov Substitution Principle states that "if S is a subtype of T, then objects of type T may be replaced with objects of type S, without altering any of the desirable properties of the program." This principle is closely related to the concept of polymorphism in object-oriented programming.
This principle is named after Barbara Liskov who introduced it in a 1987 conference keynote. In practical terms, it means that an instance of a class should be replaceable with an instance of a subclass without affecting the correctness of the program.
4. Interface Segregation Principle (ISP)
The Interface Segregation Principle states that "clients should not be forced to depend upon interfaces they do not use." This principle deals with the disadvantages of "fat" interfaces, i.e., interfaces with many methods. Classes that implement such interfaces are often forced to provide implementations that do nothing but throw exceptions, which leads to inefficiency and confusion.
In practical terms, it's better to have several small, specific interfaces than a single, general-purpose, "do-it-all" interface.
5. Dependency Inversion Principle (DIP)
The Dependency Inversion Principle states that "high-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions." In other words, you should depend upon abstractions, not upon concrete types.
This principle allows for decoupling. The principle influences how to structure your classes and their relationships and is essential for building robust and scalable systems.
In summary, the SOLID principles are guidelines that can be used to improve your software design. They help you to create a design that is robust, maintainable, and easy to understand and modify. By adhering to these principles, you can improve the quality of your code and make it easier for yourself and others to understand and maintain.
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.