Cette discussion est en relation avec le billet suivant :
Guybrushe ne l'ai pas casté, j'ai juste stocker la référence de mes Fraction dans un Object. Je m'attendrais à ce que ce soit la méthode de ma fraction qui soit appelée. Cela me permettrait de stocker des sous-types d'une classe dans des références de cette classe, sans avoir à me soucier de ce que je mets dedans, mais en ayant toujours accès à la bonne implémentation spécialisée.Casté n'est effectivement pas le bon terme, c'est un-boxé ? Ce que je voulais dire, c'est que vu que ton objet est commu comme étant un 'object' il n'a accès qu'aux méthodes définies dans `object`, comme avec les interfaces:
void Main()
{
A a = new A();
a.Test();
a.Test2();
IA a2 = new A();
a2.Test();
a2.Test2(); // Erreur à la compilation ici
}
public interface IA
{
void Test();
}
public class A : IA
{
public void Test() { Console.WriteLine("test"); }
public void Test2() { Console.WriteLine("test2"); }
}
En fait, (et je ne suis pas du tout un bon théoricien), je ne vois pas ce qui est gênant là dedans, c'est typiquement le comportement auquel je m'attends en faisant ça... (mais je suis biaisé, ça fait 10 ans que je fais du .Net quasi exclusivement...)
Ce message a été modifié 1 fois.
Dernière modification : 8 février 2016
à 17:28 par
Merle.
MerleCasté n'est effectivement pas le bon terme, c'est un-boxé ? Ce que je voulais dire, c'est que vu que ton objet est commu comme étant un 'object' il n'a accès qu'aux méthodes définies dans `object`Tout à fait, c'est normal qu'on ne puisse appeler (syntaxiquement) que les méthodes du type concerné. Mais ce n'est pas de cela dont il s'agit : dans l'exemple avec les Fraction, c'est la méthode d'Object qui est appelée, et non pas celle de Fraction. Que ce soit Object ou Fraction, la méthode existe, mais si mon objet est une Fraction, peu importe dans quelle référence d'un super-type je la place, j'aimerai que ce soit ma méthode spécialisée qui soit appelée.
A b = new B();
A c = new C();
b.Test();
c.Test();
J'aurai "a" et "a" à l'écran. IA b = new B();
IA c = new C();
b.Test();
c.Test();
J'aurai "b" et "c" à l'écran. B b = new B();
et B c = new C();
pour illustrer le fait que je ne peux pas déduire que A
est un type concret juste sur base de ces deux lignes).
Ce message a été modifié 1 fois.
Dernière modification : 8 février 2016
à 17:58 par
Guybrush.
void Main()
{
A b = new B();
A c = new C();
b.Test();
c.Test();
IA b2 = new B();
IA c2 = new C();
b2.Test();
c2.Test();
}
public interface IA
{
void Test();
}
public class A : IA
{
public virtual void Test() { Console.WriteLine("a"); }
}
public class B : A
{
override public void Test() { Console.WriteLine("b"); }
}
public class C : B
{
override public void Test() { Console.WriteLine("c"); }
}
J'ai b et c dans les 2 cas ! Bizarre
Ce message a été modifié 3 fois.
Dernière modification : 8 février 2016
à 18:34 par
Merle.
MerleEdit: Ah ben non, je viens de faire le test, et ton interprétation ne semble pas être la bonne !Ce qui est encore plus étonnant, vu que dans le message initial (celui avec les Fraction), j'ai repris un exemple sur google qui illustrait justement le souci. Peut-être que le "==" n'est effectivement pas implémenté via une méthode en "override".
MerleEdit3: En enlevant le virtual et les override, je n'ai plus que "a" comme sortie...C'est encore mieux : il faut aller voir la définition précise de la méthode pour pouvoir prédire le comportement. Je sais que ça fait partie de la philosophie du langage (et le langage "impose" d'avoir un IDE, comme Java), mais je trouve ça dommage tout de même
class A:
def test(self):
print('A')
class B(A):
def test(self):
print('B')
a = A()
b = B()
a.test()
b.test()
A.test(b) # On force l'appel de test() sur la classe A, avec b comme instance)
Cela dit, c'est le modèle "tout objet" de Python qui permet cela : la classe A n'est qu'un objet avant une méthode "test" prenant en paramètre une instance (représentée par self
par convention, mais on peut mettre le nom qu'on veut). Une méthode est juste une fonction prenant une instance en paramètre. Le passage de cette instance en paramètre est implicite quand on écrit a.test()
, il passe a
comme valeur pour self
(un des rares cas où on a un comportement implicite en Python, mais heureusement, sinon ce serait lourd à écrire !). A.test
est juste une fonction prenant un paramètre (ici self
), fonction disponible sur l'object A
(objet qui représente une classe, je vous rappelle que tout est objet en Python). Et donc A.test(b)
, c'est juste un appel à la fonction test(self)
de A
, cqfd.
MerleMais ça ne vient pas du fait que tu ne types pas tes variables ?Tout à fait. Les variables ne sont pas typées statiquement. Elles le sont dynamiquement (et même fortement typées, pour autant qu'on choisisse la définition de typage fort ). Par exemple, au runtime, tu peux voir que
type(a)
est bien A
(en réalité, nomdumodule.A
!). class A:
def test(self):
print(type(self))
a = A()
a.test() # Affiche <class '__main__.A'>
A.test(42) # Affiche <class 'int'>
Il n'y a vraiment pas de magie ici : une méthode est juste une fonction, dans le namespace de la classe, prenant en paramètre l'instance sur laquelle la fonction s'applique. C'est pour ça qu'on peut sans souci écrire A.test(42)
: je demande d'exécuter la fonction test
du namespace A
avec comme paramètre 42
. A
est juste un objet avec des attributs, ici un attribut test
qui est callable :# hasattr permet de voir si A a un attribut de nom 'test'
hasattr(A, 'test') # True
# getattr permet de l'obtenir, c'est la même chose que A.test
getattr(A, 'test') # <function A.test at 0x02FC6D20>
Comme tu peux le voir, A.test
est juste une fonction. Dans le cas présent, type(A)
sera bien <class 'function'>
(qui fait partie du core régissant les objects callables), et A
est juste un conteneur particulier (une classe) : type(A)
affichera <class 'type'>
(car une instance de classe est un objet d'un certain type, donc la classe est un type en soit. __new__
pour les connaisseurs) et l'initialisation de l'instance (__init__
, typiquement appelé "constructeur")). type
. La beauté de tout ça, c'est que toute cette complexité est en fait masquée par la syntaxe, mais que le fonctionnement de tout ça est extrêmement cohérent et uniforme, ce qui permet de comprendre exactement ce qui se passe pour autant qu'on comprenne que tout est objet en Python !Merleje veux dire, si tu mettais l'équivalent de "A b = B()" (je ne connais pas la syntaxe, ni si c'est possible), tu te retrouverais avec le même problème, non ?Ce n'est pas possible d'écrire cela car on ne peut pas forcer le type de
b
ici. class A:
def test(self):
print('A.test, type=', type(self))
def test2(self):
print('A.test2, type=', type(self))
class B(A):
def test(self):
print('B.test, type=', type(self))
a = A()
b = B()
a.test()
b.test()
a.test2()
b.test2()
Le premier groupe d'appels à test()
t'affiche :test()
est défini sur les deux classes, "nativement" (override dans B).b
est une instance de B
, et que cette classe hérite de A
sans surcharger test2
, c'est bien la méthode (fonction !!) A.test2
qui est appelée avec l'instance b
de B
en paramètre, et donc on a bien un appel à A.test2 avec comme type d'instance <class '__main__.B'>. b
est du type d'un objet qui propose le service test2
(peu importe que ça soit directement ou au travers de sa hiérarchie ou d'un autre mécanisme), le service est rendu.
GuybrushCe qui est encore plus étonnant, vu que dans le message initial (celui avec les Fraction), j'ai repris un exemple sur google qui illustrait justement le souci. Peut-être que le "==" n'est effectivement pas implémenté via une méthode en "override".Si, il y a 1 méthode pour ça dans l'interface IEquatable<T>
Guybrushne semble pas être bon. Une interface définit des méthodes et leur signature mais n'a pas d'implémentation. Par contre, tu as des choses effectivement rigolotes comme définir des valeurs par défaut dans une interface, ça sera cette valeur qui prendra le pas sur la valeur définie dans l'implémentation:IA b = new B();
IA c = new C();
b.Test();
c.Test();
interface IFoo {
void DoSomething(string input = "a");
}
class Foo : IFoo {
void DoSomething(string input = "b") {
Console.WriteLine(input);
}
}
Selon que tu utilises l'interface ou la class, tu auras une autre valeur par défaut.IEnumerable<int> Foo() {
for(var i=0; i<10; i++) {
yield return i;
}
}
ne renverra le code quand quand on en aura besoin; par exemple:var number = Foo().First();
Guybrush(en réalité, c'est un peu plus compliqué que ça car Python fait la distinction entre la création de l'instance (__new__ pour les connaisseurs) et l'initialisation de l'instance (__init__, typiquement appelé "constructeur")).Oui, en Ruby aussi, ça m'a étonné de voir ça... (mais bon je le prends comme une classe avec un constructeur vide et un constructeur avec paramètres, ça y "ressemble" vaguement (même si dans les faits, c'est plus proche de créer une instance via les outils de reflection, comme Activator.CreateInstance))
void Main()
{
A a = new A();
a.Test();
B b = new B();
b.Test();
}
public class A
{
public void Test() { Console.WriteLine(this.GetType().Name); }
}
public class B : A { }
Le retour est A et B
PetitCalgonne semble pas être bon. Une interface définit des méthodes et leur signature mais n'a pas d'implémentation.Je ne comprends pas pourquoi ce n'est pas bon. Le code ne fait pas appel à une implémentation : les objets ne sont pas castés (ie. ils ne sont pas modifiés en mémoire), c'est juste la référence qui est stockée dans une variable d'un autre type (compatible !).
PetitCalgonPar contre, tu as des choses effectivement rigolotes comme définir des valeurs par défaut dans une interface, ça sera cette valeur qui prendra le pas sur la valeur définie dans l'implémentation:Tu veux dire que dans ton exemple,
(new Foo()).doSomething();
affichera "a" et non pas "b" ? x.doSomething();
est appelé, si la référence de x
est stockée dans un type Foo
, j'ai "b", et si x
pointe vers un Foo
mais stocké dans un type IFoo
, j'ai "a" ? ne va faire qu'une itération de la boucle for la-haut, et sortira et ça sera finit.Ok, c'est un peu l'équivalent des générateurs en Python (même mot-clé,
yield
d'ailleurs). MerleLinq me manque vraiment quand je programme dans un autre langage...Pour manipuler les collections ou bien directement une db ?
itertools
pour rendre ça lisible et ne pas réinventer la roue). [edit: dans le cas des collections, je précise]MerleOui, en Ruby aussi, ça m'a étonné de voir ça... (mais bon je le prends comme une classe avec un constructeur vide et un constructeur avec paramètres, ça y "ressemble" vaguement (même si dans les faits, c'est plus proche de créer une instance via les outils de reflection, comme Activator.CreateInstance))Je ne connais pas assez Ruby pour confirmer ou infirmer. Je peux juste dire qu'en Python,
__new__
sert à créer l'objet, autrement dit à réserver son emplacement mémoire et lui associer un type, alors que __init__
sert à faire "quelque chose par défaut à la construction". D'ailleurs, la nuance n'est pas si faible : __new__
prend une classe comme premier paramètre si mes souvenirs sont bons (la classe qu'on veut instancier) alors que __init__
prend déjà ce fameux "self" qui est l'instance (vide) créée.
Ce message a été modifié 1 fois.
Dernière modification : 8 février 2016
à 21:32 par
Guybrush.
GuybrushPour manipuler les collections ou bien directement une db ?Pour manipuler tout ! collections, XML, entities, ... (ce sont des implémentations différentes à chaque fois, respectivement linq2objects, linq2xml, linq2entities)
GuybrushSi ce code n'est pas bon, je ne vois pas à quoi peuvent bien servir les interfaces en C#...Test unitaires: Mock
GuybrushTu veux dire que dans ton exemple, (new Foo()).doSomething(); affichera "a" et non pas "b" ?Non:
Foo fooImpl = new Foo();
fooImpl.DoSomething() => "b";
IFoo fooInterf = new Foo();
fooInterf.DoSomething() => "a";
GuybrushOu (mais "aussi pire" à mes yeux) que si x.doSomething(); est appelé, si la référence de x est stockée dans un type Foo, j'ai "b", et si x pointe vers un Foo mais stocké dans un type IFoo, j'ai "a" ?Voila c'est ça.
private class GitSourceControlServer : ISourceControlServer {
...
}
Et pour t'amuser, tu peux déclarer les méthodes aussi privées :private class GitSourceControlServer : ISourceControlServer {
private bool ISourceControlServer.Checkin() { ... }
}
Après dans tous les projets, il y a des normes de codage et tu fais des codes reviews pour que tout le monde code plus ou moins identiquement, tu évites de faire dériver tes classes x fois d'une classe abstraite, tu préfères implémenter plusieurs interfaces que tout mettre dans la même, tu essayes de suivre les bonnes pratiques du Clean Code, etc.1996-2024 — Lexpage v4 — GPLv3 (sources)
page générée le 15 novembre 2024 à 03:13:53