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

More about Alternative Classes With Different Interfaces

001  interface Persona {
002  
003   public int attackWithMagic();
004  
005   public void block(int incomingDamage);
006  
007   public int attackWithMelee();
008  
009   public void restoreHealth();
010  }
011  
012  interface Avatar {
013   public int useMagic();
014  
015   public void defend(int opponentAttack);
016  
017   public int useMelee();
018  
019   public void heal();
020  }
021  
022  class SimplePersona implements Persona {
023  
024   private String name;
025   private int health;
026   private int meleeDamage;
027   private int magicDamage;
028   private int defense;
029  
030   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   }
037  
038   @Override
039   public int attackWithMagic() {
040   System.out.println(this.name + " used Magic and caused " + this.magicDamage + " damage!");
041   return this.magicDamage;
042   }
043  
044   @Override
045   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   }
050  
051   @Override
052   public int attackWithMelee() {
053   System.out.println(this.name + " used Melee and caused " + this.meleeDamage + " damage!");
054   return this.meleeDamage;
055   }
056  
057   @Override
058   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   }
063  
064  }
065  
066  class SimpleAvatar implements Avatar {
067  
068   private String avatarName;
069   private int healthPoints;
070   private int meleeAttackPoints;
071   private int magicAttackPoints;
072   private int defensePoints;
073  
074   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   }
082  
083   @Override
084   public int useMagic() {
085   System.out.println(this.avatarName + " used Magic and caused " + this.magicAttackPoints + " damage!");
086   return this.magicAttackPoints;
087   }
088  
089   @Override
090   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   }
095  
096   @Override
097   public int useMelee() {
098   System.out.println(this.avatarName + " used Melee and caused " + this.meleeAttackPoints + " damage!");
099   return this.meleeAttackPoints;
100  
101   }
102  
103   @Override
104   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  }
110  
111  public class ACDIBE1 {
112  
113   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);
118  
119   }
120  
121  }