diff --git a/README.md b/README.md index a1561b4836eb..19ccf64b7863 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,8 @@ -# Design pattern samples in Java. + + +# Design pattern samples in Java [![Join the chat at https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) @@ -7,26 +11,18 @@ src="https://scan.coverity.com/projects/5634/badge.svg"/> - + + + +# Table of Contents - Introduction - - List of Design Patterns - - Creational Patterns - - Structural Patterns - - Behavioral Patterns - - Concurrency Patterns - - Presentation Tier Patterns - - Business Tier Patterns - - Architectural Patterns - - Integration Patterns - - Idioms + - How to contribute - Frequently Asked Questions - - How to contribute - - Versioning - Credits - License - -## Introduction + +# Introduction [↑](#top) Design patterns are formalized best practices that the programmer can use to solve common problems when designing an application or system. @@ -38,119 +34,11 @@ Reusing design patterns helps to prevent subtle issues that can cause major problems, and it also improves code readability for coders and architects who are familiar with the patterns. -## List of Design Patterns [↑](#top) -### Creational Patterns [↑](#top) - -Creational design patterns abstract the instantiation process. They help make a -system independent of how its objects are created, composed, and represented. - -* [Abstract Factory](#abstract-factory) -* [Builder](#builder) -* [Factory Method](#factory-method) -* [Prototype](#prototype) -* [Property](#property) -* [Singleton](#singleton) -* [Step Builder](#step-builder) -* [Multiton](#multiton) -* [Object Pool](#object-pool) - -### Structural Patterns [↑](#top) - -Structural patterns are concerned with how classes and objects are composed to -form larger structures. - -* [Adapter](#adapter) -* [Bridge](#bridge) -* [Composite](#composite) -* [Decorator](#decorator) -* [Facade](#facade) -* [Flyweight](#flyweight) -* [Proxy](#proxy) -* [Service Locator](#service-locator) -* [Servant](#servant) -* [Event Aggregator](#event-aggregator) - -### Behavioral Patterns [↑](#top) - -Behavioral patterns are concerned with algorithms and the assignment of -responsibilities between objects. - -* [Chain of responsibility](#chain-of-responsibility) -* [Command](#command) -* [Interpreter](#interpreter) -* [Iterator](#iterator) -* [Mediator](#mediator) -* [Memento](#memento) -* [Observer](#observer) -* [State](#state) -* [Strategy](#strategy) -* [Template method](#template-method) -* [Visitor](#visitor) -* [Null Object](#null-object) -* [Intercepting Filter](#intercepting-filter) -* [Specification](#specification) -* [Dependency Injection](#dependency-injection) - -### Concurrency Patterns [↑](#top) - -Concurrency patterns are those types of design patterns that deal with the -multi-threaded programming paradigm. - -* [Double Checked Locking](#double-checked-locking) -* [Thread Pool](#thread-pool) -* [Async Method Invocation](#async-method-invocation) -* [Half-Sync/Half-Async](#half-sync-half-async) - -### Presentation Tier Patterns [↑](#top) - -Presentation Tier patterns are the top-most level of the application, this is -concerned with translating tasks and results to something the user can -understand. - -* [Model-View-Controller](#model-view-controller) -* [Model-View-Presenter](#model-view-presenter) -* [Flux](#flux) -* [Front Controller](#front-controller) - -### Business Tier Patterns [↑](#top) - -* [Business Delegate](#business-delegate) - -### Architectural Patterns [↑](#top) - -An architectural pattern is a general, reusable solution to a commonly occurring -problem in software architecture within a given context. - -* [Data Access Object](#dao) -* [Service Layer](#service-layer) -* [Naked Objects](#naked-objects) -* [Repository](#repository) - -### Integration Patterns [↑](#top) - -Integration patterns are concerned with how software applications communicate -and exchange data. - -* [Tolerant Reader](#tolerant-reader) - -### Idioms [↑](#top) - -A programming idiom is a means of expressing a recurring construct in one or -more programming languages. Generally speaking, a programming idiom is an -expression of a simple task, algorithm, or data structure that is not a built-in -feature in the programming language being used, or, conversely, the use of an -unusual or notable feature that is built into a programming language. What -distinguishes idioms from patterns is generally the size, the idioms tend to be -something small while the patterns are larger. - -* [Execute Around](#execute-around) -* [Poison Pill](#poison-pill) -* [Callback](#callback) -* [Lazy Loading](#lazy-loading) -* [Double Dispatch](#double-dispatch) -* [Resource Acquisition Is Initialization](#resource-acquisition-is-initialization) -* [Private Class Data](#private-class-data) +# How to contribute [↑](#top) + +If you are willing to contribute to the project you will find the relevant information in our [developer wiki](https://github.com/iluwatar/java-design-patterns/wiki). + # Frequently asked questions [↑](#top) @@ -211,53 +99,6 @@ blocked waiting for available object from the pool. This is not the case with Flyweight. - -# How to contribute [↑](#top) - -**To work on a new pattern** you need to do the following steps: - -1. If there is no issue for the new pattern yet, raise new issue. Comment on - the issue that you are working on it so that others don't start work on the - same thing. -2. Fork the repository. -3. Implement the code changes in your fork. Remember to add sufficient comments - documenting the implementation. Reference the issue id e.g. #52 in your - commit messages. -4. Create a simple class diagram from your example code. -5. Add description of the pattern in README.md and link to the class diagram. -6. Create a pull request. - -**To work on one of the non-pattern issues** you need to do the following steps: - -1. Check that the issue has "help wanted" badge -2. Comment on the issue that you are working on it -3. Fork the repository. -4. Implement the code changes in your fork. Remember to add sufficient comments - documenting the implementation. Reference the issue id e.g. #52 in your - commit messages. -5. Create a pull request. - -**For creating/editing UML diagrams** you need [ObjectAid UML Explorer for Eclipse](http://www.objectaid.com/home). - -**For inspiration** check out the following sources: - -* there is a good list of design patterns at [Wikipedia](http://en.wikipedia.org/wiki/Software_design_pattern) -* Martin Fowler's [Catalog of Patterns of Enterprise Application Architecture](http://martinfowler.com/eaaCatalog/) -* [pattern language for microservices](http://microservices.io/patterns/index.html) -* Microsoft's [Cloud Design Patterns](http://download.microsoft.com/download/B/B/6/BB69622C-AB5D-4D5F-9A12-B81B952C1169/CloudDesignPatternsBook-PDF.pdf) - -**Links to patterns applied in real world applications** are welcome. The links -should be added to the corresponding section of the `README.md`. - - -# Versioning [↑](#top) - -Java-design-patterns project uses [semantic versioning](http://semver.org/) -scheme. However, version numbers in this project do not signify binary releases -(since we don't make any) but rather milestones achieved on the roadmap. In -other words, version numbers are used only for project planning sake. - - # Credits [↑](#top) * [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) @@ -285,4 +126,4 @@ other words, version numbers are used only for project planning sake. # License [↑](#top) -This project is licensed under the terms of the MIT license. +This project is licensed under the terms of the MIT license. \ No newline at end of file diff --git a/active-record/etc/.gitkeep b/active-record/etc/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/active-record/index.md b/active-record/index.md new file mode 100644 index 000000000000..b042104c58ce --- /dev/null +++ b/active-record/index.md @@ -0,0 +1,21 @@ +--- +layout: pattern +title: Active Record +folder: active-record +permalink: /patterns/active-record/ +categories: Architectural +tags: Java +--- + +**Intent:** Active record is an object that wraps a row in a database table or view, + encapsulates the database access, and adds domain logic on that data. Active Record + uses the most obvious approach, putting data access logic in the domain object. + +**Applicability:** Use active record pattern when + +* objects correspond directly to the database tables +* business logic is not too complex + +**Real world examples:** + +* [ActiveJDBC](https://en.wikipedia.org/wiki/ActiveJDBC) diff --git a/active-record/pom.xml b/active-record/pom.xml new file mode 100644 index 000000000000..7664faea109d --- /dev/null +++ b/active-record/pom.xml @@ -0,0 +1,30 @@ + + + + java-design-patterns + com.iluwatar + 1.6.0 + + 4.0.0 + + active-record + + + + + com.h2database + h2 + + + + + + junit + junit + test + + + + diff --git a/active-record/src/main/java/com/iluwatar/active/record/DB.java b/active-record/src/main/java/com/iluwatar/active/record/DB.java new file mode 100644 index 000000000000..2c8778488bff --- /dev/null +++ b/active-record/src/main/java/com/iluwatar/active/record/DB.java @@ -0,0 +1,58 @@ +package com.iluwatar.active.record; + +import org.h2.tools.DeleteDbFiles; + +import java.sql.*; + +/** + * An utility class that holds and encapsulates all the DB related operations. + *

