Att skapa ett interface är en sak. Att använda ett interface är en sak. Men när bör man använda det? Läste något intressant häromdagen. T.ex. > Läste något intressant häromdagen. Hmm, jag ser nu att jag formulerade fel genom att skriva den meningen som jag gjorde borde varit kan istället för gör. Självklart menade jag inte att man helt skall undvika interface. Allmänt om diskussioner om Interface.. > Som du skriver så tror du att jag har feta interface? Jag använder mig inte Jag tycker deta är ett ganska bra exempel på användaet av abstrakta klasser och interface. >>"Ett interface kan du inte lägga på mer funktionalitet " Jag brukar köra med Interface Driven Programmering, enbart för att jag kör med Factories ofta och tex med Spring.Net. Man brukar även säga att ett klassarv svarar mot en is-a relation, medan en interface implementation svarar mot en act-as relation.När använda interfaces?
Gyllene regler för Interfaces:
Rule 1: Use interfaces to say “What can my object do?“ or “What can be done to my object?“
Rule 2: Interfaces do not say “What is my object?“
Rule 3: Interfaces say what objects can do no matter what objects are.
När använder vi Interfaces? Och när behövs inte dessa användas, eller när bör inte dessa användas?
Ge gärna några exempel.
Sv: När använda interfaces?
Använder man sig av interface istället för en basklass så låser man sig lite inför framtiden. Ett interface kan du inte lägga på mer funktionalitet medans du kan göra detta på en basklass.
Självklart kan du göra ett nytt interface som ärver av det tidigare, men har du då redan gjort en hel del implemmentationer så måste du uppdatera alla dom för att få gamla och nyare metoder att fungera med det nya interfacet.Sv: När använda interfaces?
"ProductEditor" ska ärva "MyEditorBaseClass" och samtidigt implementera IPrintable ("My object can be printed"). Då kan man t.ex. kolla generiskt att om ett nytt Form poppar upp då ska man tända "File/Print.." menyn och när man klickar där kan man anropa ActiveForm.Print(), samtidigt som man kan ärva basfunktionalitet för editors som ClearAll() från basklassen.Sv:När använda interfaces?
> Använder man sig av interface istället för en basklass så låser man sig lite inför framtiden.
Niclas, har du någon länk till en websida som argumenterar i mer detalj för ditt påstående ?
Jag skulle snarare vilja påstå att det är tvärtom, d.v.s. om man t.ex. låter en metod ta emot en parameter som är typad som en klass så låser man sig till att endast kunna återanvända en sådan metod genom att skicka in en viss klass eller subklasser. Genom att använda ett interface så kan man skicka in ett en instans av en annan klasshierarki om den implementerar samma interface.
För övrigt förstår jag kanske inte riktigt din beskrivning av problematiken med interface, men tolkar det som att du använder "feta" interface (dvs med många metoder) för klasserna så att man skapar beroenden till att implementera många metoder i interfacet som inte ens kommer att bli anropade men som ändå måste implementeras för att kunna kompilera.
I så fall bör man tillämpa "Interface-Segregation Principle", alltså utnyttja möjligheten för en klass att implementera flera interface.
(angående mitt påstående ovan om att man snarare "låser" sig om man programmerar mot en klass så behöver det inte vara så, och observera att jag inte förespråkar att man alltid ska använda sig av interface, och om man själv kontrollerar hela kodbasen så är det enkelt att vid behov applicera refaktorieringen "extract interface")
Angående att separera interfacen och eliminera onödiga beroenden så kan vi använda följande exempel som utgånspunkt för ett onödigt "fett" interface:
interface I {
void metod1();
void metod2();
void metod3();
void metod4();
}
// en klass som implementerar interfacet:
class K1: I {
public void metod1() {
// implementation
}
public void metod2() {
// implementation
}
public void metod3() {
// implementation
}
public void metod4() {
// implementation
}
}
// en annan klass som implementerar interfacet:
class K2: I {
public void metod1() {
// implementation
}
public void metod2() {
// implementation
}
public void metod3() {
// implementation
}
public void metod4() {
// implementation
}
}
// Antag att interfacet "I" och klasserna "K1" och "K2" ovan endast används enligt nedan:
class X {
void someMethod() {
K1 k1 = new K1();
K2 k2 = new K2();
myMethodA(k1);
myMethodB(k1);
myMethodB(k2);
myMethodC(k2);
}
public void myMethodA(I i) {
i.metod1();
}
public void myMethodB(I i) {
i.metod2();
i.metod3();
}
public void myMethodC(I i) {
i.metod4();
}
}
Nu kan vi även tänka oss att vi vill utöka koden ovan med en ny metod "metod5()" på klassen K2 som skall anropas via "myMethodC(I i)" d.v.s. vi utökar på följande sätt:
class K2: I {
// gamla koden...
// ny kod:
public void metod5() {
// implementation
}
}
class X {
// gamla koden...
void myMethodC(I i) {
i.metod4();
// ny kod:
i.metod5();
}
Eftersom API:t (metodsignaturen för "myMethodC") var definierat via interfacet "I" så räcker det inte med att lägga till den nya metoden i klassen "K2" utan den måste även läggas till i interfacet "I":
interface I {
// gamla metoderna...
// ny metod:
void metod5();
}
Detta kommer nu att få konsekvensen att även klassen "K1" kommer att behöva implementera interfacet "I" med en ny metod trots att den aldrig blir anropad, eftersom man har skapat onödiga beroenden till feta interface med många olika metoder...
Denna problematik kan man motverka genom att tillämpa "Interface-Segregation Principle" och splitta interfacet på flera interface (observera alltså att man inte låter det ena ärva det andra).
Interfacet "I" ovan kan istället ersättas med tre interface:
interface I1 {
void metod1();
}
interface I2 {
void metod2();
void metod3();
}
interface I3 {
void metod4();
void metod5();
}
// klasserna implementerar bara relevanta metoder i sina respektive interface:
class K1: I1, I2 {
// metod från I1:
public void metod1() {
// implementation
}
// metoder från I2:
public void metod2() {
// implementation
}
public void metod3() {
// implementation
}
}
class K2: I2, I3 {
// metoder från I2:
public void metod2() {
// implementation
}
public void metod3() {
// implementation
}
// metoder från I3:
public void metod4() {
// implementation
}
public void metod5() {
// implementation
}
}
class X {
void someMethod() {
// denna metod är helt oförändrad jämfört med tidigare:
K1 k1 = new K1();
K2 k2 = new K2();
myMethodA(k1);
myMethodB(k1);
myMethodB(k2);
myMethodC(k2);
}
// men följande metoder använder de tre nya interfacen som har ersatt det tidigare enda interfacet "I"
public void myMethodA(I1 i1) {
i1.metod1();
}
public void myMethodB(I2 i2) {
i2.metod2();
i2.metod3();
}
public void myMethodC(I3 i3) {
i3.metod4();
i3.metod5();
}
}
I detta fallet kan man förstås tycka att interfacen I1 och I3 är överflödiga och att man kan byta ut de användande metodsignaturerna mot följande istället:
public void myMethodA(K1 k1) { ...
respektive
public void myMethodC(K2 k2) { ...
men genom att använda interface så kan man återanvända de anropande metoderna (dvs som använder metoderna på respektive interface), t.ex. så här:
class SomeSubclass: SomeBaseClass, I1 {
public void metod1() {
// implementation
}
// en massa annan kod...
}
class SomeSubclassInCompletelyDifferentHierarchy: SomeOtherTotallyDifferentBaseClass, I1 {
public void metod1() {
// implementation
}
// en massa annan kod...
}
// kod någonstans:
X x = new X();
x.myMethodA(new SomeSubclass());
x.myMethodA(new SomeSubclassInCompletelyDifferentHierarchy());
Slutligen vill jag poängtera att man förstås inte bara mekaniskt skall definiera interface genom att gruppera metoder på det sätt som de tillsammans brukar behöva anropas, utan de bör förstås struktureras på något vettigt sätt i abstraktioner med "high cohesion", d.v.s. att metoderna inom interfacet på något sätt är relaterade till varandra och hör ihop, vilket förresten är lite av en förutsättning för att kunna döpa interfacen till vettiga saker med god semantik som leder till lättförståelig och förvaltningsbar kod.
/ TomasSv: När använda interfaces?
Jag menade det du skriver nedan att man inte skall ha för feta interface utan mer ha små korta beskrivande interface för att slippa den problematiken jag beskrev.
Som du skriver så tror du att jag har feta interface? Jag använder mig inte av feta interface undrar lite vad du fick det ifrån? utan jag skrev att man skall tänka på att man inte kan bryta interfacen i framtiden.
Min källa var Framework Design Guidelines de som ligger bakom den är huvudarkitekterna av .NET ramverket :). Har dock inte boken framför mig så att jag kan säga vilken sida det är.
Men jag minns deras exempel i boken, där dom beskrev Stream klassen som ett typiskt exempel.
Orsaken till att dom inte har IStream är för att dom ville kunna lägga på mer funktionalitet på Stream klassen så att alla som använde sig av alla barnklasser till stream automatiskt fick denna och att de som redan hade använde sig av stream inte skulle bryta interfacet om det hade funnits något.
Jag säger inte att interface är dåligt det är skitbra, utan mitt inlägg var mer än hint på vad man skall tänka på. Dvs har man interface så kan det finnas möjlighet att man låser sig. Självklart är det samma åt andra hållet. Visst flera interface kan lösa detta problemet i många fall.
Sedan vet jag inte om jag fattat dig rätt men de problemen du beskriver förutom klasshierarki, med virtual och abstract-Sv: När använda interfaces? - Intervju med Erich Gamma
gör en Google på
"interface vs abstract class"
.....
Och eller läs denna intervju med Erich Gamma:
Från Google Cache ;)
http://64.233.183.104/search?q=cache:dX5dNoFHVFEJ:www.artima.com/lejava/articles/designprinciples.html+programming+interface&hl=sv&ct=clnk&cd=2&gl=se
Summary
In this interview, Erich Gamma, co-author of the landmark book, Design Patterns, talks with Bill Venners about two design principles: program to an interface, not an implementation, and favor object composition over class inheritance.
Erich Gamma lept onto the software world stage in 1995 as co-author of the best-selling book Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley, 1995) [1]. This landmark work, often referred to as the Gang of Four (GoF) book, cataloged 23 specific solutions to common design problems. In 1998, he teamed up with Kent Beck to produce JUnit [2], the de facto unit testing tool in the Java community. Gamma currently is an IBM Distinguished Engineer at IBM's Object Technology International (OTI) lab in Zurich, Switzerland. He provides leadership in the Eclipse community, and is responsible for the Java development effort for the Eclipse platform [3].
On October 27, 2004, Bill Venners met with Erich Gamma at the OOPSLA conference in Vancouver, Canada. In this interview, which will be published in multiple installments in Leading-Edge Java on Artima Developer, Gamma gives insights into software design.
* In Part I: How to Use Design Patterns, Gamma describes gives his opinion on the appropriate ways to think about and use design patterns, and describes the difference between patterns libraries, such as GoF, and an Alexandrian pattern language.
* In Part II: Erich Gamma on Flexibility and Reuse, Gamma discusses the importance of reusability, the risks of speculating, and the problem of frameworkitis.
* In this third installment, Gamma discusses two design principles highlighted in the GoF book: program to an interface, not an implementation, and favor object composition over class inheritance.
Program to an interface, not an implementation
Bill Venners: In the introduction of the GoF book, you mention two principles of reusable object-oriented design. The first principle is: "Program to an interface, not an implementation." What's that really mean, and why do it?
Erich Gamma: This principle is really about dependency relationships which have to be carefully managed in a large app. It's easy to add a dependency on a class. It's almost too easy; just add an import statement and modern Java development tools like Eclipse even write this statement for you. Interestingly the inverse isn't that easy and getting rid of an unwanted dependency can be real refactoring work or even worse, block you from reusing the code in another context. For this reason you have to develop with open eyes when it comes to introducing dependencies. This principle tells us that depending on an interface is often beneficial.
Bill Venners: Why?
Erich Gamma:Once you depend on interfaces only, you're decoupled from the implementation. That means the implementation can vary, and that's a healthy dependency relationship. For example, for testing purposes you can replace a heavy database implementation with a lighter-weight mock implementation. Fortunately, with today's refactoring support you no longer have to come up with an interface up front. You can distill an interface from a concrete class once you have the full insights into a problem. The intended interface is just one 'extract interface' refactoring away.
So this approach gives you flexibility, but it also separates the really valuable part, the design, from the implementation, which allows clients to be decoupled from the implementation. One question is whether you should always use a Java interfaces for that. An abstract class is good as well. In fact, an abstract class gives you more flexibility when it comes to evolution. You can add new behavior without breaking clients.
Bill Venners: How's that?
Erich Gamma: In Java when you add a new method to an interface, you break all your clients. When you have an abstract class, you can add a new method and provide a default implementation in it. All the clients will continue to work. As always there is a trade-off, an interface gives you freedom with regard to the base class, an abstract class gives you the freedom to add new methods later. It isn't always possible to define an interface in an abstract class, but in the light of evolution you should consider whether an abstract class is sufficient.
Since changing interfaces breaks clients you should consider them as immutable once you've published them. As a consequence when adding a new method to an interface you have to do so in a separate interface. In Eclipse we take API stability seriously and for this reason you will find so called I*2 interfaces like IMarkerResolution2 or IWorkbenchPart2 in our APIs. These interfaces add methods to the base interfaces IMarkerResolution and IWorkbenchPart. Since the additions are done in separate extension interfaces you do not break the clients. However, there is now some burden on the caller in that they need to determine at run- time whether an object implements a particular extension interface.
Another lesson learned is that you should focus not only on developing version one, but to also think about the following versions. This doesn't mean designing in future extensibility, but just keeping in mind that you have to maintain what you produce and try to keep the API stable for a long time. You want to build to last. That's been an important theme of Eclipse development since we started. We have built Eclipse as a platform. We always keep in mind as we design Eclipse that it has to last ten or twenty years. This can be scary at times.
We added support for evolution in the base platform when we started. One example of this is the IAdaptable interface. Classes implementing this interface can be adapted to another interface. This is an example of the Extension Object pattern,. [4]
Bill Venners: It's funny nowadays we're so much more advanced, but when we say build to last, we mean ten or twenty years. When the ancient Egyptians built to last, they meant...
Erich Gamma: Thousands of years, right? But for Eclipse, ten to twenty years, wow. Quiet honestly, I don't envision a software archeologist finding an Eclipse installation stored somewhere on a hard disk in ten or twenty years. I really mean that Eclipse should still be able to support an active community in ten or twenty years.Sv:När använda interfaces?
> av feta interface undrar lite vad du fick det ifrån?
Tja, därför att du skrev om att "lägga på" och "ärver av" men jag såg inget som tydde på en förståelse av att man kan använda multipla interface stället för att utöka befintliga...
När någon uttrycker sig på följande sätt:
"Ett interface kan du inte lägga på mer funktionalitet medans du kan göra detta på en basklass."
så tycker jag inte heller att det är alldeles enkelt att veta vilka kunskaper som skribenten har... och ifall vederbörande kanske använder uttrycket "funktionalitet" som synonym till "implementation", och därigenom vill uttrycka en slags besvikelse för att ett interface inte kan innehålla implementation medan en basklass däremot kan göra det och därför tycker att en klass är bättre...
(och jag har hört sådana resonemang IRL från utvecklare som tycker att interface är jobbiga och klasser är bättre eftersom de innehåller implementation som man får med sig till subklasser)
Med andra ord tycker jag alltså att uttrycket "lägga på mer funktionalitet" var diffust med utrymme för olika tolkningar.
Det här tycker jag också var en formulering som (enligt min mening alltså) inte var alldeles tydlig:
"Självklart kan du göra ett nytt interface som ärver av det tidigare, men har du då redan gjort en hel del implemmentationer så måste du uppdatera alla dom för att få gamla och nyare metoder att fungera med det nya interfacet."
men jag tolkade i alla fall detta som att du tyckte det är jobbigt att implementera ett utökat interface i olika klasser, trots att alla de nya metoderna egentligen inte kommer att behöva bli anropade i alla klasser men de måste ändå bli implementerade för att ens kunna kompilera, därav mitt förslag att separera metoderna på olika interface och endast implementera de nya interfacet/metoderna i de klasser som behöver få dessa nya metoder anropade.
Jag tänkte nämligen som så att om det inte var på det viset du menade, dvs om alla metoderna faktiskt kommer att bli anropade på alla klasser så är det ju faktiskt naturligt att man måste implementera dem i alla klasser.
Jag tycker f.ö. att kodexempel är det enklaste sättet att vara tydlig och undvika missförstånd.
När jag ser otydliga formuleringar som jag inte förstår brukar jag i de flesta fall helt enkelt strunta i att bry mig, och brukar inte be om ett förtydligande eftersom det då känns som ett slags förpliktigande att man ska orka engagera sig dagen därpå efter att man kanske har fått det förtydligande man frågade efter, men den kvällen kanske man då inte känner sig på ett lika engagerat humör... så därför chansade jag igår istället på hur Niclas menade och skrev ett svar på en gång medan jag kände mig motiverad...
/ TomasSv: När använda interfaces?
<<Veichle>>
<<Car>>
Compact : Implements IPassengerCarrier
Pickup : Implements IPassengerCarrier, IHeavyLoadCarrier
SUV : Implements IPassengerCarrier
<<Train>>
PassengerTrain : Implements IPassengerCarrier
FreightTrain : IHeacyLoadCarrier
Compact, Pickup och SUV är alla olika typer av bilar och ärver ifrån klassen Car, liksom PassengerTrain och Freighttrain är olika sorts tåg och allihop är någon form av Veichle.
Eftersom t ex både Pickup och PassengerTrain kan agera som IPassengerCarrier men Pickpen även kan agera som IHeavyLoadCarrier så vore det svårare att få till det genom lämpligt arv.
I detta fall är en blandning av Klassarv och Interface lämpligt.Sv: När använda interfaces?
det kan man ju med extension methods, Man kan göra
public static void MethodA(this IMyInterface myInterface, string s)
{
Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, string s)");
}
Vilket lite fult skulle kunna användas som multipla "arv" Sv: När använda interfaces?
Jag använder mig BARA av en basklass när ett objekt är av den typen. "is a kind of", tex Tiger is a Animal, där Animal är en abstrakt basklass. Ett interface kan användas av flera klasser som inte behöver vara av sama typ som någon annan och ska ha samma typ av interface. Ett bra exempel på när man ska använda interface istället för tex en basklass är IDisposable. Flera klasser ska implementera Dispose och där "using" kommer att anropa på Dispose, det spelar ingen roll vad för klass vi har när vi kör med "using", bara dom har metoden Dispose, för att vara säker på att ett klassen har Dispose så måste metoden implementeras och det kan man tvinga på med ett Interface. Om vi skulle haft en basklass till det, japp då får vi lite problem, för vi har inte multipelt arv.
Jag håller med MS när det gäller Stream exemplet, men nu har vi även Extension Method vilket MS använder sig flitligt av för att lägga på kod på befintliga typer utan att behöva ändra i dom. Det leder till att grund typerna är som dom är och blir "bakåt kompatibla", men nya versioner kan köra med de nya extention methods.
Mvh Fredrik Normén
ASP.Net MVP MEET ASPInsider
http://weblogs.asp.net/fredriknormenSv: När använda interfaces?
Ex:
Car is-a Veichle
Car act-as FunnyCar