Home >Java >javaTutorial >Better utilization of enums under Java 8
In our cloud usage analysis API, formatted analysis data is returned (here refers to generating analysis graphs). Recently, we added a feature that allows users to select a time period (initially only by day). The problem is that the time period of each day in the code is highly coupled...
For example, the following code:
private static List<DataPoint> createListWithZerosForTimeInterval(DateTime from, DateTime to, ImmutableSet<Metric<? extends Number>> metrics) { List<DataPoint> points = new ArrayList<>(); for (int i = 0; i <= Days.daysBetween(from, to).getDays(); i++) { points.add(new DataPoint().withDatas(createDatasWithZeroValues(metrics)) .withDayOfYear(from.withZone(DateTimeZone.UTC) .plusDays(i) .withTimeAtStartOfDay())); } return points; }
Note: Days, Minutes, Hours, Weeks and Months appear at the back of the code part. This code comes from the Joda-Time Java time and date API. Even the names of the methods don't reflect (their respective functionality). These names are firmly tied to the concept of days.
I have also tried using different time periods (such as months, weeks, hours). But I've seen bad switch/cases sneaking around in code.
You need to know that switch/case=sin has penetrated deeply into my heart. I already thought so during my two internship experiences during college. Therefore, I would avoid switch/case at all costs. This is mainly because they violate the open-closed principle. I deeply believe that following this principle is the best practice for writing object-oriented code. I'm not the only one who thinks this way, Robert C. Martin once said:
In many ways, the open-closed principle is the core of object-oriented design. Following this principle will reap tremendous benefits from object-oriented techniques, such as reusability and maintainability1.
I told myself: "We may be able to discover some new features using Java8 to avoid dangerous switch/case situations." Use Java 8's new functions (not that new, but you know what I mean). I decided to use an enum to represent the different available time periods.
public enum TimePeriod { MINUTE(Dimension.MINUTE, (from, to) -> Minutes.minutesBetween(from, to).getMinutes() + 1, Minutes::minutes, from -> from.withZone(DateTimeZone.UTC) .withSecondOfMinute(0) .withMillisOfSecond(0)), HOUR(Dimension.HOUR, (from, to) -> Hours.hoursBetween(from, to).getHours() + 1, Hours::hours, from -> from.withZone(DateTimeZone.UTC) .withMinuteOfHour(0) .withSecondOfMinute(0) .withMillisOfSecond(0)), DAY(Dimension.DAY, (from, to) -> Days.daysBetween(from, to).getDays() + 1, Days::days, from -> from.withZone(DateTimeZone.UTC) .withTimeAtStartOfDay()), WEEK(Dimension.WEEK, (from, to) -> Weeks.weeksBetween(from, to).getWeeks() + 1, Weeks::weeks, from -> from.withZone(DateTimeZone.UTC) .withDayOfWeek(1) .withTimeAtStartOfDay()), MONTH(Dimension.MONTH, (from, to) -> Months.monthsBetween(from, to).getMonths() + 1, Months::months, from -> from.withZone(DateTimeZone.UTC) .withDayOfMonth(1) .withTimeAtStartOfDay()); private Dimension<Timestamp> dimension; private BiFunction<DateTime, DateTime, Integer> getNumberOfPoints; private Function<Integer, ReadablePeriod> getPeriodFromNbOfInterval; private Function<DateTime, DateTime> getStartOfInterval; private TimePeriod(Dimension<Timestamp> dimension, BiFunction<DateTime, DateTime, Integer> getNumberOfPoints, Function<Integer, ReadablePeriod> getPeriodFromNbOfInterval, Function<DateTime, DateTime> getStartOfInterval) { this.dimension = dimension; this.getNumberOfPoints = getNumberOfPoints; this.getPeriodFromNbOfInterval = getPeriodFromNbOfInterval; this.getStartOfInterval = getStartOfInterval; } public Dimension<Timestamp> getDimension() { return dimension; } public int getNumberOfPoints(DateTime from, DateTime to) { return getNumberOfPoints.apply(from, to); } public ReadablePeriod getPeriodFromNbOfInterval(int nbOfInterval) { return getPeriodFromNbOfInterval.apply(nbOfInterval); } public DateTime getStartOfInterval(DateTime from) { return getStartOfInterval.apply(from); } }
With the enumeration, I was able to easily modify the code to allow the user to specify a time period for the chart data points.
The original call was like this:
for (int i = 0; i <= Days.daysBetween(from, to).getDays(); i++)
became the call like this:
for (int i = 0; i < timePeriod.getNumberOfPoints(from, to); i++)
The Usage Analytics service code that supports the getGraphDataPoints call has been completed and supports time periods. It is worth mentioning that it takes into account the open-closed principle I said before.