diff --git a/README.md b/README.md index 073cb4859..34ff788dd 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,9 @@ Creational design patterns abstract the instantiation process. They help make a * [Builder](#builder) * [Factory Method](#factory-method) * [Prototype](#prototype) +* [Property](#property) * [Singleton](#singleton) - + ### Structural Patterns Structural patterns are concerned with how classes and objects are composed to form larger structures. @@ -433,6 +434,17 @@ Behavioral patterns are concerned with algorithms and the assignment of responsi **Applicability:** Use the Execute Around idiom when * You use an API that requires methods to be called in pairs such as open/close or allocate/deallocate. +## Property [↑](#list-of-design-patterns) +**Intent:** Create hierarchy of objects and new objects using already existing objects as parents. + +![alt text](https://github.com/iluwatar/java-design-patterns/blob/master/property/etc/property.jpg "Property") + +**Applicability:** Use the Property pattern when +* when you like to have objects with dynamic set of fields and prototype inheritance + +**Real world examples:** +* [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain) prototype inheritance + # Frequently asked questions **Q: What is the difference between State and Strategy patterns?** diff --git a/pom.xml b/pom.xml index 5c15fe5f1..72c37c3ed 100644 --- a/pom.xml +++ b/pom.xml @@ -41,8 +41,9 @@ null-object event-aggregator callback - execute-around - + execute-around + property + diff --git a/property/etc/property.jpg b/property/etc/property.jpg new file mode 100644 index 000000000..e3da03e0c Binary files /dev/null and b/property/etc/property.jpg differ diff --git a/property/etc/property.ucls b/property/etc/property.ucls new file mode 100644 index 000000000..0ad1d61eb --- /dev/null +++ b/property/etc/property.ucls @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/property/pom.xml b/property/pom.xml new file mode 100644 index 000000000..a26894f0a --- /dev/null +++ b/property/pom.xml @@ -0,0 +1,18 @@ + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.0-SNAPSHOT + + property + + + junit + junit + test + + + diff --git a/property/src/main/java/com/iluwatar/App.java b/property/src/main/java/com/iluwatar/App.java new file mode 100644 index 000000000..95ff4355f --- /dev/null +++ b/property/src/main/java/com/iluwatar/App.java @@ -0,0 +1,49 @@ +package com.iluwatar; + +import com.iluwatar.Character.Type; + +/** + * Example of Character instantiation using Property pattern (as concept also known like Prototype inheritance). + * In prototype inheritance instead of classes, as opposite to Java class inheritance, + * objects are used to create another objects and object hierarchies. + * Hierarchies are created using prototype chain through delegation: every object has link to parent object. + * Any base (parent) object can be amended at runtime (by adding or removal of some property), and all child objects will be affected as result. + */ +public class App { + + public static void main(String[] args) { + /* set up */ + Prototype charProto = new Character(); + charProto.set(Stats.STRENGTH, 10); + charProto.set(Stats.AGILITY, 10); + charProto.set(Stats.ARMOR, 10); + charProto.set(Stats.ATTACK_POWER, 10); + + Character mageProto = new Character(Type.MAGE, charProto); + mageProto.set(Stats.INTELLECT, 15); + mageProto.set(Stats.SPIRIT, 10); + + Character warProto = new Character(Type.WARRIOR, charProto); + warProto.set(Stats.RAGE, 15); + warProto.set(Stats.ARMOR, 15); // boost default armor for warrior + + Character rogueProto = new Character(Type.ROGUE, charProto); + rogueProto.set(Stats.ENERGY, 15); + rogueProto.set(Stats.AGILITY, 15); // boost default agility for rogue + + /* usage */ + Character mag = new Character("Player_1", mageProto); + mag.set(Stats.ARMOR, 8); + System.out.println(mag); + + Character warrior = new Character("Player_2", warProto); + System.out.println(warrior); + + Character rogue = new Character("Player_3", rogueProto); + System.out.println(rogue); + + Character rogueDouble = new Character("Player_4", rogue); + rogueDouble.set(Stats.ATTACK_POWER, 12); + System.out.println(rogueDouble); + } +} diff --git a/property/src/main/java/com/iluwatar/Character.java b/property/src/main/java/com/iluwatar/Character.java new file mode 100644 index 000000000..ac8abaa0e --- /dev/null +++ b/property/src/main/java/com/iluwatar/Character.java @@ -0,0 +1,117 @@ +package com.iluwatar; + +import java.util.HashMap; +import java.util.Map; + +/** + * Represents Character in game and his abilities (base stats). + */ +public class Character implements Prototype { + + public enum Type { + WARRIOR, MAGE, ROGUE + } + + private final Prototype prototype; + private final Map properties = new HashMap<>(); + + private String name; + private Type type; + + public Character() { + this.prototype = new Prototype() { // Null-value object + @Override + public Integer get(Stats stat) { + return null; + } + @Override + public boolean has(Stats stat) { + return false; + } + @Override + public void set(Stats stat, Integer val) { + } + @Override + public void remove(Stats stat) { + }} + ; + } + + public Character(Type type, Prototype prototype) { + this.type = type; + this.prototype = prototype; + } + + public Character(String name, Character prototype) { + this.name = name; + this.type = prototype.type; + this.prototype = prototype; + } + + public String name() { + return name; + } + + public Type type() { + return type; + } + + @Override + public Integer get(Stats stat) { + boolean containsValue = properties.containsKey(stat); + if (containsValue) { + return properties.get(stat); + } else { + return prototype.get(stat); + } + } + + @Override + public boolean has(Stats stat) { + return get(stat) != null; + } + + @Override + public void set(Stats stat, Integer val) { + properties.put(stat, val); + } + + @Override + public void remove(Stats stat) { + properties.put(stat, null); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + if (name != null) { + builder + .append("Player: ") + .append(name) + .append("\n"); + } + + if (type != null) { + builder + .append("Character type: ") + .append(type.name()) + .append("\n"); + } + + builder.append("Stats:\n"); + for (Stats stat : Stats.values()) { + Integer value = this.get(stat); + if (value == null) { + continue; + } + builder + .append(" - ") + .append(stat.name()) + .append(":") + .append(value) + .append("\n"); + } + return builder.toString(); + } + +} diff --git a/property/src/main/java/com/iluwatar/Prototype.java b/property/src/main/java/com/iluwatar/Prototype.java new file mode 100644 index 000000000..ef9d2d7b6 --- /dev/null +++ b/property/src/main/java/com/iluwatar/Prototype.java @@ -0,0 +1,12 @@ +package com.iluwatar; + +/** + * Interface for prototype inheritance + */ +public interface Prototype { + + public Integer get(Stats stat); + public boolean has(Stats stat); + public void set(Stats stat, Integer val); + public void remove(Stats stat); +} diff --git a/property/src/main/java/com/iluwatar/Stats.java b/property/src/main/java/com/iluwatar/Stats.java new file mode 100644 index 000000000..3c5648148 --- /dev/null +++ b/property/src/main/java/com/iluwatar/Stats.java @@ -0,0 +1,9 @@ +package com.iluwatar; + +/** + * All possible attributes that Character can have + */ +public enum Stats { + + AGILITY, STRENGTH, ATTACK_POWER, ARMOR, INTELLECT, SPIRIT, ENERGY, RAGE +} diff --git a/property/src/test/java/com/iluwatar/AppTest.java b/property/src/test/java/com/iluwatar/AppTest.java new file mode 100644 index 000000000..6db5ad214 --- /dev/null +++ b/property/src/test/java/com/iluwatar/AppTest.java @@ -0,0 +1,12 @@ +package com.iluwatar; + +import org.junit.Test; + +public class AppTest { + + @Test + public void test() { + String[] args = {}; + App.main(args); + } +}