2

I have a bit of code that requires a copy of an object be sent in. This requirement is because a service (runtime library) that is called modifies the object sent. This object also needs to expose setters, in case the doThing method below needs to set any field in the ImportantObj class. This implementation is pending change, but does not have a reasonable expectation to be changed in the near future. My workaround is to provide a class that does as follows:

public class DangerousCallWrapper<T> implements DangerousCaller<T> {
  public T doThing(T dataObject) {

      T cloneOfDataObject = #Clone of dataObject

      // This service modifies the cloneOfDataObject... dangerous! 
      Optional<T> result = service.doThing(cloneOfDataObject);

      return result.orElseThrow(() -> new RuntimeException("No data object returned");
  }
}

public interface DangerousCaller<T> {

  /**
  * Performs the functionality of the DangerousService
  */
  public T doThing(T);
}


public DangerousService<T> {


  public T doThing(T data) {
   data.importantField = null;
   data.thing = "Done!";

   return data;
  }
}

public static void main() {

  DangerousService service = new DangerousService<ImportantObj>();
  ImportantObj important = new ImportantObj().setImportantField("Password for my bank account").setThing("Undone");
  service.doThing(important);
  //would fail this check
  assertNotNull(important.importantField);

  DangerousCallWrapper wrapper = new DangerousCallWrapper<ImportantObj>();
  ImportantObj important = new ImportantObj().setImportantField("Password for my bank account").setThing("Undone");
  service.doThing(important);
  //would not fail this check
  assertNotNull(important.importantField);
}

So the first line of that method is where I am stuck. It is a generic type, so I can't explicitly call some cloning utility like Jackson, or similar.

So I thought I would just add T extends Cloneable to the method... but I opened the can of worms that Cloneable is beyond taboo (https://www.artima.com/intv/bloch13.html). I have also read that copy constructors are probably the best way to handle this... However, I am unsure of how to denote that using the generics.

So my thought was to provide an interface Copyable that does what you would expect Cloneable to do: expose a method, copy() that will create a new instance of the class.

Does this constitute a viable approach?

LuCio
  • 5,055
  • 2
  • 18
  • 34
Anthony
  • 189
  • 1
  • 15
  • `Cloneable` is OK as long as you're careful, and you can ensure that everyone after you is just as careful too. `Copyable` could work and might not have some problems that `Cloneable` does, but it's a non-standard interface and folks will have to be just as careful. The other thing to do is use `Serializable`, just serialize to a memory buffer and deserialize the object back. Note this can create problems too though. – markspace Jun 29 '19 at 01:00
  • 1
    The *best* way to do this is to make your objects immutable if at all possible and to use withers or similar to perform mutations. – chrylis -cautiouslyoptimistic- Jun 29 '19 at 01:25
  • Unfortunately, immutable objects/controlling mutability is not a possibility. Due to the implementation of `DangerousService`, mutation is required on the object passed. – Anthony Jun 29 '19 at 01:32

1 Answers1

2

To solve your problem you need to polymorphically make a copy of dataObject like this:

T cloneOfDataObject = dataObject.clone();

and the issue is that Cloneable does not have a clone() method, so the above does not compile.

Given this premise, it does make sense to create your own Copyable interface that defines a clone() method so you can leverage already-implemented clone() methods (if they exist) on the classes of your data object. For maximum effectiveness this interface would need to be generic as well:

interface Copyable<T> {
    public T clone();
}

and the type bound:

public class DangerousCallWrapper<T extends Copyable<T>> 
    implements DangerousCaller<T> {
SDJ
  • 4,083
  • 1
  • 17
  • 35