+ * Created by Stephen Lazarionok. + */ +public class DB { + + static { + DeleteDbFiles.execute("~", "test", true); + try { + Class.forName("org.h2.Driver"); + final Connection connection = getConnection(); + final Statement statement = connection.createStatement(); + statement.execute( + "create table wand(id BIGINT primary key, length_inches REAL, wood varchar(100), core varchar(100))"); + statement.close(); + connection.close(); + } catch (final SQLException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + /** + * Provides a connection to the database configured. + * + * @return + * @throws SQLException + */ + public static Connection getConnection() throws SQLException { + return DriverManager.getConnection("jdbc:h2:~/test"); + } + + public static void closeConnection(final Connection connection) { + try { + if (connection != null) { + connection.close(); + } + } catch (SQLException e) { + System.out.println("Failed to close a connection"); + } + } + + public static void closePreparedStatement(final PreparedStatement ps) { + try { + if (ps != null) { + ps.close(); + } + } catch (SQLException e) { + System.out.println("Failed to close a prepared statement"); + } + } +} diff --git a/active-record/src/main/java/com/iluwatar/active/record/MagicWand.java b/active-record/src/main/java/com/iluwatar/active/record/MagicWand.java new file mode 100644 index 000000000000..21be0a34bf3f --- /dev/null +++ b/active-record/src/main/java/com/iluwatar/active/record/MagicWand.java @@ -0,0 +1,279 @@ +package com.iluwatar.active.record; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * "Every single wand is unique and will depend for its character on the particular tree and magical creature + * from which it derives its materials. Moreover, each wand, from the moment it finds its ideal owner, will begin + * to learn from and teach its human partner." @ Garrick Ollivander + *

+ * Created by Stephen Lazarionok. + */ +public class MagicWand { + + private Long id; + + private double lengthInches; + + private WandWoodType wood; + + private WandCoreType core; + + public Long getId() { + return id; + } + + public double getLengthInches() { + return lengthInches; + } + + public WandWoodType getWood() { + return wood; + } + + public WandCoreType getCore() { + return core; + } + + public void setLengthInches(double lengthInches) { + this.lengthInches = lengthInches; + } + + public void setWood(WandWoodType wood) { + this.wood = wood; + } + + public void setCore(WandCoreType core) { + this.core = core; + } + + @Override public String toString() { + final StringBuilder sb = new StringBuilder("MagicWand("); + sb.append(getWood()); + sb.append(", "); + sb.append(getCore()); + sb.append(", "); + sb.append(getLengthInches()); + sb.append(" inch(es))"); + return sb.toString(); + } + + /** + * *********************************** + * ****** Data access methods + * *********************************** + */ + + private static final String SELECT_SQL = "select * from wand where id = ?"; + private static final String DELETE_SQL = "delete from wand where id = ?"; + private static final String UPDATE_SQL = + "update wand set length_inches = ?, wood = ?, core = ? where id = ?"; + private static final String CREATE_SQL = "insert into wand values(?, ?, ?, ?)"; + + /** + * Saves the instance to the DB. + * + * @return + */ + public long save() { + validateToSave(); + Connection connection = null; + PreparedStatement ps = null; + try { + connection = DB.getConnection(); + ps = connection.prepareStatement(CREATE_SQL); + + final long id = System.currentTimeMillis(); + ps.setLong(1, id); + ps.setDouble(2, getLengthInches()); + ps.setString(3, getWood().name()); + ps.setString(4, getCore().name()); + + ps.execute(); + + + return id; + } catch (final SQLException e) { + throw new RuntimeException(e); + } finally { + DB.closePreparedStatement(ps); + DB.closeConnection(connection); + } + } + + /** + * Deletes the instance from the DB. + */ + public void delete() { + validateToDelete(); + Connection connection = null; + PreparedStatement ps = null; + try { + connection = DB.getConnection(); + ps = connection.prepareStatement(DELETE_SQL); + + + ps.setLong(1, getId()); + ps.execute(); + ps.close(); + connection.close(); + } catch (final SQLException e) { + throw new RuntimeException(e); + } finally { + DB.closePreparedStatement(ps); + DB.closeConnection(connection); + } + } + + /** + * Updates the instance in DB. + */ + public void update() { + validateToUpdate(); + Connection connection = null; + PreparedStatement ps = null; + try { + connection = DB.getConnection(); + ps = connection.prepareStatement(UPDATE_SQL); + + + ps.setDouble(1, getLengthInches()); + ps.setString(2, getWood().name()); + ps.setString(3, getCore().name()); + ps.setLong(4, getId()); + + ps.execute(); + ps.close(); + connection.close(); + } catch (final SQLException e) { + throw new RuntimeException(e); + } finally { + DB.closePreparedStatement(ps); + DB.closeConnection(connection); + } + } + + /** + * Finds the instance in the DB. + * + * @param id + * @return + */ + public static MagicWand find(long id) { + Connection connection = null; + PreparedStatement ps = null; + try { + connection = DB.getConnection(); + ps = connection.prepareStatement(SELECT_SQL); + + ResultSet rs; + ps.setLong(1, id); + rs = ps.executeQuery(); + if (rs.next()) { + final MagicWand wand = new MagicWand(); + + wand.id = rs.getLong("id"); + wand.setCore(WandCoreType.valueOf(rs.getString("core"))); + wand.setWood(WandWoodType.valueOf(rs.getString("wood"))); + wand.setLengthInches(rs.getDouble("length_inches")); + + return wand; + } else { + return null; + } + } catch (final SQLException e) { + throw new RuntimeException(e); + } finally { + DB.closePreparedStatement(ps); + DB.closeConnection(connection); + } + + } + + private void validateToSave() { + validateProperties(); + if (getId() != null) + throw new IllegalStateException( + "Can not save wand that was previously saved. Use 'update' metod instead."); + } + + private void validateToUpdate() { + validateProperties(); + if (getId() == null) + throw new IllegalStateException("Can not update a record without ID specified"); + } + + private void validateToDelete() { + if (getId() == null) + throw new IllegalStateException("Can not delete a record without ID specified"); + } + + private void validateProperties() { + if (getLengthInches() > 0.0d) + throw new IllegalStateException("Can not save a wand with length <= 0"); + if (getWood() == null) + throw new IllegalStateException("Can not save a wand without wood specified"); + if (getCore() == null) + throw new IllegalStateException("Can not save a wand without core specified"); + } + + /** + * *********************************** + * ****** Domain Logic + * *********************************** + */ + + private double calculateWoodMagicFactor() { + + switch (getWood()) { + case HOLLY: + return 1.2d; + case WINE: + return 1.0d; + case HAWTHORN: + return 0.8d; + default: + return 0.0d; + } + } + + private double calculateCoreMagicFactor() { + + switch (getCore()) { + case PHOENIX_FEATHER: + return 1.2d; + case DRAGON_HEARTSTRING: + return 1.0d; + case UNICORN_HAIR: + return 0.8d; + default: + return 0.0d; + } + } + + public double calculateMagicPower() { + + return calculateWoodMagicFactor() * calculateWoodMagicFactor() * getLengthInches(); + } + + public void castFireball() throws SpellCastException { + + if (calculateMagicPower() >= 10.0) { + System.out.println(toString() + ": a fireball spell is casted"); + } else { + throw new SpellCastException("Can not cast fire ball! Not enough magic power..."); + } + } + + public void castLighting() throws SpellCastException { + if (calculateMagicPower() >= 20.0) { + System.out.println(toString() + ": a lighting spell is casted"); + } else { + throw new SpellCastException("Can not cast lighting! Not enough magic power..."); + } + } + +} diff --git a/active-record/src/main/java/com/iluwatar/active/record/SpellCastException.java b/active-record/src/main/java/com/iluwatar/active/record/SpellCastException.java new file mode 100644 index 000000000000..91066f94edc6 --- /dev/null +++ b/active-record/src/main/java/com/iluwatar/active/record/SpellCastException.java @@ -0,0 +1,18 @@ +package com.iluwatar.active.record; + +/** + * Describes the exception that is raised when a spell can not be cast for some reason. + *

+ * Created by Stephen Lazarionok. + */ +public class SpellCastException extends Exception { + + /** + * Create an exception with the reason provided. + * + * @param message + */ + public SpellCastException(final String message) { + super(message); + } +} diff --git a/active-record/src/main/java/com/iluwatar/active/record/WandCoreType.java b/active-record/src/main/java/com/iluwatar/active/record/WandCoreType.java new file mode 100644 index 000000000000..5d1b806ce123 --- /dev/null +++ b/active-record/src/main/java/com/iluwatar/active/record/WandCoreType.java @@ -0,0 +1,15 @@ +package com.iluwatar.active.record; + +/** + * The core of a wand is a magical substance placed within the length of wood. They are usually + * bits of hair/feather extracted from some sort of Magical Being or Creature. The materials used + * for wand cores can vary widely, though certain wand-makers may prefer to use certain materials; + * for example, Garrick Ollivander discovered and pioneered the use of phoenix feathers, dragon heartstrings, + * and unicorn tail hairs. + *

+ * Created by Stephen Lazarionok. + */ +public enum WandCoreType { + + PHOENIX_FEATHER, DRAGON_HEARTSTRING, UNICORN_HAIR +} diff --git a/active-record/src/main/java/com/iluwatar/active/record/WandWoodType.java b/active-record/src/main/java/com/iluwatar/active/record/WandWoodType.java new file mode 100644 index 000000000000..b353da5b1722 --- /dev/null +++ b/active-record/src/main/java/com/iluwatar/active/record/WandWoodType.java @@ -0,0 +1,15 @@ +package com.iluwatar.active.record; + +/** + * Describes the wood types that can be used to produce magic wands. + *

+ * "Only a minority of trees can produce wand quality wood (just as a minority of humans can produce magic). + * It takes years of experience to tell which ones have the gift, although the job is made easier if Bowtruckles + * are found nesting in the leaves, as they never inhabit mundane trees." @ Garrick Ollivander + *

+ * Created by Stephen Lazarionok. + */ +public enum WandWoodType { + + HOLLY, WINE, HAWTHORN +} diff --git a/active-record/src/main/java/com/iluwatar/active/record/sample/Application.java b/active-record/src/main/java/com/iluwatar/active/record/sample/Application.java new file mode 100644 index 000000000000..e054d76d7b0b --- /dev/null +++ b/active-record/src/main/java/com/iluwatar/active/record/sample/Application.java @@ -0,0 +1,35 @@ +package com.iluwatar.active.record.sample; + +import com.iluwatar.active.record.MagicWand; +import com.iluwatar.active.record.SpellCastException; +import com.iluwatar.active.record.WandCoreType; +import com.iluwatar.active.record.WandWoodType; + +/** + * Created by Stephen Lazarionok. + */ +public class Application { + + public static void main(final String[] args) throws SpellCastException { + + final MagicWand harryPotterWand = new MagicWand(); + + harryPotterWand.setWood(WandWoodType.WINE); + harryPotterWand.setCore(WandCoreType.PHOENIX_FEATHER); + harryPotterWand.setLengthInches(11.0d); + + final long wandId = harryPotterWand.save(); + + final MagicWand wand = MagicWand.find(wandId); + + wand.castFireball(); + + wand.setCore(WandCoreType.DRAGON_HEARTSTRING); + wand.setLengthInches(21.0d); + + wand.update(); + + MagicWand.find(wandId).castLighting(); + MagicWand.find(wandId).delete(); + } +} diff --git a/active-record/src/test/java/MagicWandSpecification.java b/active-record/src/test/java/MagicWandSpecification.java new file mode 100644 index 000000000000..5f5b77ac9919 --- /dev/null +++ b/active-record/src/test/java/MagicWandSpecification.java @@ -0,0 +1,64 @@ +import com.iluwatar.active.record.MagicWand; +import com.iluwatar.active.record.SpellCastException; +import com.iluwatar.active.record.WandCoreType; +import com.iluwatar.active.record.WandWoodType; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.junit.Assert.assertTrue; + +/** + * Created by Stephen Lazarionok. + */ +public class MagicWandSpecification { + + @Rule public ExpectedException exception = ExpectedException.none(); + + @Test public void shouldCastFireball_IfMagicPower10AndMore() throws SpellCastException { + + final MagicWand wand = new MagicWand(); + wand.setWood(WandWoodType.WINE); + wand.setCore(WandCoreType.DRAGON_HEARTSTRING); + wand.setLengthInches(10.0d); + + assertTrue(wand.calculateMagicPower() == 10.0d); + wand.castFireball(); + } + + @Test public void shouldNotCastFireball_IfMagicPowerLessThan10() throws SpellCastException { + + final MagicWand wand = new MagicWand(); + wand.setWood(WandWoodType.WINE); + wand.setCore(WandCoreType.DRAGON_HEARTSTRING); + wand.setLengthInches(9.0d); + + assertTrue(wand.calculateMagicPower() == 9.0d); + exception.expect(SpellCastException.class); + + wand.castFireball(); + } + + @Test public void shouldCastLigthing_IfMagicPower20AndMore() throws SpellCastException { + + final MagicWand wand = new MagicWand(); + wand.setWood(WandWoodType.WINE); + wand.setCore(WandCoreType.DRAGON_HEARTSTRING); + wand.setLengthInches(20.5d); + + assertTrue(wand.calculateMagicPower() == 20.5d); + wand.castLighting(); + } + + @Test public void shouldNotCastLigthing_IfMagicPowerLessThan20() throws SpellCastException { + + final MagicWand wand = new MagicWand(); + wand.setWood(WandWoodType.WINE); + wand.setCore(WandCoreType.DRAGON_HEARTSTRING); + wand.setLengthInches(18.0d); + + assertTrue(wand.calculateMagicPower() == 18.0d); + exception.expect(SpellCastException.class); + wand.castLighting(); + } +} diff --git a/pom.xml b/pom.xml index 1059edc58425..fa85344b046c 100644 --- a/pom.xml +++ b/pom.xml @@ -20,6 +20,7 @@ abstract-factory + active-record builder factory-method prototype