Question

J'ai une classe Gene qui garde la trace des gènes. Gene dispose d’une méthode de calcul de la distance entre deux gènes. Y a-t-il des raisons de le rendre statique?

Quel est le meilleur?

public statique int geneDistance (gène g0, gène g1)

ou

public int geneDistance (Gène autre)

Des arguments pour / contre le rendre statique? Je comprends ce que cela signifie pour un membre d'être statique, je suis simplement intéressé par ses implications pour un maximum de propreté / efficacité / etc.

Je répète le même schéma pour renvoyer des versions parées de deux gènes, pour rechercher des correspondances entre gènes, pour rechercher des correspondances entre animaux (contenant des ensembles de gènes), etc.

Était-ce utile?

La solution

Instance non statique

Pour ce cas, je pense que le deuxième choix est clairement meilleur. Si vous y réfléchissez, n'importe quelle méthode peut être implémentée en statique si vous êtes prêt à lui passer l'objet, cela seul semble constituer un cas particulier car l'autre paramètre est également une instance.

Par conséquent, notre recherche de la symétrie et de l'abstraction est légèrement choquée par le fait de devoir choisir entre les deux objets d'instance pour l'opérateur point. Mais si vous regardez .method en tant que . , puis opérateur , ce n'est pas vraiment un problème.

De plus, le seul moyen de réaliser un chaînage de style fonctionnel consiste à utiliser un attribut, c'est-à-dire une méthode d'instance. Vous voulez probablement que thing.up.down.parent.next.distance (x) fonctionne.

Autres conseils

Lorsque vous rendez une méthode statique, cela signifie que celle-ci peut être appelée sans instance de la classe. Cela signifie également que la méthode ne peut accéder aux variables d'instance que si une référence à un objet lui est transmise.

Parfois, il est judicieux de rendre une méthode statique, car la méthode est associée à la classe, mais pas à une instance particulière de la classe. Par exemple, toutes les méthodes parseX, telles que Integer.parseInt (String s ). Ceci convertit une String en un int , mais n'a rien à voir avec une instance particulière d'un objet Integer .

Si, en revanche, une méthode doit renvoyer des données propres à une instance particulière d'un objet (comme la plupart des méthodes getter et setter), elle ne peut pas être statique.

IMO, il n'y a pas d'absolu "meilleur", mais public int geneDistance (gène autre) est stylistiquement plus similaire aux autres méthodes en Java (par exemple, Object.equals, Comparable.compareTo), donc je j'irais de cette façon.

Je préfère la seconde forme, c'est-à-dire la méthode d'instance pour les raisons suivantes:

  1. les méthodes statiques rendent les tests difficiles car elles ne peuvent pas être remplacées,
  2. les méthodes statiques sont davantage orientées vers les procédures (et donc moins vers les objets).

IMO, les méthodes statiques conviennent aux classes d’utilitaires (comme StringUtils) mais je préfère ne pas abuser de leur utilisation.

Ma reformulation de la réponse de Charle:

Si la méthode en question a l'intention d'utiliser l'état de l'objet sous-jacent sous-jacent, faites-en une méthode d'instance. Sinon, rendez-le statique.

Ce qui dépend de la façon dont la classe de l'objet est conçue.

Dans votre cas, alphazero, probablement la int geneDistance (gène g0, gène g1) ne dépend pas vraiment de l'état du gène exemple il est appelé. Je voudrais rendre cette méthode statique. Et placez-le dans une classe d’utilité telle que GeneUtils .

Bien entendu, il se peut que votre problème comporte d'autres aspects que je ne connais pas, mais il s'agit de la règle générale que j'utilise.

P.S. - > La raison pour laquelle je ne mettrais pas la méthode dans la classe Gene , c'est qu'un gène ne devrait pas être responsable du calcul de sa distance par rapport à un autre gène. ; -)

public statique int geneDistance (Gene g0, Gene g1) ferait partie d'une classe d'utilitaires distincte telle que Collections et Tableaux en Java, alors que public int geneDistance (Gene other) fera partie de la classe Gene . Considérant que vous avez d'autres opérations telles que "des versions ajustées de deux gènes, trouver des correspondances entre gènes, trouver des correspondances entre animaux (qui contiennent des collections de gènes), etc." Je créerais une classe d’utilitaire statique distincte pour eux, car ces opérations n’ont pas de signification sémantique quant à ce qu’est un gène .

Si la sémantique de " gene distance " peut être encapsulé dans votre méthode equals (Object o) , vous pourrez donc le consommer ici ou l'inclure dans votre utilitaire statique.

J'aimerais commencer à répondre à votre question par la nouvelle: Quelle est la responsabilité de votre classe Gene? Peut-être avez-vous entendu parler du «principe de responsabilité unique»: une classe ne devrait avoir qu'une seule raison de changer. Donc, je pense que si vous répondez à cette question, vous serez en mesure de décider de la conception de votre application. Dans ce cas particulier, je n’utiliserais ni la première approche ni la seconde. À mon avis, il est bien préférable de définir une nouvelle responsabilité et de l’encapsuler dans une classe séparée ou peut être une fonction.

