1

I think the title is self-descriptive but I will give an example to elaborate on my question. I have a DTO class with few fields (a CarDataTransferObj class in my example). In another class (let's call it class A) I need to create a new instance of that object few times, but with only one field updated (length field in my example). Given DTO must be immutable in class A. As there is "many" fields in the class CarDataTransferObj, I thought about following approach (to avoid repeating code in class A):

@Builder
public class CarDataTransferObj {

    private Integer id;
    private String color;
    private String manufacturer;
    private String model;
    private String uniqueIdNr;
    private Integer nrOfDoors;
    private EngineType engineType;
    private Integer length;
    private Integer safetyLevel;

    public static CarDataTransferObj newInstanceWithUpdatedLength(final CarDataTransferObj car, final Integer newLength) {
        return CarDataTransferObj.builder()
                .id(car.getId())
                .color(car.getColor())
                .manufacturer(car.getManufacturer())
                .model(car.getModel())
                .uniqueIdNr(car.getUniqueIdNr())
                .nrOfDoors(car.getNrOfDoors())
                .engineType(car.getEngineType())
                .length(newLength)
                .safetyLevel(car.getSafetyLevel())
                .build();

    }
}

For me it smells like a little anti-pattern usage of static factory methods. I am not sure whether it's acceptable or not, hence the question.

Is using static factory method in the presented way an anti-pattern, and should be avoided ?

user3529850
  • 1,632
  • 5
  • 32
  • 51
  • It looks like you're using Lombok. They actually have an experimental feature exactly for such purpose https://projectlombok.org/features/experimental/Wither. – yegodm Nov 11 '18 at 01:25

4 Answers4

2

In my searching, I didn't come across anyone calling this1 an anti-pattern.

However, it is clear that if you try to do this using a classic builder that is not specifically implemented to support this mode of operation .... it won't work. For instance, the example CarBuilderImpl in the Wikipedia article on the Builder design pattern puts the state into an eagerly created Car instance. The build() method simply returns that object. If you tried to reuse that builder in the way that you propose, you would end up modifying a Car that has already been built.

There is another problem you would need to worry about. In we modified the Wikipedia CarBuilder example to add actual wheels (rather than a number of wheels) to the Car being built, we have to worry about creating cars that share the same wheels.

You could address these things in a builder implementation, but it is unclear whether the benefits out-weigh the costs.


If you then transfer this thinking to doing this using a factory method, you come to a slightly different conclusion.

  • If you are doing this as a "one-off", that's probably OK. You have a specific need, the code is clunky ... but so is the problem.

  • If you needed to do this for lots of different parameters, or combinations of parameters, this is not going to scale.

  • If the objects that are created are mutable, then this approach is could be problematic in a multi-threaded environment depending on how you control access to the objects you are using as templates.


1 - There are no clear measurable criteria for whether something is an anti-pattern or not. It is a matter of opinion. Admittedly, for many anti-patterns, there will be wide-scale agreement on that opinion.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • Related: [What is an anti-pattern?](https://stackoverflow.com/questions/980601/what-is-an-anti-pattern) – jaco0646 Nov 13 '18 at 20:21
2

It seems a little inefficient to construct an entirely new instance via a builder every time you want to make a new copy with a small modification. More significantly, it sounds like the places where you need the class to be immutable are isolated to places like class A. Why not try something like this:

public interface ICarDataTransferObject {
    public Integer GetId();
    public String GetColor();
    public String GetManufacturer();
    public String GetModel();
    public String GetUUID();
    public Integer GetDoorCount();
    public EngineType GetEngineType();
    public Integer GetLength();
    public Integer GetSafteyLevel();
}


public class CarDataTransferObject Implements ICarDataTransferObject {
    private Integer _id;
    private String _color;
    private String _manufacturer;
    private String _model;
    private String _uniqueIdNr;
    private Integer _nrOfDoors;
    private EngineType _engineType;
    private Integer _length;
    private Integer _safetyLevel;

    public Integer GetId() { return _id; }
    public void SetId(Integer id) { _id = id; }
    public String GetColor() { return _color; }
    public void SetColor(String color) { _color = color; }
    public String GetManufacturer() { return _manufacturer; }
    public void SetManufacturer(String manufacturer) { _manufacturer = manufacturer; }
    public String GetModel() { return _model; }
    public void SetModel(String model) { _model = model; }
    public String GetUUID() { return _uniqueIdNr; }
    public void SetUUID(String uuid) { _uniqueIdNr = uuid; }
    public Integer GetDoorCount() { return _nrOfDoors; }
    public void SetDoorCount(Integer count) { _nrOfDoors = count; }
    public EngineType GetEngineType() { return _engineType; }
    public void SetEngineType(EngineType et) { _engineType = et; }
    public Integer GetLength() { return _length; }
    public void SetLength(Integer length) { _length = length; }
    public Integer GetSafteyLevel() { return _safetyLevel; }
    public void SetSafteyLevel(Integer level) { _safteyLevel = level; }

    public CarDataTransferObject() {}
    public CarDataTransferObject(ICarDataTransferObject other) { ... }

    public ReadOnlyCarDataTransferObject AsReadOnly() { 
        return ReadOnlyCarDataTransferObject (this);
        }
    }
}

public class ReadOnlyCarDataTransferObject Implements ICarDataTransferObject  {
    private ICarDataTransferObject _dto = null;

    public Integer GetId() { return _dto.GetId(); }
    public String GetColor() { return _dto.GetColor(); }
    public String GetManufacturer() { return _dto.GetManufacturer(); }
    public String GetModel() { return _dto.GetModel(); }
    public String GetUUID() { return _dto.GetUUID(); }
    public Integer GetDoorCount() { return _dto.GetDoorCount(); }
    public EngineType GetEngineType() { return _dto.GetEngineType(); }
    public Integer GetLength() { return _dto.GetLength(); }
    public Integer GetSafteyLevel() { return _dto.GetSafteyLevel; }

    public ReadOnlyCarDataTransferObject (ICarDataTransferObject other) {
        _dto = other;
    }
}

Now when you want class A to have a copy no one can modify, just use the copy constructor and only expose a ReadOnly version of that copy.

public class A {
    ICarDataTransferObject _dto;
    ReadOnlyCarDataTransferObject _readOnlyDTO;

    public ICarDataTransferObject GetDTO() { return _readOnlyDTO; }

    public A(ICarDataTransferObject dto) {
        _dto = new CarDataTransferObject(dto);
        _readOnlyDTO = new ReadOnlyCarDataTransferObject(_dto);
    }
}

You commonly see this approach in .NET applications.

1

While it is debatable whether your static method is an anti-pattern or not, it surely won't scale for combinations of different attributes. Nonetheless, even if it's not an anti-pattern, I think there is a better way to accomplish what you need.

There's a variant of the traditional builder pattern that, instead of creating a new empty builder, accepts an already built object and creates an already initialized builder. Once you create the builder this way, you simply change the length attribute in the builder. Finally, build the object. In plain code (no Lombok, sorry) it could be like this:

public class CarDataTransferObj {

    private Integer id;
    private String color;
    // other attributes omitted for brevity
    private Integer length;

    // Private constructor for builder
    private CarDataTransferObj(Builder builder) {
        this.id = builder.id;
        this.color = builder.color;
        this.length = builder.length;
    }

    // Traditional factory method to create and return builder
    public static Builder builder() {
        return new Builder();
    }

    // Factory method to create and return builder initialized from an instance
    public static Builder builder(CarDataTransferObj car) {
        Builder builder = builder();
        builder.id = car.id;
        builder.color = car.color;
        builder.length = car.length;
        return builder;
    }

    // getters

    public static class Builder {
        private Integer id;
        private String color;
        private Integer length;

        private Builder() { }

        public Builder withId(Integer id) { this.id = id; return this; }

        public Builder withColor(String color) { this.color = color; return this; }

        public Builder withLength(Integer length) { this.length = length; return this; }

        public CarDataTransferObj build() {
            return new CarDataTransferObj(this);
        }
    }
}

Now with all this infrastructure in place, you can do what you want as easy as:

CarDataTransferObj originalCar = ... // get the original car from somewhere

CarDataTransferObj newCar = CarDataTransferObj.builder(originalCar)
    .withLength(newLength)
    .build();

This approach has the advantage that it scales well (it can be used to change any combination of parameters). Maybe all this builder's code seems boilerplate, but I use an IntelliJ plugin to create the builder with two keystrokes (including the variant factory method that accepts a built instance to create an initialized builder).

fps
  • 33,623
  • 8
  • 55
  • 110
0

I'm still new to java but..
I guess making a copy method which takes the CarDataTransferObj object variables and sets their values to another CarDataTransferObj object variables and changing the the length using it's setter method would be better idea
Example:

public class CarDataTransferObj {

    private Integer id;
    private String color;
    private String manufacturer;
    private String model;
    private String uniqueIdNr;
    private Integer nrOfDoors;
    private EngineType engineType;
    private Integer length;
    private Integer safetyLevel;

    public void Copy(CarDataTransferObj copy) { //Could add another parameter here to be the new length
            copy.setId(id);
            copy.set(color);
            copy.setManufacturer(manufacturer);
            copy.setModel(model);
            copy.setUniqueIdNr(uniqueIdNr));
            copy.setNrOfDoors(nrOfDoors));
            copy.setEngineType(engineType));
            copy.setLength(length);
            copy.setSafetyLevel(safetyLevel));
    }
}
public class SomeOtherClass {
    CarDataTransferObj car1 = new CarDataTransferObj(); //Using this way made you able to use the constructor for a more useful thing
    //You set the variables you want for car1 here
    CarDataTransferObj car2 = new CarDataTransferObj();
    car1.Copy(car2)
    car2.setLength(newLength) //Set the new length here
}
  • Couple things here: 1. You are using a bunch of setter methods you didn't define (but we know what you mean). 2. How do you ensure that in class A no one can modify the DTO? I defined a ReadOnlyCarTransferObject in my answer to solve this. – Ryan Pierce Williams Nov 11 '18 at 01:35
  • I actually missed that part...but i think that would be declaring the setters private. And good idea to make a ReadOnly instance btw – Omar Abdul'Azeez Nov 11 '18 at 02:11
  • If the setters were private then SomeOtherClass wouldn't be able to modify the length ;) – Ryan Pierce Williams Nov 11 '18 at 02:30
  • @Ryan I guess 8 hours of debugging messed with my head....i thought you would never say this because it can through **car1.varName** but now i realized they have to be private – Omar Abdul'Azeez Nov 11 '18 at 02:43