CS2340: Objects and Design

Date

Jan. 2023 — May. 2023

Group

Yiwen Zhao

Xinyi Cao

Yimeng Jiang

Eric Dong

Mingming Yang

Tech Stack

Practices

Android Studio

Git/GitHub

Object-Oriented Programming

Agile/Scrum

Test-Driven Development

Version Control

Use Case Modeling

Software Architecture

Project Description

The project theme that were given to us is a road-crossing Android mobile game. In the game, the character starts at the bottom and work its way up the screen, towards the goal tiles. The objective of the game is to get to the goal on the other side of the game map. In doing so, the character must cross both roads and rivers while avoiding all obstacles.

Sprint 1: Use Cases & Domain Model

In Sprint 1, our focus was on initial analysis and modeling stages. We crafted a comprehensive use case diagram, capturing the interactions between users and the system, which served as a blueprint for the project's functionality. Additionally, we constructed a domain model that captured the key entities and their relationships within the system, laying a solid foundation for the ensuing development phases.

Sprint 2: User Stories & System Sequence Diagrams (SSD)

Embracing Agile/Scrum practices, we maintained our collaborative and iterative approach, which enabled us to stay responsive and adaptable throughout the development cycle. One of the highlights of this sprint was the formulation of clear and concise user stories. Through rigorous brainstorming sessions and team discussions, we meticulously identified the needs and requirements of the end-users, ensuring that our application would truly cater to their expectations.

Additionally, to visualize the flow of interactions between users and the system, we diligently crafted comprehensive System Sequence Diagrams (SSDs). These diagrams allowed us to outline the sequence of events and actions that would occur as users interacted with the application, aiding us in identifying potential areas for improvement and refining the user experience.

Sprint 3: Sequence Diagram & Design Class Diagram

In this phase, our primary focus was on designing the dynamic behavior of our application, and to achieve this, we invested substantial effort in crafting comprehensive Sequence Diagrams. These diagrams provided an insightful visualization of the interactions and message exchanges between various components of the system, allowing us to identify potential bottlenecks and optimize the application's performance.

Parallel to the Sequence Diagrams, we diligently worked on creating a Design Class Diagram. This diagram served as a blueprint for the class structure of our application, delineating the relationships, attributes, and methods of each class. Through meticulous design, we aimed to build a coherent and efficient system architecture that facilitated extensibility and modifiability.

Sprint 4: SOLID & GRASP

We wanted to craft clean and maintainable code. We delved deep into SOLID principles - Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, and Dependency Inversion. These guiding principles helped us design a flexible and extensible codebase, enabling us to adapt to evolving requirements and minimize code duplication.

public abstract class Tile extends android.appcompat.widget. AppCompatImageView {
   private int x; private int y;

   /** 4-argument Tile constructor. ...*/
   public Tile(Context context, int resID, int x, int y) {
      super (context);
      setImageResource (resID);
      this.x = x;
      this.y = y;
   }

   @Override
   public float get() { return x; }

   @Override
   public float getY() { return y; }

   ** This class represents a safe tile. The player's cannot collide while on this tile. */
   public class SafeTile extends Tile {
      /** 3-arqument SafeTile constructor. ...*/
      public SafeTile (Context context, int x, int y) {
         super (context, R.drawable.safe_tile, x, y);
      }
   }
** This class represents a Ferrari vehicle. public class Ferrari extends Vehicle
public class Ferrari extends Vehicle {
   /** 3-argument Ferrari constructor. ...*/
   public Ferrari (Context context, Direction direction, int row) {
      super (context, direction, row,
             direction == Direction.LEFT ? R.drawable.ferrari_travel_left
             : R.drawable.ferrari_travel_right);
   }

   @Override
   public void move() {
      x += (direction == Direction.LEFT) ? -20 : 20;
      super. move ();
   }
}

** This class represents a Bike vehicle.
public class Bike extends Vehicle {
   /** 3-argument Bike constructor. ...*/
   public Bike(Context context, Direction direction, int row) {
      super (context, direction, row,
             direction == Direction.LEFT ? R.drawable.bike_travel_left
                                         : R.drawable.bike_travel_right);
   }