Je vais essayer de résumer certains des points déjà évoqués ici sur lesquels je suis d’accord.

Personnellement, je ne pense pas qu’il existe un "se sent mieux". réponse. Des raisons valides existent pour lesquelles vous ne voulez pas une classe utilitaire remplie de méthodes statiques.

La réponse courte est que, dans un monde orienté objet, vous devez utiliser des objets et tous les bons "articles". qui vient avec eux (encapsulation, polymorphisme)

Polymorphisme

Si la méthode de calcul de la distance entre les gènes varie , vous devez approximativement (plus probablement un Stratégie ) ont une classe de gènes par variation. Encapsulez ce qui varie. Sinon, vous allez vous retrouver avec plusieurs ifs.

Ouvert pour extension fermé pour modification

Cela signifie que si une nouvelle méthode de calcul de la distance entre les gènes apparaît sur la ligne, vous ne devez pas modifier le code existant , mais plutôt en ajouter une nouvelle . Sinon, vous risquez de casser ce qui existe déjà.

Dans ce cas, vous devez ajouter une nouvelle classe de gènes sans modifier le code écrit dans la #geneDistance

Dites-le à ne pas demander

Vous devez dire à vos objets quoi faire, ne pas leur demander leur état et prendre des décisions à leur place. Soudainement, vous enfreignez le principe de responsabilité unique puisqu'il s'agit d'un polymorphisme.

Testabilité

Les méthodes statiques peuvent être faciles à tester isolément, mais vous utiliserez plus tard cette méthode statique dans d’autres classes. Quand il s'agira de tester ces classes sur l'isolement, vous aurez du mal à le faire. Ou plutôt pas.

Je laisserai Misko a son dicton qui est probablement mieux que ce que je peux trouver.

import junit.framework.Assert;

import org.junit.Test;

public class GeneTest
{
    public static abstract class Gene
    {
        public abstract int geneDistance(Gene other);
    }

    public static class GeneUtils
    {
        public static int geneDistance(Gene g0, Gene g1)
        {
            if( g0.equals(polymorphicGene) )
                return g0.geneDistance(g1);
            else if( g0.equals(oneDistanceGene) )
                return 1;
            else if( g0.equals(dummyGene) )
                return -1;
            else
                return 0;            
        }
    }


    private static Gene polymorphicGene = new Gene()
                                    {

                                        @Override
                                        public int geneDistance(Gene other) {
                                        return other.geneDistance(other);
                                        }
                                    };

    private static Gene zeroDistanceGene = new Gene() 
                                    {                                        
                                        @Override
                                        public int geneDistance(Gene other) {
                                        return 0;
                                        }
                                    };

    private static Gene oneDistanceGene = new Gene() 
                                    {                                        
                                        @Override
                                        public int geneDistance(Gene other) {
                                        return 1;
                                        }
                                    };

    private static Gene hardToTestOnIsolationGene = new Gene()
                                    {

                                        @Override
                                        public int geneDistance(Gene other) {
                                        return GeneUtils.geneDistance(this, other);
                                        }
                                    };

    private static Gene dummyGene = new Gene()
                                    {

                                        @Override
                                        public int geneDistance(Gene other) {
                                        return -1;
                                        }
                                    };                                    
    @Test
    public void testPolymorphism()
    {
        Assert.assertEquals(0, polymorphicGene.geneDistance(zeroDistanceGene));
        Assert.assertEquals(1, polymorphicGene.geneDistance(oneDistanceGene));
        Assert.assertEquals(-1, polymorphicGene.geneDistance(dummyGene));
    }

    @Test
    public void testTestability()
    {

        Assert.assertEquals(0, hardToTestOnIsolationGene.geneDistance(dummyGene));
        Assert.assertEquals(-1, polymorphicGene.geneDistance(dummyGene));
    }    

    @Test
    public void testOpenForExtensionClosedForModification()
    {

        Assert.assertEquals(0, GeneUtils.geneDistance(polymorphicGene, zeroDistanceGene));
        Assert.assertEquals(1, GeneUtils.geneDistance(oneDistanceGene, null));
        Assert.assertEquals(-1, GeneUtils.geneDistance(dummyGene, null));
    }    
}

Voici une méta-réponse et un exercice amusant: examinez quelques classes de la bibliothèque du SDK Java et voyez si vous pouvez catégoriser les points communs entre les méthodes statiques dans différentes classes.

Dans ce cas particulier, je vais en faire une méthode d'intance. MAIS si vous avez une réponse logique lorsque g0 est null, utilisez BOTH (cela se produit plus souvent que vous ne le pensez).

Par exemple, aString.startsWith () , si la chaîne a la valeur null, vous pouvez penser qu'il est LOGIQUE de renvoyer la valeur null (si vous pensez que la fonction peut être NULL-TOLERATE). Cela me permet de simplifier un peu mon programme car il n’est pas nécessaire d’avoir une vérification Chaîne nulle dans le code client.


final Stirng         aPrefix = "-";
final Vector aStrings = new Vector();
for(final String aString : aStrings) {
    if (MyString.startsWith(aString, aPrefix))
        aStrings.aStringadd();
}

