簡體   English   中英

設計幫助! enum工廠變壓器中的java泛型!

[英]Design Help! java generics in a enum factory transformer!

我想知道我是否可以通過一種很好的方式來設計這個。 我將介紹我的方法,但我認為有更好的解決方案(因此,問題:))。

我想創建一個枚舉(清除選項並避免使用單例體系結構),它具有從另一個對象創建一個對象的訪問器。 但那些對象是非常靈活的。

可以將其視為限制此轉換的選項數量的一種方式。

讓我進入一個層次結構。 如果我要從各種各樣的對象轉到類似這樣的對象:

class Base {...}
class ValueA extends Base {...}
class ValueB extends Base {...}

我在考慮做這樣的事情:

public enum ValueTransformer{
  VALUE_A{

    @Override
    public <T> T createVo (Class<T> expectedRtn, Object obj) {
      ValueA retObj = null;

      if (expectedRtn == getReturnType ()) {
        if (obj != null && CanBeTranslatedToA.class == obj.getClass ()) {
          retObj = new ValueA ();
          /*...*/
        }
      }

      return retObj;
    }

    @Override
    public Class<ValueA> getReturnType () { return ValueA.class; }

  },
  VALUE_B {
    @Override
    public Class<ValueB> getReturnType () { return ValueB.class; }

    @Override
    public <T> T createVo (Class<T> expectedRtn, Object obj) {
      ValueB retObj = null;

      if (expectedRtn == getReturnType ()) {
        if (obj != null && CanBeTranslatedToB.class == obj.getClass ()) {
          retObj = new ValueB ();
          /*...*/
        }  else if (obj != null && AnotherClassForB.class = obj.getClass ()){
          retObj = new ValueB();
          /* ... */
        }
      }

      return retObj;
    }
  };

  public abstract <T> Class<T> getReturnType ();
  public abstract <T> T createVo (Class<T> expectedRtn, Object obj);
}

這是一個體面的設計嗎? 這個枚舉可能會增長,可以創建的ValueA和ValueB可能會發生變化(隨着sys的增長)。 在所有這些情況下,我都可以返回“ Base”,但這需要強制轉換和檢查。 我寧願沒有那個。

我是否有必要使用expectedRtn參數? 我應該使用泛型嗎? 我對Java相當陌生,因此我並不總是確定處理這種情況的最佳方法。

謝謝你的任何提示!!!!

這不是一個非常好的設計,我甚至無法分辨這個枚舉試圖完成的內容。 首先,您使用的是每個枚舉值實現的通用方法 ,這意味着該方法的調用者可以確定他們希望T為哪種類型...但這不是您想要的,因為這些方法實際上是有目的的關於它們將返回什么類型的對象。

Class<String> foo = ValueTransformer.VALUE_B.getReturnType();
String string = ValueTransformer.VALUE_A.createVo(String.class, "");

鑒於您的代碼,上述內容完全合法,但您的代碼實際上並未處理此問題。 通用方法不會像您認為的那樣做。

我覺得您真正想要的只是將特定類型的對象轉換為ValueAValueB類型的對象的簡單方法。 最簡單的方法就是讓每個可以通過這種方式轉換的類都提供一個對每個此類進行處理的方法:

public class CanBeTranslatedToB {
  ...

  public ValueB toValueB() {
    ValueB result = new ValueB();
    ...
    return result;
  }
}

然后,如果您具有CanBeTranslatedToB的實例,而不是這樣做:

 CanBeTranslatedToB foo = ...
 ValueB b = ValueTransformer.VALUE_B.createVo(ValueB.class, foo);

你只是這樣做:

CanBeTranslatedToB foo = ...
ValueB b = foo.toValueB();

就像枚舉版本那樣,這更加清晰且不易出錯。

如有必要,您可以做各種事情來toValueA()操作,例如制作一個定義toValueA()toValueB()方法的接口,以及制作幫助程序類以提供所有實現都需要使用的任何常見行為。 我看不到您所描述的枚舉有任何用處。

編輯:

如果您無法更改需要轉換為ValueB等的類的代碼,則有幾種選擇。 處理該問題的最簡單(可能是最好的)方法是在ValueAValueB添加工廠方法,例如:

// "from" would be another good name
public static ValueB valueOf(CanBeTranslatedToB source) {
  ...
}

public static ValueB valueOf(AnotherClassForB source) {
  ...
}

