├── .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 | --------------------------------------------------------------------------------