au lieu de


final Stirng         aPrefix = "-";
final Vector aStrings = new Vector();
for(final String aString : aStrings) {
    if ((aString != null) && aString.startsWith(aPrefix))
        aStrings.aStringadd();
}

REMARQUE: cet exemple est trop simplifié.

Juste une pensée.

Je voudrais en faire une méthode d'instance. Mais cela pourrait être dû au fait que je n'ai aucune idée de gènes;)

Les méthodes d'instance peuvent être remplacées par des sous-classes, ce qui réduit considérablement la complexité de votre code (moins besoin d'instructions if). Dans l'exemple de la méthode statique, que se passera-t-il si vous obtenez un type spécifique de gène pour lequel la distance est calculée différemment? Ad une autre méthode statique? Si vous devez traiter une liste polymorphe de gènes, vous devez rechercher le type de gène pour sélectionner la méthode de distance correcte ... ce qui augmente le couplage et la complexité.

Je choisirais la deuxième approche. Je ne vois aucun avantage à rendre la méthode statique. Puisque la méthode est dans la classe Gene, la rendre statique n’ajoute qu’un paramètre supplémentaire sans gain supplémentaire. Si vous avez besoin d'une classe d'util, c'est une affaire complètement différente. Mais à mon avis, il n'est généralement pas nécessaire d'avoir une classe utilitaire si vous pouvez ajouter la méthode à la classe en question.

Je pense que le domaine problématique devrait éclairer la réponse au-delà des considérations stylistiques générales et / ou OO.

Par exemple, je suppose que dans le domaine de l'analyse génétique, les notions de "gène" et de "distance" sont assez concrètes et ne nécessiteront pas de spécialisation par héritage. Si ce n'était pas le cas, on pourrait plaider en faveur de l'utilisation des méthodes d'instance.

La principale raison de préférer la méthode d'instance est le polymorphisme. Une méthode statique ne peut pas être remplacée par une sous-classe, ce qui signifie que vous ne pouvez pas personnaliser l'implémentation en fonction du type d'instance. Cela pourrait ne pas s'appliquer dans votre cas, mais il convient de le mentionner.

Si la distance entre les gènes est complètement indépendante du type de gène, je préférerais utiliser une classe d’utilité séparée pour rendre cette indépendance plus explicite. Avoir une méthode geneDistance dans la classe Gene implique que la distance est un comportement lié à l'instance du gène.

Ma réponse est très controversée.

Je voudrais suivre le même chemin que l'une des implémentations StringUtils.getLevenshteinDistance dans StringUtils.

    public interface GeneDistance{
        public int get();
    }

    public class GeneDistanceImpl implements GeneDistance{
        public int get(){ ... }
    }

    public class GeneUtils{
        public static int geneDistance(Gene g0, Gene g1){
            return new GeneDistanceImpl(g0, g1).get();
        }
    }

Quelques points pour le faire de cette façon

  • Il peut y avoir plusieurs implémentations à distance, donc une méthode utilitaire est préférable à g0.distanceTo (g1)
  • Je peux l'importer en statique pour une notation courte
  • Je peux tester mon implémentation
  • Je peux aussi ajouter ceci:

    class Gene{
        // ... Gene implementation ...
    
        public int distanceTo(Gene other){
            return distance.get(this, GeneUtils.getDefaultDistanceImpl());
        }
    
        public int distanceTo(Gene other, GeneDistance distance){
            return distance.get(this, other);
        }
    }
    

Une des raisons pour rendre une méthode complexe complètement statique est la performance. Le mot clé static indique aux compilateurs JIT que la méthode peut être insérée. À mon avis, il / elle n'a pas besoin de s'inquiéter de telles choses à moins que leurs appels de méthodes soient presque instantanés - moins d'une microseconde, c'est-à-dire quelques opérations sur les chaînes ou un simple calcul C’est peut-être la raison pour laquelle la distance de Levenshtein est devenue complètement statique dans la dernière mise en œuvre.

Deux considérations importantes qui n’ont pas été mentionnées sont de savoir si on s'attend toujours à ce que gene1.geneDistance (gene2) corresponde à gene2.geneDistance (gene1) et si Gene est et sera toujours une classe scellée. Les méthodes d'instance sont polymorphes en ce qui concerne les types d'éléments sur lesquels elles sont appelées, mais pas les types de leurs arguments. Cela peut créer une certaine confusion si la fonction de distance est supposée être transitive, mais des types différents peuvent calculer la distance différemment. Si la fonction de distance est supposée être transitive et définie comme étant la transformation la plus courte connue de l'une ou l'autre classe, un bon modèle peut consister à avoir une méthode d'instance protégée int getOneWayDistance (Gene other) , puis avoir quelque chose comme:

public static int geneDistance(Gene g0, Gene g1)
{
  int d0=g0.getOneWayDistance(g1);
  int d1=g1.getOneWayDistance(g0);
  if (d0 < d1) return d0; else return d1;
}

Une telle conception garantira le comportement transitoire de la relation de distance, tout en permettant aux types individuels de signaler les raccourcis vers des instances d'autres types que ceux-ci peuvent ne pas connaître.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top