Maison  >  Article  >  Java  >  Comment mieux gérer vos objets en Java

Comment mieux gérer vos objets en Java

little bottle
little bottleavant
2019-04-30 15:11:541927parcourir

Cet article explique la méthode de gestion des objets sous forme de narration, qui a une certaine valeur. Les amis intéressés peuvent y jeter un œil. J'espère que cela vous permettra de mieux comprendre les cours.

Une nuit, une question m'est soudainement venue à l'esprit : "Comment gérer les objets dans notre code".

Xiao Yi était moi quand j'ai commencé à travailler. Il a dit : Créez simplement un objet via new et utilisez-le directement.

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

Écoutez, j'ai une classe HelloWorld, je peux créer directement un objet en utilisant new, et ensuite je peux utiliser toutes les méthodes de cet objet.

Eryi, c'est moi qui travaille depuis deux ans. Il a dit à Xiaoyi avec un air méprisant, s'il te plaît, ne dis pas Hello World toute la journée, d'accord. De plus, à part New, tu ne sais rien ? sinon, peux-tu être un peu Poursuivre ?

Xiaoyi a dit à Eryi, que peux-tu faire d'autre à part du nouveau ?

Eryi a dit que vous pouvez créer des instances d'objet via newInstance of Class ou newInstance of Constructor.

Mais vous devez vous rappeler que la newInstance de Class ne peut instancier que des objets pour les classes qui ont des constructeurs visibles (accessibles) sans paramètre, alors que Constructor n'a pas ces restrictions.

Dayi, qui travaille pour moi depuis trois ans, a déclaré que bien que vos méthodes puissent être utilisées pour créer des objets, elles sont toutes créées manuellement, ce qui est trop primitif et la productivité est trop faible.

Si un travailleur veut bien faire son travail, il doit d'abord affûter ses outils. Il faut également trouver un outil de productivité efficace. Connaissez-vous les conteneurs IOC ?

Dans le passé, lorsque nous voulions appeler une méthode d'un autre objet dans un objet, nous créions manuellement l'objet par le biais d'une nouvelle ou d'une réflexion, mais faire cela à chaque fois était trop fatiguant, et le couplage entre les classes aussi très élevé.

Grâce au conteneur IOC, nous pouvons remettre tous les objets au conteneur pour la gestion. Il nous suffit de définir l'objet avant de l'utiliser, puis lorsque l'objet est utilisé, le conteneur IOC nous aidera à initialiser le. objet , n'est-ce pas plus pratique ?

Après que Da Yi ait fini de parler, il a donné un exemple :

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

Le conteneur IOC utilise une annotation appelée Bean pour analyser toutes les classes annotées par Bean au démarrage du système et instancie ces classes. , puis enregistrez tous les objets dans le conteneur. Analysez ensuite toutes les propriétés ou méthodes marquées par Autowired, recherchez les objets correspondants (par nom ou type, etc.) dans le conteneur et attribuez des objets spécifiques à ces propriétés. De cette façon, nous pouvons utiliser ces objets directement. N'est-ce pas très heureux d'être une fête main dans la main ?

Lao Yi travaille pour moi depuis cinq ans. Après avoir écouté les paroles de Da Yi, il a soulevé une question. Ce type de conteneur IOC peut être utilisé pour de nouveaux projets, mais pour ces anciens projets, c'est le cas. Il n’est pas réaliste d’utiliser l’IOC pour la transformation.

Permettez-moi de vous donner un exemple. Dans un ancien projet hérité, il existe une interface principale Handler :

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

L'interface Handler a de nombreuses classes d'implémentation. Nous devons en appeler différentes pour différentes requêtes. .Les classes d'implémentation du gestionnaire sont utilisées pour le traitement.Si des conteneurs IOC sont utilisés pour gérer ces classes d'implémentation, cela n'est évidemment pas approprié car nous ne savons pas quelle classe d'implémentation du gestionnaire utiliser avant le traitement.

Dayi y a pensé, si l'interface du Handler n'a que quelques classes d'implémentation fixes et qu'une seule sera utilisée pour le traitement, alors elle peut être déterminée via la configuration avant le démarrage. Une sorte de Handler, par exemple. vous pouvez utiliser @Conditional pour déterminer l'objet spécifique à charger selon certaines conditions, mais il est vraiment délicat de déterminer le type de l'objet Handler lorsqu'il est utilisé.

Lao Yi a vu que tout le monde était silencieux, alors il a continué.

Afin d'utiliser différents gestionnaires pour gérer différentes requêtes lors de l'appel de méthodes, deux classes doivent être déterminées, l'une est la classe de requête et l'autre est la classe de traitement, et la classe de requête et la classe de traitement doivent être identifiées. un par un Correspondre.

Supposons que notre classe de requête est une classe Packet et que chaque classe de requête spécifique hérite de cette classe de base.

Il existe donc de nombreuses façons de déterminer le type de chaque paquet spécifique. Vous pouvez donner à chaque paquet un nom unique, par exemple :

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

Vous pouvez également donner à chaque paquet un nom unique A. Packet spécifie un indicateur, par exemple :

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

Mais quelle que soit la méthode, chaque classe d'implémentation de Packet doit implémenter la méthode dans la classe abstraite pour "marquer" de quel type de paquet il s'agit.

Prenons la deuxième méthode comme exemple, en supposant que nous ayons deux paquets spécifiques :

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

De cette façon, lorsque nous recevrons l'objet de requête, nous connaîtrons la requête en appelant request.symbol () De quel type de paquet s'agit-il ? Pour le moment, il vous suffit de trouver la classe d'implémentation spécifique du gestionnaire pour le gérer.

La classe de requête a été déterminée. Comment déterminer la classe de traitement du Handler ? Pouvons-nous également définir une méthode de symbole dans l'interface du gestionnaire, comme ceci :

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

Dans ce cas, il nous suffit d'implémenter la méthode de symbole dans toutes les classes d'implémentation pour marquer quel type de requête le gestionnaire est utilisé pour gérer . Peut.

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){
    // 具体的处理方法
    }
}

Enfin, instanciez toutes les classes d'implémentation de Handler et enregistrez-les dans un HandlerProvider. Lorsque vous souhaitez les utiliser, vous pouvez les obtenir dans le HandlerProvider :

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

Ensuite, comment obtenir tous les Handlers As. pour la classe d’implémentation, il existe deux méthodes.

一种是通过 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;
            }
        }
    }
}

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

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer