image

Cracking Core Java Interviews 3rd Edition

A Comprehensive Guide to Crack Core Java Interviews in Investment Banks, HealthCare IT & Startups. It covers Core Java, Algorithms, Data Structures, Concurrency, Hibernate and Spring MVC.

Specifically for investment banking domain, healthcare IT and product companies i.e. UBS, RBS, Blackrock, Morgan Stanley, JP Morgan, Nomura, Barclays, Citibank, Markit, Bank of America, Goldman Sachs and other companies i.e. Global Logic, Adobe, hCentive, Edifecs, Expedia, Infosys, TCS, Sapient, Wipro, HCL etc.
Free Chapters PDF
1131 downloads
Buy Full PDF ₹250
3rd Edition
Last Updated : Thursday, April 14, 2016 5:01:48 PM IST Total Page Hits 3972

Design Metro Smart Card System for Delhi using Java

Requirement Scope

  1. Develop an API to calculate total footfall on a given station ( swipe in + swipe out)
  2. API to generate per card report on demand i.e. print all journey details for a given smart card - source station, destination station, date & time of travel, balance, fare, etc.

This Design is not production ready

The motive of this exercise is to evaluate candidate skills rather than developing a production ready software for Delhi Metro. In a real production environment, the technology stack may differ significantly. In real life scenario, for example,

  1. lot of hardware interaction will be required for smart card sensor devices connectivity with servers.
  2. caching needs to be implemented for quick swipe in/ swipe out logic.
  3. there will be a RDBMS for storing all the transactional data (Traveler Information, Smart Card, Journey Details, Station information, etc), or there may be NoSQL database as well.
  4. we will probably use a framework like spring/hibernate for seamless development and thus not rewriting the boilerplate code again and again.
  5. real production application may be hosted on a cloud to meet changing infrastructural requirements of the software, for example morning and evening office hours could need much more processing power than off hours. Thus some kind of Elastic Computing solution will be required in real scenario.

Considering it as a purely skills evaluation excercise, Let’s move on to the solution now.

Identifying the entities

  1. Traveler or the User
  2. Smart Card
  3. Station
  4. Journey Details (CardTrx)

Starting with TestCases

Lets start with all the Testcases that cover our scope of requirements for desiigning this application.


import org.hamcrest.CustomMatcher;
import org.junit.Before;
import org.junit.Test;

import java.time.LocalDateTime;
import java.time.Month;
import java.util.List;

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItemInArray;
import static org.junit.Assert.assertThat;

/**
 * Created by munish on 4/9/2016.
 */
public class MetroServiceTest {
    private MetroService metroService = new MetroService();

    private SmartCard card;

    @Before
    public void setUp() throws Exception {
        card = new SmartCard();
        card.setId(1);
        card.setBalance(100);
        card.setTraveller(new Traveller(1L, "Munish"));
    }

    @Test
    public void testCalculateFootFallForStation() throws Exception {
        metroService.swipeIn(card, Station.A1, LocalDateTime.of(2016, Month.APRIL, 8, 18, 25));
        metroService.swipeOut(card, Station.A6, LocalDateTime.of(2016, Month.APRIL, 8, 18, 35));

        metroService.swipeIn(card, Station.A6, LocalDateTime.of(2016, Month.APRIL, 10, 19, 05));
        metroService.swipeOut(card, Station.A10, LocalDateTime.of(2016, Month.APRIL, 10, 19, 15));

        assertThat("FootFall for station A6 should be 2", metroService.calculateFootFall(Station.A6), equalTo(2));
        assertThat("FootFall for station A1 should be 1", metroService.calculateFootFall(Station.A1), equalTo(1));
        assertThat("FootFall for station A10 should be 1", metroService.calculateFootFall(Station.A10), equalTo(1));
    }

