Calling methods from the constructor

I wrote the code below, and as you can see, in the constructor I call some methods to perform certain operations. And now what I'm asking is, is there a good practice to call these methods from the constructor OR declare these methods as public and instantiate the object from the class information and let the object call those methods? What's a good practice for this?

Code:

class Info {
public RoadInfo(String cityName, double lat, double lng) throws FileNotFoundException, SAXException, IOException, XPathExpressionException {
    // TODO Auto-generated constructor stub
    this.cityname = cityName;
    this.lat = lat;
    this.lng = lng;

    this.path = "c:"+File.separatorChar+this.cityname+".xml";

    System.out.println(path);

    this.initXPath();
    this.method1()
    this.method2()
    ..

    this.expr = "//node[@lat='"+this.lat+"'"+"]/following-sibling::tag[1]/@v";
    this.xPath.compile(this.expr);
    String s = (String) this.xPath.evaluate(this.expr, this.document, XPathConstants.STRING);
    System.out.println(s);
}

      

+3


source to share


6 answers


TL; DR In my opinion, using methods inside a constructor is a sign of bad design. If you're not looking for design tips, then the answer "no, there is nothing wrong with that, technically, as long as you avoid using unconfigured methods" should do your best. If you're looking for design tips, see below.

I think your example code is not good practice. In my opinion, a constructor should only receive values โ€‹โ€‹that are relevant to it, and there is no need to do any other initialization of those values. There is no way to test that your constructor "works" with all these little extra steps - all you can do is build the object and hope that everything ends up in the correct state. Also, your constructor ends up with more than one change cause that violates SRP.

class Info {
public RoadInfo(String cityName, double lat, double lng) throws FileNotFoundException, SAXException, IOException, XPathExpressionException {
    // TODO Auto-generated constructor stub
    this.cityname = cityName;
    this.lat = lat;
    this.lng = lng;

    this.path = "c:"+File.separatorChar+this.cityname+".xml";

    System.out.println(path);

    this.initXPath();
    this.method1()
    this.method2()
    ..

    this.expr = "//node[@lat='"+this.lat+"'"+"]/following-sibling::tag[1]/@v";
    this.xPath.compile(this.expr);
    String s = (String) this.xPath.evaluate(this.expr, this.document, XPathConstants.STRING);
    System.out.println(s);
}

      

So, for example, this constructor loads a file by parsing it into XPath .. If I ever want to create an object RoadInfo

, now I can only do so by loading the files and having to worry about the exceptions. unit test, because now you cannot test the method this.initXPath()

separately, for example - if this.initXPath()

, this.method1()

or this.method2()

have any failures, then each of your test cases will fail. Poorly!

I would prefer it to look something like this:



class RoadInfoFactory {
  public RoadInfo getRoadInfo(String cityName, double lat, double lng) {
    String path = this.buildPathForCityName(cityName);
    String expression = this.buildExpressionForLatitute(lat);
    XPath xpath = this.initializeXPath();
    XDocument document = ...;

    String s =  (String) xpath.evaluate(expression, document, XPathConstants.STRING);
    // Or whatever you do with it..
    return new RoadInfo(s);
  }
}

      

Never mind that you have at least 5 responsibilities here.

  • Build OS-neutral path
  • Building an XPath Expression for Latitude / Longitude
  • Create XPath Documentation
  • Get it s

    - whatever it is
  • Create a new instance RoadInfo

Each of these responsibilities (except the last one) should be divided into their own classes (IMO) and have to RoadInfoFactory

organize them all together.

+6


source


The purpose of a constructor is to set class invariants, that is, to put a newly created object into a state that then allows clients to use them. In general, it is bad practice for an object to rely on additional initialization after it has built it. What you want to avoid is to write things in the documentation like:

... after instantiating the class X

, remember to ALWAYS call initX()

, or there will be bad things!

Although in some cases this is difficult to avoid and the constructor can get quite messy. For example, loading external files in the constructor is problematic.

In these cases, you can do two things:

  • Rewrite your constructor to require the contents of the file instead of the name. Let the caller download. The main difference is that you require the caller to do something before the object has been created and you can express it with your constructor's signature: public RoadInfo(String cityName, Document cityDatabase, double lat, double lng) {...}

    Of course you can go even further and require a value s

    and let the caller do the lookup XPath. Note that all of these steps move the class to the same responsibility, which is considered a good thing.
  • But now you need the caller to complete many steps before they can create yours RoadInfo

    . You can use factories here that do this extra initialization as well and return fully constructed objects RoadInfo

