Y a-t-il plus d’une interface que d’avoir les méthodes correctes

Alors disons que j’ai cette interface:

public interface IBox { public void setSize(int size); public int getSize(); public int getArea(); //...and so on } 

Et j’ai une classe qui l’implémente:

 public class Rectangle implements IBox { private int size; //Methods here } 

Si je voulais utiliser l’interface IBox, je ne peux pas en créer une instance, de la manière suivante:

 public static void main(Ssortingng args[]) { Ibox myBox=new Ibox(); } 

droite? Donc je devrais réellement faire ceci:

 public static void main(Ssortingng args[]) { Rectangle myBox=new Rectangle(); } 

Si cela est vrai, le seul but des interfaces est de s’assurer que la classe qui implémente une interface contient les bonnes méthodes décrites par une interface? Ou existe-t-il une autre utilisation des interfaces?

Les interfaces sont un moyen de rendre votre code plus flexible. Qu’est-ce que vous faites est la suivante:

 Ibox myBox=new Rectangle(); 

Ensuite, plus tard, si vous souhaitez utiliser un autre type de boîte (peut-être qu’il existe une autre bibliothèque, avec un meilleur type de boîte), vous passez votre code à:

 Ibox myBox=new OtherKindOfBox(); 

Une fois que vous vous y serez habitué, vous constaterez que c’est une excellente façon de travailler.

Une autre raison est, par exemple, si vous souhaitez créer une liste de boîtes et effectuer certaines opérations sur chacune, mais vous souhaitez que la liste contienne différents types de boîtes. Sur chaque boîte, vous pouvez faire:

 myBox.close() 

(en supposant qu’IBox ait une méthode close ()) même si la classe réelle de myBox change en fonction de la boîte dans laquelle se trouve l’itération.

Ce qui rend les interfaces utiles, ce n’est pas le fait que «vous pouvez changer d’avis et utiliser une implémentation différente plus tard et que vous n’avez qu’à changer l’endroit où l’object est créé». C’est un non-problème.

Le vrai point est déjà dans le nom: ils définissent une interface que n’importe qui peut implémenter pour utiliser tout le code qui fonctionne sur cette interface. Le meilleur exemple est java.util.Collections qui fournit toutes sortes de méthodes utiles qui fonctionnent exclusivement sur des interfaces, telles que sort() ou reverse() pour List . Le point ici est que ce code peut maintenant être utilisé pour sortinger ou inverser toute classe qui implémente les interfaces List – pas seulement ArrayList et LinkedList , mais aussi les classes que vous écrivez vous-même, qui peuvent être implémentées de la même manière que les personnes qui ont écrit java.util.Collections jamais imaginées.

De la même manière, vous pouvez écrire du code qui fonctionne sur des interfaces connues, ou des interfaces que vous définissez, et d’autres personnes peuvent utiliser votre code sans avoir à vous demander de prendre en charge leurs classes.

Une autre utilisation courante des interfaces concerne les rappels. Par exemple, java.swing.table.TableCellRenderer , qui vous permet d’influencer la façon dont une table Swing affiche les données dans une colonne donnée. Vous implémentez cette interface, transmettez une instance à la JTable et, à un moment donné pendant le rendu de la table, votre code sera appelé à faire son travail.

L’une des nombreuses utilisations que j’ai lues est celle où il est difficile de ne pas utiliser d’interfaces utilisant plusieurs inheritances en Java:

 class Animal { void walk() { } .... .... //other methods and finally void chew() { } //concentrate on this } 

Maintenant, imaginez un cas où:

 class Reptile extends Animal { //reptile specific code here } //not a problem here 

mais,

 class Bird extends Animal { ...... //other Bird specific code } //now Birds cannot chew so this would a problem in the sense Bird classes can also call chew() method which is unwanted 

Un meilleur design serait:

 class Animal { void walk() { } .... .... //other methods } 

Animal n’a pas la méthode chew () et est placé dans une interface comme:

 interface Chewable { void chew(); } 

et que la classe Reptile mette en œuvre cela et non les oiseaux (puisque les oiseaux ne peuvent pas mâcher):

 class Reptile extends Animal implements Chewable { } 

et en cas d’Oiseaux simplement:

 class Bird extends Animal { } 

Le but des interfaces est le polymorphism , alias substitution de type . Par exemple, avec la méthode suivante:

