This document discusses strategies for fixing class and package tangling issues. For class tangling, potential solutions include depending on interfaces instead of concrete classes to remove cycles, moving methods that refer to derived classes to a factory class, and removing unnecessary references between classes. For package tangling, solutions involve separating interface and implementation packages, moving misplaced classes to more suitable packages, and splitting packages that contain unrelated classes.
2. WHAT’S A “TANGLE”?
➤ “A tangle is a portion of a dependency graph within which all
the items are directly or indirectly dependent on all the other
nodes in the tangle.” (Source: structure101.com)
Example of a class tangle (cycle)
Example of a package tangle
5. DEPENDENCIES BETWEEN CONCRETE CLASSES - TANGLE
import java.util.List;
public class Customer {
List<Order> orders;
}
class Order {
Customer purchaser;
}
A direct cyclic dependency between the Customer & Order class because
they contain instances of each other’s type
6. DEPENDENCIES BETWEEN CONCRETE CLASSES - TANGLE - FIX
import java.util.List;
public class Customer {
List<Order> orders;
}
class Order {
Customer purchaser;
}
Depend on the interface instead of the implementation (and the cycle is gone)
import java.util.List;
interface Buyer {}
public class Customer implements Buyer {
List<Buyable> orders;
}
interface Buyable {}
class Order implements Buyable {
Buyer purchaser;
}
7. BASE CLASS REFERS TO ITS DERIVED TYPES(S)
enum ImageType { JPEG, BMP, PNG };
abstract class Image {
public static Image getImage(ImageType imageType,
String name) {
switch (imageType) {
// JPEGImage, BMPImage, PNGImage are
// derived classes of the abstract class Image
case JPEG: return new JPEGImage(name);
case BMP: return new BMPImage(name);
case PNG: return new PNGImage(name);
}
return null;
}
}
This is a special kind of cyclic dependency - the base type knows about its
derived type! Here it is creation of its derived objects results in a tangle
8. BASE CLASS REFERS TO ITS DERIVED TYPES(S) - FIX
It’s easy to break this cyclic dependency - move the getImage() method to a
dedicated class named ImageFactory!
9. AVOIDABLE REFERENCES TO CLASSES - TANGLE
In this case, the Target abstract class has unnecessary references to concrete
classes; instead of overloading, could be specific method calls instead
package main;
abstract class Target {
public abstract void genCode(Constant constant);
public abstract void genCode(Plus plus);
public abstract void genCode(Mult mult);
}
class JVMTarget extends Target {
public void genCode(Constant constant) {
System.out.println("bipush " + constant.getValue());
}
public void genCode(Plus plus) {
System.out.println("iadd");
}
public void genCode(Mult mult) {
System.out.println("imul");
}
}
class DotNetTarget extends Target {
public void genCode(Constant constant) {
System.out.println("ldarg " + constant.getValue());
}
public void genCode(Plus plus) {
System.out.println("add");
}
public void genCode(Mult mult) {
System.out.println("mul");
}
}
abstract class ExprNode {
protected static Target target = new JVMTarget();
public static void setTarget(Target newTarget) {
target = newTarget;
}
public abstract void genCode();
}
class Constant extends ExprNode {
int val;
public Constant(int arg) {
val = arg;
}
public int getValue() {
return val;
}
public void genCode() {
target.genCode(this);
}
}
10. UNNECESSARY REFERENCES TO CLASSES - TANGLE - FIX
abstract class Target {
public abstract void genCodeConstant(int constValue);
public abstract void genCodePlus();
public abstract void genCodeMult();
}
class JVMTarget extends Target {
public void genCodeConstant(int constValue) {
System.out.println("bipush " + constValue);
}
public void genCodePlus() {
System.out.println("iadd");
}
public void genCodeMult() {
System.out.println("imul");
}
}
abstract class Target {
public abstract void genCode(Constant constant);
public abstract void genCode(Plus plus);
public abstract void genCode(Mult mult);
}
class JVMTarget extends Target {
public void genCode(Constant constant) {
System.out.println("bipush " + constant.getValue());
}
public void genCode(Plus plus) {
System.out.println("iadd");
}
public void genCode(Mult mult) {
System.out.println("imul");
}
}
By making the Target class not refer to the concrete ExprNode classes, the
tangle is gone!
11. SUMMARY - STRATEGIES FOR BREAKING CLASS TANGLES
Cause(s) Potential Solution(s)
References among concrete classes
causes cycle(s)/tangle(s)
Depend on the interfaces than on the
concrete classes (extract interfaces
if they are absent)
A base class refers to one or more
of its derived class(es) causing
tangle(s) (e.g., base class creates
objects of its derived types)
Remove the offending references
from base class to the derived
class(es) (e.g., move the object
creation to a dedicated factory class)
Unnecessary references to classes
causes tangles
Remove the unnecessary references
13. INTERFACE & IMPLEMENTATION PACKAGED TOGETHER
Packaging the interface & corresponding implementation together causes tangle!
package buyers;
import buyables.Buyable;
import java.util.List;
interface Buyer {}
public class Customer implements Buyer {
List<Buyable> orders;
}
package buyables;
interface Buyable {}
public class Order implements Buyable {
Buyer purchaser;
}
14. INTERFACE & IMPLEMENTATION PACKAGED TOGETHER - FIX
Separating the interfaces as a separate package (from the
implementation package) disentangles the structure!
15. MISPLACED ENTITY - PACKAGE TANGLE
An entity misplaced in a package causes a tangle (where does enum
ImageType belong? In the “factory” package or “image” package?
package image;
import factory.ImageType;
public abstract class Image {
public abstract ImageType getType();
}
package factory;
public enum ImageType { JPEG, BMP, PNG }
package imagetypes;
import factory.ImageType;
import image.Image;
public class BMPImage extends Image {
public BMPImage(String name) {
super();
}
public ImageType getType() {
return ImageType.BMP;
}
}
16. MISPLACED ENTITY - PACKAGE TANGLE - FIX
Here, the entity “enum ImageType” arguably belongs better in “image”
package than in the “factory” package (moving the enum breaks the tangle)
17. MIXED PACKAGE - PACKAGE TANGLE
Here, the Expr class and the builder class ExprBuilder (that builds objects) are put
together in the same “core” package - this results in a tangle
package core;
import nodes.*;
public class ExprBuilder {
private Expr expr = null;
public ExprBuilder() {}
public ExprBuilder Const(int arg) {
expr = Constant.make(arg);
return this;
}
public ExprBuilder Plus(int arg) {
expr = new Addition(expr, Constant.make(arg));
return this;
}
public ExprBuilder Mult(int arg) {
expr = new Multiplication(expr, Constant.make(arg));
return this;
}
public Expr Build() {
return expr;
}
}
18. MIXED PACKAGE - PACKAGE TANGLE - FIX
The tangle is broken by splitting the
“core” package into two packages (“core” and “builder”)
19. SUMMARY - STRATEGIES FOR BREAKING PACKAGE TANGLES
Cause(s) Potential Solution(s)
Interfaces and implementations are
mixed together
Separate the interfaces and
implementations to separate
packages
A class is misplaced in a package
causing a tangle
Move the offending class to a more
suitable package
Classes that may not belong
together packaged into a single
package cause a tangle
Split the package