    .



Most importantly, however, the constructor should not call any method on the object under construction that can be overridden . Calling private methods is fine, calling public methods is this

not a good idea unless the methods or the class itself are marked as final

.

If you call a method like this, there is always a chance that the class that overrides the method is doing something that breaks your functionality, such as exposing it this

to the outside world before construction is complete. Here's an example:

public abstract class Foo {
    public Foo(String param) {
       if (this.processParam(param) == null)
          throw new IllegalArgumentException( "Can't process param.");
    }

    protected abstract processParam(String param);
}

public class Bar extends Foo {
    public Bar(String param) {super(param);}

    protected processParam(String param) {
        SomeOtherClass.registerListener(this); // Things go horribly wrong here
        return null; 
    }
}

      

If you now call new Bar("x")

, the constructor Foo

throws an exception because it considers this parameter to be invalid. But a Bar.processParam()

link this

to leaked SomeOtherClass

, potentially allowing SomeOtherClass

an instance to be used Bar

that shouldn't even exist.

+5


source


More typically, classes that require a lot of initialization will be exposed to the client using a factory method. Constructors are often too restrictive, an accidental example is the inability to surround the initial call super

or this

using try-catch

.

If you provide a public factory method, you can make the constructor private. The constructor can only do light work like assigning final fields, and the factory takes over. This is ultimately a more robust design. Many public libraries have had to break their earlier API to implement factories that allow their code to be extended.

+4


source


Is it okay to use some methods from the constructor?

Unfortunately, the only good answer to this question is: it depends on the object.

If the object is meant to hold information , then the answer should probably be no, try to avoid that, because the object should really only do one thing .

If, however, the object exists to execute a function , then be sure to make sure it is ready to execute that function by calling methods, etc. If, for example, it is a database connection, then you might want to connect to the database at build time, or at least register yourself in the connection pool.

It is good practice, however, to postpone any potentially slow stuff that you may want to postpone until you need it. In my example database, you can defer the actual connection to the database, but you will make sure to register the connection in the connection pool.

Unfortunately - the answer to another question:

Is it bad practice to call some methods from the constructor?

Also depends on the object for the same reasons.

+3


source


There is no good practice, just bad practice that you shouldn't do.

When you call a method inside a constructor, some dangerous ones here:

1) a method can be overwritten and its subclassing break breaks a constructor-protected constraint of your class, an implementation out of your control

class T {
    int v;

    T() {
        v = getValue();
    }

    int getValue() {
        return 1;
    }
}

class Sub extends T {
    @Override
    int getValue() {
        return -1;
    }
}

      

here T v is supposed to be 1 when you call new T()

, but when you create new Sub()

, 'v' will be set to -1, which could violate the T constraint, and this happens unconsciously.

2) a semi-constructed object has leaked, while its status may be illegal.

class T {
    int a, b;

    T(C c) {
        // status of "this" is illegal now, but visible to c
        c.calc(this);
        a = 1;
        b = 2;
    }
}

class C {
    int calc(T t) {
        return t.a / t.b;
    }
}

      

3) something more that I don't know ...

if you can prevent them all, you can do what you want.

+2


source


  • (Try not to throw exceptions, so the constructor can initialize the field nicely.)
  • Don't call overridden methods in the constructor.

About the errors of calling the overridden method in the constructor:

Constructor (child) evaluation:

  • "Zero" all fields ( 0, null, 0.0, false

    )
  • Super constructor call (implicit if not code)
  • Call all field initializations (field declarations with = ...)
  • The rest of the constructor code

So:

class A {
    A() { f(); }
    protected void f() { }
}

class B implements A {
    String a;
    String b = null;
    String c = "c";

    B() {
        //[ a = null, b = null, c = null; ]
        //[ super();
        //    B.f();
        //]
        //[ b = null; ]
        //[ c = "c"; ]

        // "nullA" - null - "c"
        System.out.printf("end %s - %s - %s%n", a, b, c);
    }

    @Override
    protected void f() {
        System.out.printf("enter f : %s - %s - %s%n", a, b, c);
        // null - null - null
        a += "A";
        b += "B";
        c += "C";
        // "nullA" - "nullB" - "nullC"
        System.out.printf("leave f : %s - %s - %s%n", a, b, c);
    }
}

      

This behavior is pretty confusing, and assignments are made here that are immediately overwritten by field initialization.

A common call often seen in constructors is a setter, which may have some normalization code. Make this setter public final void setX(X x);

.

0


source







All Articles