itdoesntwork
itdoesntwork

Reputation: 4802

How can I uphold the principle of DRY in a specialized subclass?

I want to make a class with N fields, then subclass it with N-1 fields, and in the subclass the first field should be filled in the constructor. Here's the simplest example:

class Thing {
    protected int a;
    protected int b;

    public Thing(int a, int b) {
        this.a = a;
        this.b = b;
    }
}

class ThreeThing extends Thing {
    public ThreeThing(int b) {
        this.a = 3;
        this.b = b;
    }
}

Now, I want to make a method for all Things that respects immutability- it returns a new Thing with 1 added to b.

public Thing transform() {
    return new Thing(this.a, this.b + 1);
}

However, when this method is inherited into ThreeThing, the returned object is still a Thing instead of ThreeThing, so I'd have to override the method in ThreeThing. This wouldn't be a big problem, but in my project, there are going to be lots of specialized Things and I don't want to have to override the method in all of them if the behavior is intended to be the same.

Here are the possible solutions I have thought of, none of them satisfy me

Is there a way to do this without reflection or a better way to design the objects?

Upvotes: 4

Views: 94

Answers (2)

Nir Alfasi
Nir Alfasi

Reputation: 53535

Since the parent can't instantiate the child directly (because it doesn't "know" it) you can use reflection to do it:

class Thing {
    protected int a;
    protected int b;

    public void setB(int b) {
        this.b = b;
    }

    public void setA(int a) {
        this.a = a;
    }

    public Thing(){}

    public Thing(int a, int b) {
        this.a = a;
        this.b = b;
    }

    Thing copy() {                  // adding a copy method
        return new Thing(a, b);
    }

    public Thing transform() throws IllegalAccessException, InstantiationException {
        // Thing result = (Thing)this.getClass().newInstance(); // one way to do it (reflection)
        Thing result = copy(); // a better way to do it
        result.setA(a);
        result.setB(b+1);
        return result;
    }

    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        ThreeThing x = new ThreeThing(1);
        System.out.println(x.a + " : " + x.b);
        Thing y = x.transform();
        System.out.println(y.a + " : " + y.b);
    }
}

class ThreeThing extends Thing {

    public ThreeThing(){}

    ThreeThing(int b) {
        this.a = 3;
        this.b = b;
    }

    @Override
    Thing copy() {           // adding a copy method
        return new ThreeThing(b);
    }
}

That said, it's better to avoid using newInstance() and if you find yourself using it - you might want to back up a few steps and check the overall design and see if it can be improved. As an example, I added a copy() method which should be overridden in the child classes.

Upvotes: 1

Eran
Eran

Reputation: 393986

You can do it with just clone and no reflection.

Each non-abstract class of your hierarchy with have a clone method that creates an instance and passes it to another clone method that clones the members. Abstract classes in the hierarchy would only have the protected method that only clones the members.

So, for example, in the sub-class ThreeThing you will have :

  public Object clone ()
  {
    ThreeThing 
      thing = new ThreeThing ();

    clone (thing);

    return thing;
  }

  protected void clone (Thing other)
  {
    super.clone (other);

    ThreeThing 
      thing = (ThreeThing) other;
    thing.some_member = this.some_member;
    ....
  }

Upvotes: 1

Related Questions