Alternative Classes With Different Interfaces
Description (What)
Two classes which perform identical functions but have different method names, field names and have different interfaces.
How to Locate It (Where)
Look for hierarchies that look similar and perform similar functions. Look at the logical use of the class and make sure that there aren't other interfaces and classes that supposedly perform similar or exactly same functions.
How It Manifests (Why)
This can happen in cases where two developers working on the same codebase eventually end up creating the same hierarchies and were unaware of a similar structure existing in the program already.
How to Fix It (Possible Treatments)
In Example 1, we simply removed on of the duplicate hierarchies. A more formal treatment is possible by slowly renaming methods, moving methods and eventually simplifying the two classes such that they appear identical and then removing one of them.
In Example 2, a combination of Extract superclass and Rename Method is used. For the common methods, better coding practice will be extract a super class to push all the
common methods into that class; accordingly, some methods have to be renamed because the change of placements.
Other treatments are also possible based on the specific scenario, they can be found here
Examples
Example 1
Before:
There are two different interfaces each of which have their respective implementations all of which are identical but only in method names and field names.
Observed Code Smells:
- Alternative Classes With Different Interfaces (lines 12-20 and 65-107)
After:
Removed one of the similar interface and its class.
Refactoring Applied:
- Alternative Classes With Different Interfaces
- Delete alternate interface and its implementation
Observed Code Smells After Refactoring:
- None
Example 2
Before:
In the bad example, the developer put too much emphasis on the subject that makes the action,
for example, a bishop can getBishopRow(), getBishopCol(), getBishopColor(), bishopCanMove(), bishopCanKil().
In the scenario of the international chess, all the chess pieces have these functionalities.
It doesn't matter who makes the action, just be careful with the rule for each of them respectively.
Therefore, have these chess pieces classes written separately is not ideal, even the names of the methods are different
(which means the subject who makes that action differs), the functionality ends up being the same.
Each of the chess piece can get its locations and color, can kill another chess piece and can move within the bounds.
There are two different interfaces each of which have their respective implementations all of which are identical but only in method names and field names.
Observed Code Smells:
- Alternative Classes With Different Interfaces (lines 11-29, lines 41-69,
lines 78-93, lines 107-153, lines 164-179, lines 192-238, lines 253-273, lines 317-405,
lines 422-437, lines 449-495, lines 503-518, lines 530-576)
After:
Applied Extract superclass to summarize the getter methods and constructors.
Applied Rename Method to get rid of the subject, e.g., bishopCanKill() -> canKill().
Refactoring Applied:
- Alternative Classes With Different Interfaces
- Extract superclass (lines 18-55, lines 58-64, lines 88-94, lines 124-130, lines 163-174, lines 235-241,
lines 265-271).
- Rename Method (lines 36-54)
Observed Code Smells After Refactoring:
- None
When to Ignore
In cases where these two classes might be part of different programs, libraries or frameworks
More
- Example 1
- Example 2
- Before
- After
001 interface Persona {002003 public int attackWithMagic();004005 public void block(int incomingDamage);006007 public int attackWithMelee();008009 public void restoreHealth();010 }011012 interface Avatar {013 public int useMagic();014015 public void defend(int opponentAttack);016017 public int useMelee();018019 public void heal();020 }021022 class SimplePersona implements Persona {023024 private String name;025 private int health;026 private int meleeDamage;027 private int magicDamage;028 private int defense;029030 public SimplePersona(String name, int health, int meleeDamage, int magicDamage, int defense) {031 this.name = name;032 this.health = health;033 this.meleeDamage = meleeDamage;034 this.magicDamage = magicDamage;035 this.defense = defense;036 }037038 @Override039 public int attackWithMagic() {040 System.out.println(this.name + " used Magic and caused " + this.magicDamage + " damage!");041 return this.magicDamage;042 }043044 @Override045 public void block(int incomingDamage) {046 int finalDamage = this.defense * incomingDamage / 100;047 System.out.println(this.name + " defended and suffered " + finalDamage + " damage!");048 this.health -= finalDamage;049 }050051 @Override052 public int attackWithMelee() {053 System.out.println(this.name + " used Melee and caused " + this.meleeDamage + " damage!");054 return this.meleeDamage;055 }056057 @Override058 public void restoreHealth() {059 System.out.println(this.name + " healed themselves " + this.defense / 100 + " points!");060 System.out.println("New health: " + this.health);061 this.health += this.defense / 100;062 }063064 }065066 class SimpleAvatar implements Avatar {067068 private String avatarName;069 private int healthPoints;070 private int meleeAttackPoints;071 private int magicAttackPoints;072 private int defensePoints;073074 public SimpleAvatar(String avatarName, int healthPoints, int meleeAttackPoints, int magicAttackPoints,075 int defensePoints) {076 this.avatarName = avatarName;077 this.healthPoints = healthPoints;078 this.meleeAttackPoints = meleeAttackPoints;079 this.magicAttackPoints = magicAttackPoints;080 this.defensePoints = defensePoints;081 }082083 @Override084 public int useMagic() {085 System.out.println(this.avatarName + " used Magic and caused " + this.magicAttackPoints + " damage!");086 return this.magicAttackPoints;087 }088089 @Override090 public void defend(int opponentAttack) {091 int damageSuffered = this.defensePoints * opponentAttack / 100;092 this.healthPoints -= damageSuffered;093 System.out.println(this.avatarName + " defended and suffered " + damageSuffered + " damage!");094 }095096 @Override097 public int useMelee() {098 System.out.println(this.avatarName + " used Melee and caused " + this.meleeAttackPoints + " damage!");099 return this.meleeAttackPoints;100101 }102103 @Override104 public void heal() {105 System.out.println(this.avatarName + " healed themselves " + this.defensePoints / 100 + " points!");106 System.out.println("New health: " + this.healthPoints);107 this.healthPoints += this.defensePoints / 100;108 }109 }110111 public class ACDIBE1 {112113 public static void main(String[] args) {114 Avatar joseph = new SimpleAvatar("Joseph", 10, 10, 10, 10);115 Persona drake = new SimplePersona("Drake", 10, 10, 10, 10);116 joseph.defend(10);117 drake.block(10);118119 }120121 }