簡體   English   中英

從構造函數中調用方法

[英]Calling methods from the constructor

我編寫了下面的代碼,如您所見,在構造函數中,我調用了一些方法來執行某些操作。 現在我要問的是,從構造函數中調用這些方法是一個好習慣還是將這些方法聲明為public並從類info中實例化一個對象,讓對象調用這些方法? 這有什么好的做法?

碼:

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);
}

TLDR在我看來,使用構造函數內部的方法是糟糕設計的標志。 如果你不是在尋找設計建議,那么答案是“沒有任何問題,從技術上講,只要你避免調用非最終方法”應該沒問題。 如果您正在尋找設計建議,請參閱下文。

我認為你的示例代碼根本不是好的做法。 在我看來,構造函數應該只接收與它相關的值,並且不需要對這些值執行任何其他初始化。 你無法測試你的構造函數是否與所有這些額外的步驟一起“工作” - 你所能做的就是構造對象並希望一切都以正確的狀態結束。 此外,您的構造函數最終會有多個更改原因,這違反了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);
}

所以,例如,這個構造函數正在加載一個文件,在XPath中解析它。如果我想創建一個RoadInfo對象,我現在只能通過加載文件並且不必擔心拋出異常來實現。 這個類現在變得非常難以進行單元測試,因為現在你不能孤立地測試this.initXPath()方法,例如 - 如果this.initXPath()this.method1()this.method2()有任何失敗,那么每個測試用例都會失敗。 壞!

我希望它看起來像這樣:

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);
  }
}

別介意你在這里至少有5個職責。

  • 構建與OS無關的路徑
  • 為緯度/經度構建XPath表達式
  • 創建XPath文檔
  • 檢索s - 無論是什么
  • 創建新的RoadInfo實例

這些責任中的每一個(除了最后一個)都應該分成它們自己的類(IMO),並讓RoadInfoFactory它們全部編排在一起。

構造函數的目的是建立類不變量,即將新創建的對象置於允許客戶端使用它們的狀態。 通常,對象在構造之后依賴於額外的初始化是不好的做法。 您要避免的是將這些內容寫入文檔中:

...在創建類X的實例后,記得總是調用initX() ,否則會發生不好的事情!

雖然在某些情況下很難避免它,但構造函數可能變得非常混亂。 例如,在構造函數中加載外部文件是有問題的。

在這些情況下,您可以做兩件事:

  1. 重寫您的構造函數,因此它需要文件的內容而不是名稱。 讓呼叫者進行加載。 主要區別在於您需要調用者創建對象之前執行某些操作,並且您可以使用構造函數的簽名來表達它: public RoadInfo(String cityName, Document cityDatabase, double lat, double lng) {...}當然,你可以更進一步,直接需要s的值,讓調用者進行XPath搜索。 請注意,所有這些步驟都會使班級承擔單一責任,這被視為一件好事。
  2. 但是現在您需要調用者在構建RoadInfo之前執行許多步驟。 這是您可以使用工廠的地方,這些工廠也會執行額外的初始化並返回完全構建的RoadInfo對象。

但最重要的是構造函數不能調用正在構造的可被覆蓋的對象的任何方法 調用私有方法是好的,呼吁公眾對方法this是不是一個好主意,除非方法或類本身被標記為final

如果你調用這樣的方法,那么重寫方法的類總是有可能會破壞你的功能,比如在構造完成之前將this暴露給外部世界。 這是一個例子:

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; 
    }
}

如果現在調用new Bar("x")Foo的構造函數將拋出異常,因為它認為參數無效。 Bar.processParam() this引用泄露給SomeOtherClass ,可能允許SomeOtherClass使用甚至不存在的Bar實例。

更典型地,需要大量初始化的類將通過工廠方法提供給客戶端。 構造函數通常限制太多 - 一個隨機的例子是無法使用try-catch包圍superthis開始調用。

如果提供公共工廠方法,則可以將構造函數設為私有。 構造函數只能像分配最終字段一樣輕松完成工作,工廠接管。 從長遠來看,這是一個更具前瞻性的設計。 許多公共圖書館不得不打破他們早期的API來引入允許其代碼增長的工廠。

從構造函數中調用一些方法是一個好習慣嗎?

可悲的是這是唯一的好答案,這取決於對象

如果該對象旨在保存信息,那么答案必定可能不是,盡量避免它,因為一個對象應該只做一件事

但是,如果對象是執行函數,那么通過調用方法等確保它已准備好執行該函數。例如,如果它是數據庫連接,那么您可能希望連接到數據庫構造時間,或者至少在連接池中注冊自己。

它,但是,好的做法是推遲任何潛在的慢的東西,你可以推遲,直到你需要它。 在我的數據庫示例中,您可能希望推遲與數據庫的實際連接,但您肯定會在連接池中注冊連接。

可悲的是 - 相反問題的答案:

從構造函數中調用一些方法是不好的做法?

也是因為類似的原因取決於對象

沒有好的做法,只是你不應該做的壞習慣。

在構造函數中調用方法時,這里有一些危險:

1)該方法可以被覆蓋,並且它的子類實現打破了由構造函數保護的類的約束,該工具不受你的控制。

class T {
    int v;

    T() {
        v = getValue();
    }

    int getValue() {
        return 1;
    }
}

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

當你調用new T() ,T's v假設為1,但是當你創建一個new Sub() ,'v'將被設置為-1,這可能會破壞T的約束,並且這會在不知不覺中發生。

2)半結構物體泄露,而它的狀態可能是非法的。

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)更多我不知道的東西......

如果你可以阻止所有這些,你可以做你想要的。

  • (盡量不要拋出異常。所以構造函數可以很好地初始化一個字段。)
  • 不要在構造函數中調用可覆蓋的方法。

關於構造函數中可覆蓋方法調用的缺陷:

(子)構造函數的評估是:

  • “歸零”所有字段( 0, null, 0.0, false
  • 調用超級構造函數(如果不在代碼中則隱式)
  • 調用所有字段初始化(字段聲明= ...)
  • 做其余的構造函數代碼

所以:

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);
    }
}

這種行為在水域中非常混亂,並且在這里進行由字段初始化立即覆蓋的分配。

在構造函數中經常看到的正常調用是一個setter,它可能有一些規范化代碼。 使該setter public final void setX(X x);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM