首頁  >  文章  >  Java  >  Java 怎麼更好地管理你的對象

Java 怎麼更好地管理你的對象

little bottle
little bottle轉載
2019-04-30 15:11:541878瀏覽

本篇文章以說故事的形式講述管理對象的方法,具有一定價值,有興趣的朋友可以看看一下,希望能夠讓你對類的認識更加深刻。

有一天晚上我腦中突然冒出來一個問題:「怎麼管理我們程式碼中的物件」。

小弈是剛工作時的我,他說:透過 new 來創建一個物件然後直接使用就好了啊。

public class HelloWorld {
public void hello() {
System.out.println("hello world!");
}
}
HelloWorld helloWorld = new HelloWorld();
helloWorld.hello();

你們看,我有一個 HelloWorld 類,我用 new 就能直接創建一個對象,然後就能使用這個對像中所有的方法了,多簡單啊。

二弈是工作兩年的我,他一臉鄙視的對小弈說,你別整天HelloWorld 好不好,還有啊,除了new 你就不會其他的了,能不能有點追求啊?

小弈對二弈說那你說除了 new 還有什麼辦法啊?

二弈說可以透過 Class 的 newInstance 或 Constructor 的 newInstance 來建立物件實例啊。

不過你得記住,Class 的 newInstance 只能對那些擁有可見的(Accessible)無參構造函數的類,才能進行物件的實例化,而 Constructor 就沒有這些限制。

大弈是工作三年的我,他說,雖然你們的方法都可以用來創建對象,但都還是手動創建的,太原始了,生產力太低。

工欲善其事,必先利其器,我們也得找個高效率的生產力工具。 IOC 容器你們了解吧?

以前我們在一個對像中如果要調用另外一個對象的方法時,都是通過new 或者反射來手動創建該對象,但是每次都這樣做太累了,並且類之間的耦合也很高。

透過IOC 容器,我們可以把所有的物件交給容器來管理,在使用之前只需要定義一下對象,然後再使用到該物件時,IOC 容器就會幫我們把該物件初始化好,這樣是不是更方便呢?

大弈說完,舉了一個例子:

@Bean
public class RegisterService {
public void register() {
// do register
}
}
@Bean
public class LoginService {
public void login() {
// do login
}
}
@Bean
public class HelloWorld {
@Autowired
private RegisterService registerService;
@Autowired
private LoginService loginService;
public void hello() {
// 注册
registerService.register();
// ...
// 登录
loginService.login();
}
}

IOC 容器透過一種叫Bean 的註解,在系統啟動時掃描所有透過Bean 標註的類別,對這些類別進行實例化,然後將所有的物件都保存在容器中。再掃描所有透過 Autowired 標註的屬性或方法,從容器中找到與之符合(透過名稱或型別等)的物件將特定的物件賦值給這些屬性。這樣我們就可以直接將這些物件拿來使用了,作為一個伸手黨是不是很幸福啊。

老弈是工作五年的我,他聽了大弈的話後,提出了一個問題,對於新的專案可以使用這種IOC 的容器,可是對於那些遺留的老專案來說,要使用IOC 來改造是不太符合實情的。

我舉個例子,在一個遺留的老專案中,有一個核心的介面Handler:

public interface Handler<REQ, RES> {
    RES handle(REQ request);
}

Handler 介面有很多的實作類,我們需要對不同的請求來呼叫不同的Handler 實作類別進行處理,如果用IOC 容器來管理這些實作類,顯然不太合適,因為我們處理之前是不知道該用哪個Handler 實作類別的。

大弈想了想,如果Handler 介面只有幾個固定的實作類,並且在使用時只會使用一個來進行處理,那麼倒是可以在啟動前透過配置的方式來確定具體使用哪種Handler ,例如可以透過@Conditional 根據某些條件來確定載入具體的對象,但是這種要在使用時才能確定Handler 對象的類型確實比較棘手。

老弈看大家都不說話了,就繼續說了下去。

為了要在呼叫方法時使用不同的Handler 來處理不同的而請求,需要確定兩種類,一種是請求類,一種是處理類,並且要讓請求類和處理類一一對應起來。

假設我們的請求類別是一個 Packet 類,每個特定的請求類別都繼承自這個基底類別。

那麼想要確定每一個具體的Packet 是什麼類型的,可以有很多種方法,可以為每個Packet 取一個唯一的名字,例如:

public abstract class Packet {
    public abstract String name();
}

也可以為每一個Packet 指定一個標誌,例如:

public abstract class Packet {
    public abstract int symbol();
}

但是不管哪種方式,每一個Packet 的實作類別都需要實作抽象類別中的方法,來「標誌」自己是哪一種Packet。

我們以第二種方式舉例,假設我們有兩個具體的Packet:

public class RegisterPacket extends Packet {
// 注册所需要的其他参数
int symbol() {
return 1;
}
}
public class LoginPacket extends Packet {
// 登录所需要的其他参数
int symbol() {
return 2;
}
}

這樣當我們接收到request 物件時,透過呼叫request.symbol() 就知道這個request是哪種類型的Packet 了,這時只要找到具體的Handler 實作類別來處理就可以了。

那請求類別已經可以確定了,要怎麼確定 Handler 處理類別呢?我們是否也可以在Handler 介面中定義一個symbol 方法呢,像這樣:

public interface Handler<REQ, RES> {
    int symbol();
    RES handle(REQ request);
}

這樣的話,只要在所有的實作類別中實作symbol 方法來標註該Handler 是用來處理何種request 的即可。

