Les Interfaces sont un mécanisme pour réduire le couplage entre autre, peut-être des parties disparates d'un système.
À partir d'un .net point de vue
- la définition de l'interface, une liste d'activités et/ou des propriétés
- les méthodes d'interface sont toujours publics
- l'interface elle-même n'a pas à être public
Lorsque vous créez une classe qui implémente l'interface, vous devez fournir soit explicite ou implicite de la mise en œuvre de toutes les méthodes et propriétés définies par l'interface.
Plus loin .net a seulement de l'héritage simple, et les interfaces sont une nécessité pour un objet à exposer des méthodes à d'autres objets qui ne sont pas au courant, ou se trouvent en dehors de la hiérarchie de classe. Parfois, nous appelons cela de l'exposer à des comportements.
Un exemple un peu plus concret:
Considérer est que nous avons beaucoup de DTO (les objets de transfert de données) qui ont des propriétés pour qui la dernière mise à jour, et quand c'était. Le problème est que pas toutes les DTO ont cette propriété, car il n'est pas toujours pertinente. Dans le même temps, nous désirons un mécanisme générique pour garantir ces propriétés sont définies si disponible lorsqu'il est soumis à des flux de travail, mais le flux de travail de l'objet doit être faiblement couplé à partir des objets. c'est à dire la soumission de la méthode de workflow ne devrait pas vraiment au courant de toutes les subtilités de chaque objet et tous les objets dans le flux de travail ne sont pas nécessairement DTO objets.
// first pass - not maintainable
void SubmitToWorkflow(object o, User u)
{
if( o is StreetMap )
{
var map = (StreetMap)o;
map.LastUpdated = DateTime.UtcNow;
map.UpdatedByUser = u.UserID;
}
else if( o is Person )
{
var person = (Person)o;
person.LastUpdated = DateTime.Now; // whoops .. should be UtcNow
person.UpdatedByUser = u.UserID;
}
// whoa - very unmaintainable.
Dans le code ci-dessus, SubmitToWorkflow()
devez savoir à propos de chaque objet. En outre, le code est un gâchis avec un énorme if/else/switch, viole l'Don't Repeat Yourself (SEC) principe, et oblige les développeurs à se souvenir de copier/coller des changements à chaque fois qu'un nouvel objet est ajouté au système.
// second pass - brittle
void SubmitToWorkflow(object o, User u)
{
if( o is DTOBase )
{
DTOBase dto = (DTOBase)o;
dto.LastUpdated = DateTime.UtcNow;
dto.UpdatedByUser = u.UserID;
}
Un peu mieux, mais encore fragile. Si nous voulons nous soumettre d'autres types d'objets, nous avons besoin d'encore besoin de plus de cas annuels. etc.
// third pass pass - also brittle
void SubmitToWorkflow(DTOBase dto, User u)
{
dto.LastUpdated = DateTime.UtcNow;
dto.UpdatedByUser = u.UserID;
Encore fragile, et les deux méthodes d'imposer la contrainte que tous les DTO ont à mettre en œuvre cette propriété, ce qui nous a indiqué n'était pas universellement applicable. Certains développeurs pourraient être tentés d'écrire ne rien faire, mais qui sent mauvais. Nous ne voulons pas que les classes qui prétendent la mise à jour du suivi, mais ne le font pas.
Interfaces, comment peuvent-ils aider?
Si l'on définit une interface très simple:
public interface IUpdateTracked
{
DateTime LastUpdated { get; set; }
int UpdatedByUser { get; set; }
}
Toute la classe qui a besoin de cette mise à jour automatique de suivi peut implémenter l'interface.
public class SomeDTO : IUpdateTracked
{
// IUpdateTracked implementation as well as other methods for SomeDTO
}
La méthode de workflow peut être fait pour être beaucoup plus générique, plus petite, et plus facile à gérer, et continuera à travailler n'importe comment beaucoup de classes implémentent l'interface (DTO ou autre) car il ne traite que de l'interface.
void SubmitToWorkflow(object o, User u)
{
IUpdateTracked updateTracked = o as IUpdateTracked;
if( updateTracked != null )
{
updateTracked.LastUpdated = DateTime.UtcNow;
updateTracked.UpdatedByUser = u.UserID;
}
// ...
- On peut noter la variation
void SubmitToWorkflow(IUpdateTracked updateTracked, User u)
permettrait de garantir la sécurité de type, toutefois, il ne semble pas appropriée dans ces circonstances.
Dans certains code de production que nous utilisons, nous avons de génération de code pour crée ces DTO classes à partir de la base de données de définition. La seule chose que le développeur n'est d'avoir créer le champ nom correctement et décorer la classe avec l'interface. Tant que les propriétés sont appelés LastUpdated et UpdatedByUser, il fonctionne, tout simplement.
Peut-être que vous vous demandez Ce qui se passe si ma base de données est l'héritage et ce n'est pas possible? Vous avez juste à faire un peu plus de la frappe; une autre grande caractéristique des interfaces, ils peuvent vous permettre de créer un pont entre les classes.
Dans le code ci-dessous, nous avons une fictifs LegacyDTO
, un objet préexistant ayant même nom de champs. C'est la mise en œuvre de la IUpdateTracked interface de pont de l'existant, mais différemment des propriétés nommées.
// using an interface to bridge properties
public class LegacyDTO : IUpdateTracked
{
public int LegacyUserID { get; set; }
public DateTime LastSaved { get; set; }
public int UpdatedByUser
{
get { return LegacyUserID; }
set { LegacyUserID = value; }
}
public DateTime LastUpdated
{
get { return LastSaved; }
set { LastSaved = value; }
}
}
Vous pourriez chose de Cool, mais n'est-il pas confusion ayant de multiples propriétés? ou Ce qui se passe si il y a déjà ces propriétés, mais elles veulent dire quelque chose d'autre? .net vous donne explicitement la capacité de mettre en œuvre l'interface. Ce que cela signifie, c'est que le IUpdateTracked propriétés ne sera visible que lorsque nous sommes à l'aide d'une référence à IUpdateTracked. Notez comment il n'y a pas de modificateur sur la déclaration et la déclaration comprend le nom de l'interface.
// explicit implementation of an interface
public class YetAnotherObject : IUpdatable
{
int IUpdatable.UpdatedByUser
{ ... }
DateTime IUpdatable.LastUpdated
{ ... }
Le fait d'avoir autant de souplesse pour définir la façon dont la classe implémente l'interface permet au développeur de beaucoup de liberté afin de dissocier l'objet de méthodes qui la consomment. Les Interfaces sont un excellent moyen de briser le couplage.
Il y a beaucoup plus à des interfaces de tout cela. C'est juste une simplification de la vie réelle exemple qui utilise un seul aspect de l'interface en fonction de la programmation.
Comme je l'ai mentionné plus tôt, et par d'autres intervenants, vous pouvez créer des méthodes qui prennent et/ou le retour à l'interface des références plutôt que sur une classe de référence. Si je devais trouver les doublons dans une liste, je pourrais écrire une méthode qui prend en entrée et retourne un IList
(une interface qui définit les opérations que le travail sur les listes) et je ne suis pas limité à un béton de classe de collection.
// decouples the caller and the code as both
// operate only on IList, and are free to swap
// out the concrete collection.
public IList<T> FindDuplicates( IList<T> list )
{
var duplicates = new List<T>()
// TODO - write some code to detect duplicate items
return duplicates;
}
Versioning mise en garde
Si c'est une interface publique, vous êtes déclarant j'ai la garantie de l'interface x ressemble à ça! et une fois que vous avez envoyé le code et publié l'interface, vous ne devriez jamais changer. Dès que le code de la consommation commence à s'appuyer sur cette interface, vous ne voulez pas casser leur code dans le champ.
Voir ce Haacked post pour une bonne discussion.
Interfaces rapport résumé (de base) des classes
Les classes abstraites peuvent fournir de la mise en œuvre tandis que les Interfaces ne peuvent pas. Les classes abstraites sont à certains égards plus de souplesse dans la gestion des versions aspect si vous suivez quelques règles comme le NVPI (Non Virtuelle Interface Publique) modèle.
Il est bon de répéter que, dans .net, une classe ne peut hériter que d'une seule classe, mais une classe peut implémenter autant d'interfaces qu'elle aime.
L'Injection De Dépendance
Le résumé rapide des interfaces et DI est que l'utilisation des interfaces permet aux développeurs d'écrire du code qui est programmé à l'encontre d'une interface pour fournir des services. Dans la pratique, vous pouvez vous retrouver avec beaucoup de petites interfaces et des classes de petite taille, et une idée est que les classes de petite taille qui font une chose et une seule chose est beaucoup plus facile à coder et à maintenir.
class AnnualRaiseAdjuster
: ISalaryAdjuster
{
AnnualRaiseAdjuster(IPayGradeDetermination payGradeDetermination) { ... }
void AdjustSalary(Staff s)
{
var payGrade = payGradeDetermination.Determine(s);
s.Salary = s.Salary * 1.01 + payGrade.Bonus;
}
}
En bref, l'intérêt montré dans l'extrait ci-dessus est que la catégorie de salaire détermination est juste injecté dans l'examen annuel de soulever de réglage. Comment payer le grade est déterminé n'est pas vraiment de l'importance à cette classe. Lors de l'essai, le développeur peut se moquer de l'échelle salariale de la détermination des résultats afin d'assurer le salaire de réglage des fonctions comme souhaité. Les tests sont aussi rapides, parce que le test est seulement de tester la classe, et pas tout le reste.
Ce n'est pas un DI apprêt même si, comme il y a des livres entiers consacrés à ce sujet; l'exemple ci-dessus est très simplifiée.