   @Override
   public void move() {
      × += (direction == Direction.LEFT) ? -12 : 12;
      super.move();
   }
}

** This class represents a Cybertruck vehicle.
public class Cybertruck extends Vehicle {
   /** 3-argument Cybertruck constructor. ...*/
   public Cybertruck(Context context, Direction direction, int row) {
      super (context, direction, row,
             direction == Direction.LEFT ? R.drawable.cybertruck_expanded_travel_left
           : R.drawable.cybertruck_expanded_travel_right);
   }

@Override
public void move() {
   x += (direction == Direction.LEFT) ? -16 : 16;
   super. move ();
}

In tandem with SOLID principles, we harnessed the power of GRASP - General Responsibility Assignment Software Patterns - to solve complex design challenges. By employing GRASP patterns such as Creator, Controller, and Polymorphism, we achieved a cohesive and intuitive system architecture that efficiently allocated responsibilities among classes and objects.

Polymorphism

Polymorphism states that objects of different classes should be able to be used interchangeably. This promotes flexibility and promotes reuse of code. The SafeTile class is a subclass of the Tile class and it inherits the properties and behavior of the Tile class. However, it also has its own unique properties and behavior, such as the update() method. Furthermore, the Tile class is an abstract class, which cannot be instantiated directly. Instead, it serves as a base class for other classes to inherit from. This allows for the creation of multiple subclasses that share behavior and properties but can have unique behavior and properties as well.

Liskov Substitution Principle

Classes such as Ferrari, Bike, and Cybertruck are good examples for the Liskov Substitution Principle. They are substitutable for their base class - Vehicle. Since each class completely fulfilled all behaviors of the parent class, they can be interchangeable (can use any of the vehicles in place of each other and the functionality is still correct). Every subclass should be able to replace its parent class without affecting the correctness of the program.

Single Responsibility Principle (SRP)

Classes such as Ferrari, Bike, and Cybertruck are good examples for Single Responsibility Principle. Each class is only responsible for one thing and has only one reason to change. This is beneficial because changes related to the class’s responsibility are fairly isolated from each other. They can be used in other parts without exposing unrelated functionality.

Sprint 5: Code Smells

During Sprint 5, we honed our software development expertise by diving into the critical topic of code smells. Recognizing the significance of writing clean, maintainable, and efficient code, we meticulously conducted code reviews and performed thorough analyses to identify instances of code smells within our application. Code smells are indicative of design weaknesses or potential issues that can lead to suboptimal system performance and reduced code comprehensibility. By addressing these code smells, we aimed to enhance the overall quality of our codebase and streamline the development process.

Throughout this sprint, we employed various techniques to identify and understand different types of code smells, such as duplicated code, long methods, and excessive complexity. Upon detection, we collaboratively brainstormed and implemented appropriate refactoring techniques, ensuring that our code adhered to best practices and remained in line with the SOLID and GRASP principles we had previously embraced.

Bloaters - Long Method

The onCreate() method is really long and certainly exceeds ten lines of code. It contains multiple functionalities such as selecting the difficulty of the game, initializing movement buttons, generating the game board, and generating the vehicles. The method is therefore unreadable and hard to maintain.

We used the Extract Method solution to separate these functionalities and give them new names. Each method performs only one responsibility and we simply call these functions when we need them.

Final Implementation

Throughout the development process, version control using Git/GitHub played a pivotal role in ensuring seamless collaboration and code management. With each team member working on different features and improvements, version control allowed us to track changes, resolve conflicts, and maintain a clean and coherent codebase.

Using Java in Android Studio as our primary programming language, we built an engaging and immersive game that captivated our target audience. Java's robustness and versatility empowered us to implement intricate game mechanics, create intuitive user interfaces, and handle complex data structures with ease. Android Studio provided a comprehensive development environment, equipped with powerful tools and features that streamlined the development process, allowing us to focus on perfecting the game's functionality and user experience.