public RegisterHandler implements Handler<RegisterPacket, RES> {
    int symbol(){
    return 1;
    }
    RES handle(RegisterPacket request){
    // 具体的处理方法
    }
}
public LoginHandler implements Handler<LoginPacket, RES> {
    int symbol(){
    return 2;
    }
    RES handle(LoginPacket request){
    // 具体的处理方法
    }
}

最後把所有的Handler 實作類別都實例化後保存在一個HandlerProvider 中,要使用時再到HandlerProvider 中來獲取即可:

public interface HandlerProvider {
    Handler getHandler(int symbol);
}

那怎樣獲取到所有的Handler的實作類別呢,有兩種方法。

一种是通过 ServiceLoader.load(Handler.class) 的方式来获取,不过这种通过 spi 的方式需要在项目的 resources/META-INF/services/ 目录下创建一个 xxx.Handler 的文件,并在文件中将所有 Handler 的实现类的完全类限定符列出来。

另一种比较简单的方式是通过扫描的方式,获取到所有 Handler 的实现类。

到现在为止,我们的实现还算可以,但是有一个问题,那就是在 Handler 接口中我们增加了一个方法,这样做就对原来的代码进行了侵入。

为了让原来的代码保持不变,我们可以定义一个注解来标注在所有的 Handler 实现类上,比如这样:

@Symbol(1)
public RegisterHandler implements Handler<RegisterPacket, RES> {
    RES handle(RegisterPacket request){
    // 具体的处理方法
    }
}
@Symbol(2)
public LoginHandler implements Handler<LoginPacket, RES> {
    RES handle(LoginPacket request){
    // 具体的处理方法
    }
}

这样就将 Handler 的实现和标注进行了解耦了,也可以通过扫描 @Symbol 注解来获取到所有的 Handler 实现类,不过这样做的缺点就是假如我忘记对某个 Handler 实现类添加 @Symbol 注解,到时候就获取不到该 Handler 了。

大家听完老弈的话之后,都陷入了沉思,我靠,还可以这么玩,真有趣。

这时候现在的我,也就是逅弈,说了一句,如果我有一个接口,他只有几个固定的实现类,我不想搞那一套那么重的实现方式,但是我也需要动态的获取实现类来对请求进行处理,那我该怎么办呢?

比如我有一个序列化的接口,如下所示:

public interface Serializer {
    byte[] serialize(Packet packet);
}

然后只有五种具体的序列化的实现类,如下所示:

public class JdkSerializer implements Serializer {
@Override
    public byte[] serialize(Packet packet) {
    // 具体的序列化操作
    }
}
public class FastJsonSerializer implements Serializer {
@Override
    public byte[] serialize(Packet packet) {
    // 具体的序列化操作
    }
}
public class HessianSerializer implements Serializer {
@Override
    public byte[] serialize(Packet packet) {
    // 具体的序列化操作
    }
}
public class KryoSerializer implements Serializer {
@Override
    public byte[] serialize(Packet packet) {
    // 具体的序列化操作
    }
}
public class ProtoStuffSerializer implements Serializer {
@Override
    public byte[] serialize(Packet packet) {
    // 具体的序列化操作
    }
}

那么我们该怎么确定使用哪种序列化方式对参数 packet 进行序列化呢?

使用老弈刚刚说的那一套也确实能够实现,不过太麻烦了,又得对 Packet 定义 symbol,又得对 Hander 实现类进行标注,还得扫描所有的实现类。

我只有五个实现类,不需要搞那么麻烦的。

其实很简单,只需要定义一个枚举类,表示序列化的算法,然后对 Packet 增加一个 algorithm 方法用来表示,使用何种序列化算法,如下所示:

public enum SerializeAlgorithm {
    JDK((byte) 1),
    FAST_JSON((byte) 2),
    HESSIAN((byte) 3),
    KRYO((byte) 4),
    PROTO_STUFF((byte) 5);
    private byte type;
    SerializeAlgorithm(byte type) {
        this.type = type;
    }
}
public abstract class Packet implements Serializable {
public abstract byte algorithm();
}

然后定义一个 SerializerChooser 根据不同的算法选择不同的 Serializer 实现类即可:

public interface SerializerChooser {
    Serializer choose(byte algorithm);
}

因为根据算法是可以知道对应的序列化接口的,所以就没有必要去扫描了,直接把几种序列化的实现类枚举出来即可,对象的实例可以使用单例模式,如下所示:

public class DefaultSerializerChooser implements SerializerChooser {
    private DefaultSerializerChooser() {
    }
    public static SerializerChooser getInstance() {
        return Singleton.get(DefaultSerializerChooser.class);
    }
    @Override
    public Serializer choose(byte algorithm) {
        SerializeAlgorithm serializeAlgorithm = SerializeAlgorithm.getEnum(algorithm);
        switch (serializeAlgorithm) {
            case JDK: {
                return Singleton.get(JdkSerializer.class);
            }
            case FAST_JSON: {
                return Singleton.get(FastJsonSerializer.class);
            }
            case HESSIAN: {
                return Singleton.get(HessianSerializer.class);
            }
            case KRYO: {
                return Singleton.get(KryoSerializer.class);
            }
            case PROTO_STUFF: {
                return Singleton.get(ProtoStuffSerializer.class);
            }
            default: {
                return null;
            }
        }
    }
}

我说完后,大家又一次陷入了沉思,我知道大家都在思考,他们会在每一次思考中获得进步和成长,正如我在思考后得到成长一样。

小鸟总有一天会成长为老鸟,我还走在成长的路上。

以上是Java 怎麼更好地管理你的對象的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:oschina.net。如有侵權,請聯絡admin@php.cn刪除