├── .gitignore
├── README.md
├── pom.xml
└── src
├── main
└── java
│ └── com
│ └── vivek
│ └── pattern
│ ├── Lens.java
│ └── models
│ ├── Booking.java
│ ├── Movie.java
│ ├── Show.java
│ └── User.java
└── test
└── java
└── com
└── vivek
└── pattern
└── LensTest.java
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled class file
2 | *.class
3 |
4 | # Log file
5 | *.log
6 |
7 | # BlueJ files
8 | *.ctxt
9 |
10 | # Mobile Tools for Java (J2ME)
11 | .mtj.tmp/
12 |
13 | # Package Files #
14 | *.jar
15 | *.war
16 | *.nar
17 | *.ear
18 | *.zip
19 | *.tar.gz
20 | *.rar
21 |
22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
23 | hs_err_pid*
24 |
25 | .idea
26 | *.iml
27 | *.lst
28 | target/
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # lenses-java
2 | Implementation of Lens Pattern in Java
3 |
4 | I've tried to show the usage of Lens pattern with the simple movie booking model.
5 |
6 | ### Build & Execute
7 |
8 | You can run `mvn clean install` to build and execute tests
9 |
10 | > Note: You need to have `Java 11` and `junit 5.4` on the classpath to execute the LensTest
11 |
12 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.vivek
8 | lenses-java
9 | 1.0-SNAPSHOT
10 | lenses-java
11 |
12 |
13 | 5.4.0
14 |
15 |
16 |
17 |
18 | org.junit.jupiter
19 | junit-jupiter-api
20 | ${version.junit}
21 | test
22 |
23 |
24 |
25 |
26 |
27 |
28 | org.apache.maven.plugins
29 | maven-compiler-plugin
30 |
31 | 11
32 | 11
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/main/java/com/vivek/pattern/Lens.java:
--------------------------------------------------------------------------------
1 | package com.vivek.pattern;
2 |
3 | import java.util.function.BiFunction;
4 | import java.util.function.Function;
5 | import java.util.function.UnaryOperator;
6 |
7 | public final class Lens {
8 |
9 | private final Function getter;
10 | private final BiFunction setter;
11 |
12 | public Lens(final Function getter, final BiFunction setter) {
13 | this.getter = getter;
14 | this.setter = setter;
15 | }
16 |
17 | public static Lens of(Function getter, BiFunction setter) {
18 | return new Lens<>(getter, setter);
19 | }
20 |
21 | public B get(final A a) {
22 | return getter.apply(a);
23 | }
24 |
25 | public A set(final A a, final B b) {
26 | return setter.apply(a, b);
27 | }
28 |
29 | public A mod(final A a, final UnaryOperator unaryOperator) {
30 | return set(a, unaryOperator.apply(get(a)));
31 | }
32 |
33 | public Lens compose(final Lens that) {
34 | return new Lens<>(
35 | c -> get(that.get(c)),
36 | (c, b) -> that.mod(c, a -> set(a, b))
37 | );
38 | }
39 |
40 | public Lens andThen(final Lens that) {
41 | return that.compose(this);
42 | }
43 |
44 | }
--------------------------------------------------------------------------------
/src/main/java/com/vivek/pattern/models/Booking.java:
--------------------------------------------------------------------------------
1 | package com.vivek.pattern.models;
2 |
3 | public class Booking {
4 |
5 | public final Show show;
6 | public final Integer numSeats;
7 |
8 | public Booking(Show show, Integer numSeats) {
9 | this.show = show;
10 | this.numSeats = numSeats;
11 | }
12 |
13 | public Booking withShow(Show s) {
14 | return new Booking(s, numSeats);
15 | }
16 |
17 | public Booking withNumSeats(Integer n) {
18 | return new Booking(show, n);
19 | }
20 |
21 | @Override
22 | public String toString() {
23 | return "Booking{" +
24 | "show=" + show +
25 | ", numSeats=" + numSeats +
26 | '}';
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/vivek/pattern/models/Movie.java:
--------------------------------------------------------------------------------
1 | package com.vivek.pattern.models;
2 |
3 | public class Movie {
4 |
5 | public final String title;
6 |
7 | public Movie(String title) {
8 | this.title = title;
9 | }
10 |
11 | public Movie withTitle(String t) {
12 | return new Movie(t);
13 | }
14 |
15 | @Override
16 | public String toString() {
17 | return "Movie{" +
18 | "title='" + title + '\'' +
19 | '}';
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/vivek/pattern/models/Show.java:
--------------------------------------------------------------------------------
1 | package com.vivek.pattern.models;
2 |
3 | import java.time.LocalDateTime;
4 |
5 | public class Show {
6 |
7 | public final Movie movie;
8 | public final LocalDateTime dateTime;
9 |
10 | public Show(Movie movie, LocalDateTime dateTime) {
11 | this.movie = movie;
12 | this.dateTime = dateTime;
13 | }
14 |
15 | public Show withMovie(Movie m) {
16 | return new Show(m, dateTime);
17 | }
18 |
19 | public Show withDateTime(LocalDateTime dt) {
20 | return new Show(movie, dt);
21 | }
22 |
23 | @Override
24 | public String toString() {
25 | return "Show{" +
26 | "movie=" + movie +
27 | ", dateTime=" + dateTime +
28 | '}';
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/com/vivek/pattern/models/User.java:
--------------------------------------------------------------------------------
1 | package com.vivek.pattern.models;
2 |
3 | public class User {
4 |
5 | public final String username;
6 | public final String emailId;
7 | public final Booking booking;
8 |
9 | public User(String username, String emailId, Booking booking) {
10 | this.username = username;
11 | this.emailId = emailId;
12 | this.booking = booking;
13 | }
14 |
15 | public User withUsername(String u) {
16 | return new User(u, emailId, booking);
17 | }
18 |
19 | public User withEmailId(String e) {
20 | return new User(username, e, booking);
21 | }
22 |
23 | public User withBooking(Booking b) {
24 | return new User(username, emailId, b);
25 | }
26 |
27 | @Override
28 | public String toString() {
29 | return "User{" +
30 | "username='" + username + '\'' +
31 | ", emailId='" + emailId + '\'' +
32 | ", booking=" + booking +
33 | '}';
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/test/java/com/vivek/pattern/LensTest.java:
--------------------------------------------------------------------------------
1 | package com.vivek.pattern;
2 |
3 | import com.vivek.pattern.models.Booking;
4 | import com.vivek.pattern.models.Movie;
5 | import com.vivek.pattern.models.Show;
6 | import com.vivek.pattern.models.User;
7 | import org.junit.jupiter.api.Assertions;
8 | import org.junit.jupiter.api.Test;
9 |
10 | import java.time.LocalDateTime;
11 |
12 | public class LensTest {
13 |
14 | @Test
15 | public void testLenses() {
16 | // Basic Lenses
17 | Lens movieTitleLens = Lens.of(s -> s.title, Movie::withTitle);
18 |
19 | Lens showMovieLens = Lens.of(s -> s.movie, Show::withMovie);
20 | Lens showDateTimeLens = Lens.of(s -> s.dateTime, Show::withDateTime);
21 |
22 | Lens bookingShowLens = Lens.of(s -> s.show, Booking::withShow);
23 | Lens bookingSeatsLens = Lens.of(s -> s.numSeats, Booking::withNumSeats);
24 |
25 | Lens userNameLens = Lens.of(s -> s.username, User::withUsername);
26 | Lens userEmailLens = Lens.of(s -> s.emailId, User::withEmailId);
27 | Lens userBookingLens = Lens.of(s -> s.booking, User::withBooking);
28 |
29 | // Lens composition
30 | Lens changeMovieName = userBookingLens.andThen(bookingShowLens).andThen(showMovieLens).andThen(movieTitleLens);
31 | Lens changeShowDateTime = userBookingLens.andThen(bookingShowLens).andThen(showDateTimeLens);
32 | Lens changeBookingSeats = userBookingLens.andThen(bookingSeatsLens);
33 |
34 | // Immutable Structure
35 | User user = new User("johndoe", "jdoe@example.com", new Booking(new Show(new Movie("shawshank redemption"), LocalDateTime.now()), 2));
36 |
37 | // Mutations through lenses
38 | String username = userNameLens.get(user);
39 | Assertions.assertEquals("johndoe", username);
40 |
41 | user = userNameLens.set(user, "janedoe");
42 | Assertions.assertEquals("janedoe", user.username);
43 |
44 | user = changeMovieName.mod(user, s -> "street race");
45 | Assertions.assertEquals("street race", user.booking.show.movie.title);
46 |
47 | user = changeShowDateTime.mod(user, s -> LocalDateTime.of(2021, 10, 14, 5, 30));
48 | Assertions.assertEquals("2021-10-14T05:30", user.booking.show.dateTime.toString());
49 |
50 | user = changeBookingSeats.mod(user, s -> 3);
51 | Assertions.assertEquals(3, user.booking.numSeats);
52 |
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------