 public void scale(IBox b, int i) { b.setSize(b.getSize() * i); } 

Lors de l’appel de la méthode scale , vous pouvez fournir toute valeur d’un type qui implémente l’interface IBox . En d’autres termes, si Rectangle et Square implémentent tous les deux IBox , vous pouvez fournir un Rectangle ou un Square partout où un IBox est attendu.

Les interfaces permettent aux langages typés statiquement de prendre en charge le polymorphism. Un puriste orienté object insisterait pour qu’un langage fournisse l’inheritance, l’encapsulation, la modularité et le polymorphism afin d’être un langage orienté object complet. Dans les langages à typage dynamic ou à caractères typés (comme Smalltalk), le polymorphism est sortingvial; cependant, dans les langages statiquement typés (comme Java ou C #), le polymorphism est loin d’être sortingvial (en fait, il semble en apparence en contradiction avec la notion de typage fort).

Permettez-moi de démontrer:

Dans un langage typescript dynamicment (ou typé en canard) (comme Smalltalk), toutes les variables sont des références à des objects (rien de moins et rien de plus). Donc, dans Smalltalk, je peux le faire:

 |anAnimal| anAnimal := Pig new. anAnimal makeNoise. anAnimal := Cow new. anAnimal makeNoise. 

Ce code:

  1. Déclare une variable locale appelée anAnimal (notez que nous NE spécifions PAS le TYPE de la variable – toutes les variables sont des références à un object, ni plus ni moins).
  2. Crée une nouvelle instance de la classe nommée “Pig”
  3. Assigne cette nouvelle instance de Pig à la variable anAnimal.
  4. Envoie le message makeNoise au cochon.
  5. Répète le tout en utilisant une vache, mais en lui atsortingbuant la même variable exacte que le cochon.

Le même code Java ressemblerait à ceci (en faisant l’hypothèse que Duck et Cow sont des sous-classes d’Animal:

 Animal anAnimal = new Pig(); duck.makeNoise(); anAnimal = new Cow(); cow.makeNoise(); 

C’est bien beau, jusqu’à ce que nous introduisions des légumes de classe. Les légumes ont le même comportement que les animaux, mais pas tous. Par exemple, les animaux et les légumes peuvent être cultivés, mais il est clair que les légumes ne font pas de bruit et que les animaux ne peuvent pas être récoltés.

Dans Smalltalk, nous pouvons écrire ceci:

 |aFarmObject| aFarmObject := Cow new. aFarmObject grow. aFarmObject makeNoise. aFarmObject := Corn new. aFarmObject grow. aFarmObject harvest. 

Cela fonctionne très bien dans Smalltalk car il est typé en canard (s’il marche comme un canard, et comme un canard: c’est un canard). Dans ce cas, lorsqu’un message est envoyé à un object, une recherche est effectuée. la liste des méthodes du récepteur, et si une méthode correspondante est trouvée, elle est appelée. Si ce n’est pas le cas, une exception NoSuchMethodError est générée, mais tout est fait au moment de l’exécution.

Mais en Java, un langage typé statiquement, quel type pouvons-nous atsortingbuer à notre variable? Le maïs doit hériter de Vegetable, pour soutenir la croissance, mais ne peut pas hériter d’Animal, car il ne fait pas de bruit. La vache doit hériter d’Animal pour supporter makeNoise, mais ne peut pas hériter de Vegetable car elle ne doit pas implémenter la récolte. Il semble que nous ayons besoin d’ un inheritance multiple – la capacité d’hériter de plusieurs classes. Mais cela s’avère être une fonctionnalité de langage assez difficile à cause de tous les cas extrêmes qui apparaissent (que se passe-t-il lorsque plusieurs super-classes parallèles implémentent la même méthode?, Etc.)

Les interfaces viennent …

Si nous fabriquons des classes animales et végétales, avec chaque implémentable Growable, nous pouvons déclarer que notre vache est un animal et que notre maïs est un légume. Nous pouvons également déclarer que les animaux et les légumes sont cultivables. Cela nous permet d’écrire cela pour tout faire pousser:

 List list = new ArrayList(); list.add(new Cow()); list.add(new Corn()); list.add(new Pig()); for(Growable g : list) { g.grow(); } 

Et cela nous permet de faire des bruits d’animaux:

 List list = new ArrayList(); list.add(new Cow()); list.add(new Pig()); for(Animal a : list) { a.makeNoise(); } 

L’avantage du langage typé en canard est que vous obtenez un polymorphism très agréable: tout ce qu’une classe doit faire pour fournir un comportement est de fournir la méthode. Tant que tout le monde joue bien et envoie uniquement des messages correspondant aux méthodes définies, tout va bien. L’inconvénient est que le type d’erreur ci-dessous n’est pas détecté avant l’exécution:

 |aFarmObject| aFarmObject := Corn new. aFarmObject makeNoise. // No comstackr error - not checked until runtime. 

Les langages typés statiquement fournissent une bien meilleure “programmation par contrat”, car ils attraperont les deux types d’erreur ci-dessous au moment de la compilation:

 // Comstackr error: Corn cannot be cast to Animal. Animal farmObject = new Corn(); farmObject makeNoise(); 

 // Comstackr error: Animal doesn't have the harvest message. Animal farmObject = new Cow(); farmObject.harvest(); 

Donc …. pour résumer:

  1. L’implémentation de l’interface vous permet de spécifier les types de choses que les objects peuvent faire (interaction) et l’inheritance de classe vous permet de spécifier comment les choses doivent être faites (implémentation).

  2. Les interfaces nous apportent de nombreux avantages du «vrai» polymorphism, sans sacrifier la vérification de type de compilateur.

Normalement, les interfaces définissent l’interface à utiliser (comme son nom l’indique ;-)). Échantillon

 public void foo(List l) { ... do something } 

Maintenant, votre fonction foo accepte ArrayList s, LinkedList s, … pas seulement un type.

La chose la plus importante en Java est que vous pouvez implémenter plusieurs interfaces mais vous ne pouvez étendre qu’une classe! Échantillon:

 class Test extends Foo implements Comparable, Serializable, Formattable { ... } 

est possible mais

 class Test extends Foo, Bar, Buz { ... } 

n’est pas!

Votre code ci-dessus pourrait également être: IBox myBox = new Rectangle(); . L’important est que myBox SEULEMENT contienne les méthodes / champs d’IBox et pas les autres méthodes (éventuellement existantes) de Rectangle .

Je pense que vous comprenez tout ce que font les interfaces, mais vous n’imaginez pas encore les situations dans lesquelles une interface est utile.

Si vous instanciez, utilisez et libérez un object dans une scope restreinte (par exemple, dans un appel de méthode), une interface n’ajoute rien. Comme vous l’avez noté, la classe concrète est connue.

Lorsque les interfaces sont utiles, c’est quand un object doit être créé à un endroit et renvoyé à un appelant qui peut ne pas se soucier des détails de l’implémentation. Changeons votre exemple IBox en une forme. Maintenant, nous pouvons avoir des implémentations de Shape telles que Rectangle, Circle, Triangle, etc. Les implémentations des méthodes getArea () et getSize () seront complètement différentes pour chaque classe concrète.

Maintenant, vous pouvez utiliser une fabrique avec une variété de méthodes createShape (params) qui renverront une forme appropriée en fonction des parameters transmis. De toute évidence, la fabrique saura quel type de forme est créée, mais l’appelant n’aura pas se soucier de savoir si c’est un cercle, ou un carré, etc.

Maintenant, imaginez que vous avez plusieurs opérations à effectuer sur vos formes. Vous devez peut-être les sortinger par zone, les définir tous à une nouvelle taille, puis les afficher dans une interface utilisateur. Les formes sont toutes créées par l’usine puis peuvent être transmises très facilement aux classes de sortingeuse, de calibreur et d’affichage. Si vous avez besoin d’append une classe hexagonale dans le futur, vous ne devez rien changer d’autre que l’usine. Sans l’interface, l’ajout d’une autre forme devient un processus très compliqué.

vous pourriez faire

 Ibox myBox = new Rectangle(); 

de cette façon, vous utilisez cet object comme Ibox et vous ne vous souciez pas vraiment de son Rectangle .

POURQUOI INTERFACE ??????

Cela commence avec un chien. En particulier, un carlin .

Le carlin a divers comportements:

 public class Pug { private Ssortingng name; public Pug(Ssortingng n) { name = n; } public Ssortingng getName() { return name; } public Ssortingng bark() { return "Arf!"; } public boolean hasCurlyTail() { return true; } } 

Et vous avez un Labrador, qui a également un ensemble de comportements.

 public class Lab { private Ssortingng name; public Lab(Ssortingng n) { name = n; } public Ssortingng getName() { return name; } public Ssortingng bark() { return "Woof!"; } public boolean hasCurlyTail() { return false; } } 

Nous pouvons faire des pugs et des laboratoires:

 Pug pug = new Pug("Spot"); Lab lab = new Lab("Fido"); 

Et nous pouvons invoquer leurs comportements:

 pug.bark() -> "Arf!" lab.bark() -> "Woof!" pug.hasCurlyTail() -> true lab.hasCurlyTail() -> false pug.getName() -> "Spot" 

Disons que je dirige un chenil et que je dois suivre tous les chiens que je loge. J’ai besoin de stocker mes pugs et labradors dans des tableaux séparés :

 public class Kennel { Pug[] pugs = new Pug[10]; Lab[] labs = new Lab[10]; public void addPug(Pug p) { ... } public void addLab(Lab l) { ... } public void printDogs() { // Display names of all the dogs } } 

Mais ce n’est clairement pas optimal. Si je veux héberger des caniches , je dois aussi changer ma définition de chenil pour append un tableau de caniches. En fait, j’ai besoin d’un tableau séparé pour chaque type de chien.

Insight: les carlins et les labradors (et les caniches) sont des types de chiens et ils ont le même ensemble de comportements. C’est-à-dire que nous pouvons dire (aux fins de cet exemple) que tous les chiens peuvent aboyer, avoir un nom et avoir ou non une queue bouclée. Nous pouvons utiliser une interface pour définir ce que tous les chiens peuvent faire, mais nous nous en remettons aux types spécifiques de chiens pour mettre en œuvre ces comportements particuliers. L’interface dit “voici ce que tous les chiens peuvent faire” mais ne dit pas comment chaque comportement est fait.

 public interface Dog { public Ssortingng bark(); public Ssortingng getName(); public boolean hasCurlyTail(); } 

Ensuite, je modifie légèrement les classes Pug et Lab pour implémenter les comportements Dog. On peut dire qu’un Carlin est un Chien et qu’un Laboratoire est un Chien.

 public class Pug implements Dog { // the rest is the same as before } public class Lab implements Dog { // the rest is the same as before } 

Je peux toujours instancier des Pugs et des Labs comme je l’ai fait précédemment, mais j’ai maintenant une nouvelle façon de le faire:

 Dog d1 = new Pug("Spot"); Dog d2 = new Lab("Fido"); 

Cela dit que d1 n’est pas seulement un chien, c’est spécifiquement un carlin. Et d2 est aussi un chien, en particulier un laboratoire. Nous pouvons invoquer les comportements et ils fonctionnent comme avant:

 d1.bark() -> "Arf!" d2.bark() -> "Woof!" d1.hasCurlyTail() -> true d2.hasCurlyTail() -> false d1.getName() -> "Spot" 

Voici où tout le travail supplémentaire paye. La classe Kennel devient beaucoup plus simple. Je n’ai besoin que d’un tableau et d’une méthode addDog. Les deux vont travailler avec n’importe quel object qui est un chien; c’est-à-dire des objects qui implémentent l’interface Dog.

 public class Kennel { Dog[] dogs = new Dog[20]; public void addDog(Dog d) { ... } public void printDogs() { // Display names of all the dogs } } 

Voici comment l’utiliser:

 Kennel k = new Kennel(); Dog d1 = new Pug("Spot"); Dog d2 = new Lab("Fido"); k.addDog(d1); k.addDog(d2); k.printDogs(); 

La dernière déclaration afficherait: Spot Fido

Une interface vous permet de spécifier un ensemble de comportements communs à toutes les classes implémentant l’interface. Par conséquent, nous pouvons définir des variables et des collections (telles que des tableaux) qui n’ont pas besoin de connaître à l’avance le type d’object spécifique qu’elles détiendront, mais seulement qu’elles contiendront des objects qui implémentent l’interface.

Interfaces

Il existe un certain nombre de situations dans l’ingénierie logicielle lorsqu’il est important que des groupes de programmeurs disparates acceptent un «contrat» expliquant comment leurs logiciels interagissent. Chaque groupe doit pouvoir écrire son code sans savoir comment le code de l’autre groupe est écrit. De manière générale, les interfaces sont de tels contrats.

Par exemple, imaginons une société futuriste où des voitures robotisées contrôlées par ordinateur transportent des passagers dans les rues de la ville sans opérateur humain. Les constructeurs automobiles écrivent des logiciels (Java bien sûr) qui font fonctionner l’automobile – arrêtez, démarrez, accélérez, tournez à gauche, etc. Un autre groupe indussortingel, les fabricants d’instruments de guidage électronique, fabrique des systèmes informatiques qui reçoivent des données de position GPS (Global Positioning System) et une transmission sans fil des conditions de circulation et utilise ces informations pour conduire la voiture.

Les constructeurs automobiles doivent publier une interface standard qui détaille les méthodes pouvant être utilisées pour déplacer la voiture (n’importe quelle voiture, de n’importe quel fabricant). Les fabricants de guides peuvent alors écrire des logiciels qui invoquent les méthodes décrites dans l’interface pour commander la voiture. Aucun groupe indussortingel n’a besoin de savoir comment le logiciel de l’autre groupe est implémenté. En fait, chaque groupe considère que son logiciel est hautement propriétaire et se réserve le droit de le modifier à tout moment, tant qu’il continue à adhérer à l’interface publiée.

Un bon exemple de la façon dont les interfaces sont utilisées est le cadre Collections. Si vous écrivez une fonction qui prend une List , cela n’a pas d’importance si l’utilisateur passe un Vector une liste de ArrayList une liste de HashList ou autre. Et vous pouvez également passer cette List à toute fonction nécessitant une interface Collection ou Iterable .

Cela rend possible des fonctions telles que Collections.sort(List list) , quelle que soit la manière dont la List est implémentée.

C’est la raison pour laquelle les patterns d’usine et les autres modèles de création sont si populaires en Java. Vous avez raison de dire que sans eux, Java ne fournit pas de mécanisme prêt à l’emploi pour faciliter l’abstraction de l’instanciation. Cependant, vous obtenez une abstraction partout où vous ne créez pas d’object dans votre méthode, ce qui devrait être la plus grande partie de votre code.

En passant, j’encourage généralement les gens à ne pas suivre le mécanisme “IRealname” pour nommer les interfaces. C’est une chose Windows / COM qui met un pied dans la tombe de la notation hongroise et n’est vraiment pas nécessaire (Java est déjà fortement typé, et le but de disposer d’interfaces est de les rendre aussi indiscernables que possible).

N’oubliez pas qu’à une date ultérieure, vous pouvez prendre une classe existante et la faire implémenter IBox . Elle deviendra alors disponible pour tous vos codes compatibles avec les boîtes.

Cela devient un peu plus clair si les interfaces sont nommées -able . par exemple

 public interface Saveable { .... public interface Printable { .... 

etc. (Les schémas de nommage ne fonctionnent pas toujours, par exemple, je ne suis pas sûr que Boxable soit approprié ici)

Le seul but des interfaces est de s’assurer que la classe qui implémente une interface contient les méthodes correctes décrites par une interface? Ou existe-t-il une autre utilisation des interfaces?

Je mets à jour la réponse avec de nouvelles fonctionnalités d’interface, introduites avec la version java 8 .

De la page de documentation d’Oracle sur le résumé de l’interface :

Une déclaration d’interface peut contenir

  1. signatures de méthode
  2. méthodes par défaut
  3. méthodes statiques
  4. définitions constantes

Les seules méthodes qui ont des implémentations sont les méthodes par défaut et les méthodes statiques.

Utilisations de l’interface :

  1. Définir un contrat
  2. Pour lier des classes sans rapport avec des capacités (par exemple, les classes implémentant une interface Serializable peuvent avoir ou ne pas avoir de relation entre elles sauf implémenter cette interface)
  3. Fournir une implémentation interchangeable, par exemple un modèle de stratégie
  4. Les méthodes par défaut vous permettent d’append de nouvelles fonctionnalités aux interfaces de vos bibliothèques et d’assurer la compatibilité binary avec le code écrit pour les anciennes versions de ces interfaces.
  5. Organiser des méthodes d’assistance dans vos bibliothèques avec des méthodes statiques (vous pouvez conserver des méthodes statiques spécifiques à une interface dans la même interface plutôt que dans une classe distincte)

Quelques questions SE relatives à la différence entre classe abstraite et interface et cas d’utilisation avec exemples pratiques:

Quelle est la différence entre une interface et une classe abstraite?

Comment devrais-je expliquer la différence entre une interface et une classe abstraite?

Consultez la page de documentation pour comprendre les nouvelles fonctionnalités ajoutées dans java 8: méthodes par défaut et méthodes statiques .

Le but des interfaces est l’ abstraction ou le découplage de l’implémentation.

Si vous introduisez une abstraction dans votre programme, vous ne vous souciez pas des implémentations possibles. Vous êtes intéressé par ce qu’il peut faire et pas comment , et vous utilisez une interface pour l’exprimer en Java.

Si vous avez CardboardBox et HtmlBox (qui implémentent tous les deux IBox), vous pouvez les transmettre à n’importe quelle méthode qui accepte un IBox. Même si elles sont à la fois très différentes et pas complètement interchangeables, les méthodes qui ne se soucient pas de “ouvrir” ou de “redimensionner” peuvent toujours utiliser vos classes (peut-être parce que le nombre de pixels requirejs pour afficher quelque chose est important).

Interfaces où une fétature ajoutée à Java pour permettre l’inheritance multiple. Cependant, les développeurs de Java ont réalisé qu’avoir un inheritance multiple était une fonctionnalité «dangereuse», c’est pourquoi ils ont eu l’idée d’une interface.

l’inheritance multiple est dangereux car vous pourriez avoir une classe comme celle-ci:

class Box{ public int getSize(){ return 0; } public int getArea(){ return 1; } } class Triangle{ public int getSize(){ return 1; } public int getArea(){ return 0; } } class FunckyFigure extends Box, Triable{ // we do not implement the methods we will used the inherited ones }
class Box{ public int getSize(){ return 0; } public int getArea(){ return 1; } } class Triangle{ public int getSize(){ return 1; } public int getArea(){ return 0; } } class FunckyFigure extends Box, Triable{ // we do not implement the methods we will used the inherited ones } 

Quelle serait la méthode qui devrait être appelée quand on utilise

FunckyFigure.GetArea();
FunckyFigure.GetArea(); 

Tous les problèmes sont résolus avec des interfaces, car vous savez que vous pouvez étendre les interfaces et qu’elles n’auront pas de méthodes de classification … bien sûr, le compilateur est sympa et vous indique si vous n’avez pas implémenté de méthodes, mais je pense que c’est un effet secondaire d’une idée plus intéressante.

Voici ma compréhension de l’avantage de l’interface. Corrigez-moi si je me trompe. Imaginez que nous développions des systèmes d’exploitation et que d’autres équipes développent des pilotes pour certains appareils. Nous avons donc développé une interface StorageDevice. Nous en avons deux implémentations (FDD et HDD) fournies par d’autres développeurs.

Ensuite, nous avons une classe OperatingSystem qui peut appeler des méthodes d’interface telles que saveData en passant simplement une instance de classe implémentée par l’interface StorageDevice.

L’avantage est que l’implémentation de l’interface ne nous intéresse pas. L’autre équipe fera le travail en implémentant l’interface StorageDevice.

 package mypack; interface StorageDevice { void saveData (Ssortingng data); } class FDD implements StorageDevice { public void saveData (Ssortingng data) { System.out.println("Save to floppy drive! Data: "+data); } } class HDD implements StorageDevice { public void saveData (Ssortingng data) { System.out.println("Save to hard disk drive! Data: "+data); } } class OperatingSystem { public Ssortingng name; StorageDevice[] devices; public OperatingSystem(Ssortingng name, StorageDevice[] devices) { this.name = name; this.devices = devices.clone(); System.out.println("Running OS " + this.name); System.out.println("List with storage devices available:"); for (StorageDevice s: devices) { System.out.println(s); } } public void saveSomeDataToStorageDevice (StorageDevice storage, Ssortingng data) { storage.saveData(data); } } public class Main { public static void main(Ssortingng[] args) { StorageDevice fdd0 = new FDD(); StorageDevice hdd0 = new HDD(); StorageDevice[] devs = {fdd0, hdd0}; OperatingSystem os = new OperatingSystem("Linux", devs); os.saveSomeDataToStorageDevice(fdd0, "blah, blah, blah..."); } }