refactoring: Refactor the prototype pattern to avoid code duplications (Closes #584) (#1970)

This commit  refactors the Prototype pattern by making it Cloneable and thus inheriting the clone() method to its subclasses which removes code duplications.
This commit is contained in:
Yonatan Karp-Rudin
2022-09-08 15:55:02 +03:00
committed by GitHub
parent c1c863843f
commit 5397f85d7d
13 changed files with 35 additions and 69 deletions
+1 -1
View File
@@ -1,2 +1,2 @@
lombok.log.fieldName = LOGGER lombok.log.fieldName = LOGGER
lombok.addLombokGeneratedAnnotation = true
+10 -13
View File
@@ -22,7 +22,7 @@ used for creating new objects from prototype instances.
Real-world example Real-world example
> Remember Dolly? The sheep that was cloned! Lets not get into the details but the key point here is > Remember Dolly? The sheep that was cloned! Let's not get into the details but the key point here is
> that it is all about cloning. > that it is all about cloning.
In plain words In plain words
@@ -45,8 +45,11 @@ interface with a method for cloning objects. In this example, `Prototype` interf
this with its `copy` method. this with its `copy` method.
```java ```java
public interface Prototype { public abstract class Prototype<T> implements Cloneable {
Object copy(); @SneakyThrows
public T copy() {
return (T) super.clone();
}
} }
``` ```
@@ -54,15 +57,13 @@ Our example contains a hierarchy of different creatures. For example, let's look
`OrcBeast` classes. `OrcBeast` classes.
```java ```java
@EqualsAndHashCode @EqualsAndHashCode(callSuper = false)
@NoArgsConstructor @NoArgsConstructor
public abstract class Beast implements Prototype { public abstract class Beast extends Prototype<Beast> {
public Beast(Beast source) { public Beast(Beast source) {
} }
@Override
public abstract Beast copy();
} }
@EqualsAndHashCode(callSuper = false) @EqualsAndHashCode(callSuper = false)
@@ -76,19 +77,15 @@ public class OrcBeast extends Beast {
this.weapon = orcBeast.weapon; this.weapon = orcBeast.weapon;
} }
@Override
public OrcBeast copy() {
return new OrcBeast(this);
}
@Override @Override
public String toString() { public String toString() {
return "Orcish wolf attacks with " + weapon; return "Orcish wolf attacks with " + weapon;
} }
} }
``` ```
We don't want to go into too much details, but the full example contains also base classes `Mage` We don't want to go into too many details, but the full example contains also base classes `Mage`
and `Warlord` and there are specialized implementations for those for elves in addition to orcs. and `Warlord` and there are specialized implementations for those for elves in addition to orcs.
To take full advantage of the prototype pattern, we create `HeroFactory` and `HeroFactoryImpl` To take full advantage of the prototype pattern, we create `HeroFactory` and `HeroFactoryImpl`
@@ -29,14 +29,11 @@ import lombok.NoArgsConstructor;
/** /**
* Beast. * Beast.
*/ */
@EqualsAndHashCode @EqualsAndHashCode(callSuper = false)
@NoArgsConstructor @NoArgsConstructor
public abstract class Beast implements Prototype { public abstract class Beast extends Prototype<Beast> {
public Beast(Beast source) { public Beast(Beast source) {
} }
@Override
public abstract Beast copy();
} }
@@ -40,11 +40,6 @@ public class ElfBeast extends Beast {
this.helpType = elfBeast.helpType; this.helpType = elfBeast.helpType;
} }
@Override
public ElfBeast copy() {
return new ElfBeast(this);
}
@Override @Override
public String toString() { public String toString() {
return "Elven eagle helps in " + helpType; return "Elven eagle helps in " + helpType;
@@ -40,11 +40,6 @@ public class ElfMage extends Mage {
this.helpType = elfMage.helpType; this.helpType = elfMage.helpType;
} }
@Override
public ElfMage copy() {
return new ElfMage(this);
}
@Override @Override
public String toString() { public String toString() {
return "Elven mage helps in " + helpType; return "Elven mage helps in " + helpType;
@@ -24,31 +24,24 @@
package com.iluwatar.prototype; package com.iluwatar.prototype;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
/** /**
* ElfWarlord. * ElfWarlord.
*/ */
@EqualsAndHashCode @EqualsAndHashCode(callSuper = true)
@RequiredArgsConstructor
public class ElfWarlord extends Warlord { public class ElfWarlord extends Warlord {
private final String helpType; private final String helpType;
public ElfWarlord(String helpType) {
this.helpType = helpType;
}
public ElfWarlord(ElfWarlord elfWarlord) { public ElfWarlord(ElfWarlord elfWarlord) {
super(elfWarlord); super(elfWarlord);
this.helpType = elfWarlord.helpType; this.helpType = elfWarlord.helpType;
} }
@Override
public ElfWarlord copy() {
return new ElfWarlord(this);
}
@Override @Override
public String toString() { public String toString() {
return "Elven warlord helps in " + helpType; return "Elven warlord helps in " + helpType;
} }
} }
@@ -29,14 +29,11 @@ import lombok.NoArgsConstructor;
/** /**
* Mage. * Mage.
*/ */
@EqualsAndHashCode @EqualsAndHashCode(callSuper = false)
@NoArgsConstructor @NoArgsConstructor
public abstract class Mage implements Prototype { public abstract class Mage extends Prototype<Mage> {
public Mage(Mage source) { public Mage(Mage source) {
} }
@Override
public abstract Mage copy();
} }
@@ -40,11 +40,6 @@ public class OrcBeast extends Beast {
this.weapon = orcBeast.weapon; this.weapon = orcBeast.weapon;
} }
@Override
public OrcBeast copy() {
return new OrcBeast(this);
}
@Override @Override
public String toString() { public String toString() {
return "Orcish wolf attacks with " + weapon; return "Orcish wolf attacks with " + weapon;
@@ -40,11 +40,6 @@ public class OrcMage extends Mage {
this.weapon = orcMage.weapon; this.weapon = orcMage.weapon;
} }
@Override
public OrcMage copy() {
return new OrcMage(this);
}
@Override @Override
public String toString() { public String toString() {
return "Orcish mage attacks with " + weapon; return "Orcish mage attacks with " + weapon;
@@ -40,11 +40,6 @@ public class OrcWarlord extends Warlord {
this.weapon = orcWarlord.weapon; this.weapon = orcWarlord.weapon;
} }
@Override
public OrcWarlord copy() {
return new OrcWarlord(this);
}
@Override @Override
public String toString() { public String toString() {
return "Orcish warlord attacks with " + weapon; return "Orcish warlord attacks with " + weapon;
@@ -23,11 +23,21 @@
package com.iluwatar.prototype; package com.iluwatar.prototype;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
/** /**
* Prototype. * Prototype.
*/ */
public interface Prototype { @Slf4j
public abstract class Prototype<T> implements Cloneable {
Object copy();
/**
* Object a shallow copy of this object or null if this object is not Cloneable.
*/
@SuppressWarnings("unchecked")
@SneakyThrows
public T copy() {
return (T) super.clone();
}
} }
@@ -29,14 +29,11 @@ import lombok.NoArgsConstructor;
/** /**
* Warlord. * Warlord.
*/ */
@EqualsAndHashCode @EqualsAndHashCode(callSuper = false)
@NoArgsConstructor @NoArgsConstructor
public abstract class Warlord implements Prototype { public abstract class Warlord extends Prototype<Warlord> {
public Warlord(Warlord source) { public Warlord(Warlord source) {
} }
@Override
public abstract Warlord copy();
} }
@@ -39,7 +39,7 @@ import org.junit.jupiter.params.provider.MethodSource;
* @param <P> Prototype * @param <P> Prototype
* @author Jeroen Meulemeester * @author Jeroen Meulemeester
*/ */
class PrototypeTest<P extends Prototype> { class PrototypeTest<P extends Prototype<P>> {
static Collection<Object[]> dataProvider() { static Collection<Object[]> dataProvider() {
return List.of( return List.of(
new Object[]{new OrcBeast("axe"), "Orcish wolf attacks with axe"}, new Object[]{new OrcBeast("axe"), "Orcish wolf attacks with axe"},