    @Test
    public void testCardReport() throws Exception {
        metroService.swipeIn(card, Station.A1, LocalDateTime.of(2016, Month.APRIL, 8, 18, 25));
        metroService.swipeOut(card, Station.A6, LocalDateTime.of(2016, Month.APRIL, 8, 18, 35));

        metroService.swipeIn(card, Station.A6, LocalDateTime.of(2016, Month.APRIL, 10, 19, 05));
        metroService.swipeOut(card, Station.A10, LocalDateTime.of(2016, Month.APRIL, 10, 19, 15));
        final List<CardTrx> trxs = metroService.cardReport(card);

        assertThat("There should be 2 trxs for this card", trxs.size(), equalTo(2));
        assertThat("One of the Trx should be charged 35", trxs.toArray(new CardTrx[0]), hasItemInArray(new CustomMatcher<CardTrx>("Fare shall be 35") {
            @Override
            public boolean matches(Object o) {
                CardTrx trx = (CardTrx) o;
                return trx.getFare() == 35.0 && trx.getFareStrategyUsed() instanceof WeekdayFareStrategy && trx.distance == 5;
            }
        }));

        assertThat("Other Trx should be charged 35", trxs.toArray(new CardTrx[0]), hasItemInArray(new CustomMatcher<CardTrx>("Fare shall be 35") {
            @Override
            public boolean matches(Object o) {
                CardTrx trx = (CardTrx) o;
                return trx.getFare() == 22.0 && trx.getFareStrategyUsed() instanceof WeekendFareStrategy && trx.distance == 4;
            }
        }));
    }

    @Test(expected = MinimumCardBalanceException.class)
    public void testMinimumBalanceAtSwipeIn() throws Exception {
        card.setBalance(1);
        metroService.swipeIn(card, Station.A1, LocalDateTime.of(2016, Month.APRIL, 8, 18, 25));
    }

    @Test(expected = InsufficientCardBalance.class)
    public void testSufficientBalanceAtSwipeOut() throws Exception {
        card.setBalance(10);
        metroService.swipeIn(card, Station.A1, LocalDateTime.of(2016, Month.APRIL, 8, 18, 25));
        metroService.swipeOut(card, Station.A6, LocalDateTime.of(2016, Month.APRIL, 8, 18, 35));
    }
}

Handling different FareStrategy for Weekday and Weekend

As a part of requirement, we need to implement different fare strategy for weekdays and weekends. Fare would be less on weekends compared to weekdays since traffic is lesser on weekends.
med

FareStrategy Interface
FareStrategy interface will have getFarePerStation method that will return fare rate for different implementations.

public interface FareStrategy {
    String getName();

    double getFarePerStation();
}

Now we will provide FareStrategy implementation for Weekdays in below class.

WeekdayFareStrategy

public class WeekdayFareStrategy implements FareStrategy {
    @Override
    public String getName() {
        return WeekdayFareStrategy.class.toGenericString();
    }

    @Override
    public double getFarePerStation() {
        return 7;
    }
}

Weekend Fare would be different, so we will create a new implementation for FareStrategy for the weekends.

WeekendFareStrategy

public class WeekendFareStrategy implements FareStrategy {

    @Override
    public String getName() {
        return WeekendFareStrategy.class.toGenericString();
    }

    @Override
    public double getFarePerStation() {
        return 5.5;
    }
}

We will create a Factory Class for creating appropriate instance of FareStrategy based on datetime

FareStrategyFactory Class (Factory Design Pattern)

public class FareStrategyFactory {
    static final FareStrategy weekendStrategy = new WeekendFareStrategy();
    static final FareStrategy weekdayStrategy = new WeekdayFareStrategy();

    public static FareStrategy getFareStrategy(LocalDateTime localDateTime) {
        if (localDateTime.getDayOfWeek() == DayOfWeek.SATURDAY || localDateTime.getDayOfWeek() == DayOfWeek.SUNDAY) {
            return weekendStrategy;
        } else {
            return weekdayStrategy;
        }
    }
}

Exceptions for Metro Services

There are two exceptional scenarios given to us in this requirement.

  1. MinimumCardBalanceException - This exception will be raised if Traveller does not have minimum balance of Rs 5. at the time of Swipe In.
  2. InsufficientCardBalance - it will be raised if user does not have sufficient balance at the time of swipe out as per Journey charges.

Below is the UML class diagram for our Exception Hierarchy

med

Business Domain Objects

As we covered earlier, there are 4 Domain Objects (Traveller, SmartCard, Station, CardTrx) which will store domain related information in some data store. For brevity we will use InMemoryCardTrxRepository to store the data. In a production like environment, we might go for Hibernate with RDBMS or Spring with NoSql Data Store.

public class Traveller {
    long id;
    String name;

 // Getters and Setters not shown for brevity
}

public class SmartCard {
    long id;

    Traveller traveller;
    double balance;

 // Getters and Setters not shown for brevity
}

public enum Station {
    A1, A2, A3, A4, A5, A6, A7, A8, A9, A10;

    public int distance(Station other) {
        return Math.abs(other.ordinal() - this.ordinal());
    }
}


public class CardTrx {
    long id;

    SmartCard card;

    Station source;
    Station destination;

    int distance;

    LocalDateTime startTime;
    LocalDateTime endTime;

    double balance;
    double fare;

    FareStrategy fareStrategyUsed;

 // Getters and Setters not shown for brevity
}

DAO Layer and Services

public class InMemoryCardTrxRepository {

    private ConcurrentMap<SmartCard, CardTrx> transientTrxStore = new ConcurrentHashMap<>();
    private ConcurrentMap<SmartCard, List<CardTrx>> completedTrxStore = new ConcurrentHashMap<>();


    public void addCompletedTrx(SmartCard card, CardTrx trx){
        completedTrxStore.putIfAbsent(card, new ArrayList<>());
        completedTrxStore.get(card).add(trx);
    }

    public void addTransientTrx(SmartCard card, CardTrx trx){
        transientTrxStore.put(card, trx);
    }

    public CardTrx getTransientTrx(SmartCard card) {
        return transientTrxStore.remove(card);
    }

    public List<CardTrx> getCompletedTrxs(SmartCard card) {
        return completedTrxStore.getOrDefault(card, Collections.emptyList());
    }
}
public class MetroService {
    private ConcurrentMap<Station, AtomicInteger> stationFootFall = new ConcurrentHashMap<>();

    private InMemoryCardTrxRepository trxRepository = new InMemoryCardTrxRepository();
    private FareCalculator fareCalculator = new FareCalculator();

    public void swipeIn(SmartCard card, Station source, LocalDateTime dateTime) {
        if (card.getBalance() < 5.5) {
            throw new MinimumCardBalanceException("Minimum balance of Rs 5.5 is required at Swipe In");
        }
        stationFootFall.putIfAbsent(source, new AtomicInteger());
        stationFootFall.get(source).incrementAndGet();
        CardTrx trx = new CardTrx();
        trx.setSource(source);
        trx.setCard(card);
        trx.setStartTime(dateTime);
        trxRepository.addTransientTrx(card, trx);
    }

    public void swipeOut(SmartCard card, Station destination, LocalDateTime dateTime) {
        stationFootFall.putIfAbsent(destination, new AtomicInteger());
        stationFootFall.get(destination).incrementAndGet();
        CardTrx trx = trxRepository.getTransientTrx(card);
        trx.setDestination(destination);
        trx.setEndTime(dateTime);
        trx.setDistance(destination.distance(trx.source));
        trx.setFare(fareCalculator.getFare(trx.source, trx.destination, dateTime));
        if (trx.getFare() > card.getBalance()) {
            throw new InsufficientCardBalance("Insufficient balance at Swipe Out, Please recharge card and try again");
        }
        trx.setFareStrategyUsed(FareStrategyFactory.getFareStrategy(dateTime));
        trx.setBalance(card.getBalance() - trx.getFare());
        card.setBalance(card.getBalance() - trx.getFare());
        trxRepository.addCompletedTrx(card, trx);
    }


    public int calculateFootFall(Station station) {
        return stationFootFall.getOrDefault(station, new AtomicInteger(0)).get();
    }

    public List<CardTrx> cardReport(SmartCard card) {
        List<CardTrx> trxs = trxRepository.getCompletedTrxs(card);
        trxs.forEach(trx -> {
            System.out.println("trx = " + trx);
        });
        return trxs;
    }
}
public class FareCalculator {

    public double getFare(Station source, Station destination, LocalDateTime localDateTime) {
        FareStrategy fareStrategy = FareStrategyFactory.getFareStrategy(localDateTime);
        int distance = source.distance(destination);

        double fare = distance * fareStrategy.getFarePerStation();

        return fare;
    }

}

TODO

More details will be added later on!




Similar Articles

1. Synechron Java Interview Questions

Collection of Java Interview Questions (Core Java, Spring, database and other concepts) for Synechron in banking and finance domain

2. Design Metro Smart Card System for Delhi using Java

Design a program in Java for Metro Smart Card System in Delhi. Evaluation criteria will be based on code completeness, code structure and quality, modularity, usage of OO principles, choice of data structure and unit tests.

3. What does volatile keyword do in a multi-threading environment

volatile keyword helps programmers write thread safe program