然后你可以寫:

CanBeTranslatedToB foo = ...
ValueB b = ValueB.valueOf(foo);

如果您不希望在ValueB上使用這些方法,則可以將它們放在另一個類中,並使用類似newValueB(CanBeTranslatedToB)方法名稱。

最后,另一種選擇是使用Guava並為每次轉換創建一個Function 這是最接近您原始設計的設計,但是它是類型安全的,並且可以與Guava提供的所有接受Function實用程序一起很好地工作。 您可以根據需要在類中收集這些Function實現。 以下是實現從FooValueB的轉換的單例的示例:

public static Function<Foo, ValueB> fooToValueB() {
  return FooToValueB.INSTANCE;
}

private enum FooToValueB implements Function<Foo, ValueB> {
  INSTANCE;

  @Override public ValueB apply(Foo input) {
    ...
  }
}

但是,我不會將其用作進行轉換的唯一方法...最好使用上面提到的static valueOf方法,並且僅在您的應用程序需要轉換以下內容的整個集合時提供此類Function這是為了方便對象很快就會出現。

關於泛型,Java沒有“真正的”泛型,在這種情況下可能既有益又有害。 當您在編譯時不知道要處理的對象類型時,使用泛型將很棘手。 如果使用此信息的代碼實際上知道通過調用ValueTransformer.ValueA.createVo應該獲得的對象類型,則應該誠實地轉換其返回值。 我希望通話看起來更像這樣:

MyTypeA myType = (MyTypeA)ValueTransformer.ValueA.createVo(sourceObject);

如果我從這個方法中得到了錯誤的類型,我寧願在這一行上看到一個Cast異常(問題確實發生了),而不是稍后的空指針異常。 這是正確的“快速失敗”練習。

如果您真的不喜歡顯式強制轉換,那么我看過一個很酷的技巧,可以讓您隱式地強制轉換這些內容。 我認為它是這樣的:

public abstract <T> T createVo (Object obj) {...}

MyTypeA myType = ValueTransformer.ValueA.createVo(sourceObject);

但是,我並不真正推薦這種方法,因為它仍然在運行時執行強制轉換,但是沒有人會通過查看您的用法代碼來懷疑這一點。

我可以看到你可能希望實現的一些目標:

  1. 對於給定Base類的所有對象,只有一個“事實來源”。
  2. 每次請求時都允許創建給定對象的實例。
  3. 具有類型安全性並避免在運行時進行鑄造。

除非你有其他要求我沒想到,看起來工廠會更好:

public class ValueFactory
{
    public ValueA getValueA(Object obj) {return new ValueA();} 
    public ValueB getValueB(Object obj) {return new ValueB();}
}

這滿足了上述所有要求。 此外,如果您知道生成ValueA對象所需的對象類型,則可以在輸入值上使用更明確的類型。

我花了一些時間,最終設法實現了基於enum的工廠,看起來像你在尋找什么。

這是我工廠的源代碼:

import java.net.Socket;

public enum EFactory {
    THREAD(Thread.class) {
        protected <T> T createObjectImpl(Class<T> type) {
            return (T)new Thread();
        }
    },
    SOCKET(Socket.class) {
        protected <T> T createObjectImpl(Class<T> type) {
            return (T)new Socket();
        }
    },
    ;

    private Class<?> type;

    EFactory(Class<?> type) {
        this.type = type;
    }

    protected abstract <T> T createObjectImpl(Class<T> type);

    public <T> T createObject(Class<T> type) {
        return assertIfWrongType(type, createObjectImpl(type));
    }

    public <T> T assertIfWrongType(Class<T> type, T obj) {
        if (!type.isAssignableFrom(obj.getClass())) {
            throw new ClassCastException();
        }
        return obj;
    }
}

這是我如何使用它。

Thread t1 = EFactory.THREAD.createObject(Thread.class);
String s1 = EFactory.THREAD.createObject(String.class); // throws ClassCastException

我個人不太喜歡這種實現方式。 枚舉定義為枚舉,因此無法在類級別進行參數化。 這就是必須將類參數(我的示例中的Thread和Socket)傳遞給工廠方法本身的原因。 同樣,工廠實現本身包含產生警告的強制轉換。 但是從另一方面來說,至少使用這個工廠的代碼足夠干凈並且不會產生警告。

暫無
暫無

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

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