Methodenparameter näher definieren

@discobot ICh will was schreiben

Hallo,

ich wollte fragen ob man die Parameter einer Methode näher definieren kann als nur eine bestimmte Klasse anzufordern?
Also in etwa so:

public void doSomething( (Car implements Cabriolet) car ) {
     // do something
}

Ich habe mir folgende Sachen überlegt:
A) Eine eigene Klasse definieren die Car Extended und Cabriolet implementiert
B) Generics - Aber die kann ich nur außerhalb meiner Klasse definieren?

Möchte nur nicht die Implementierung einschränken.

Danke

Außerdem finde ich es doof das man hier nicht sofort schreiben kann. Nur aus Zufall hab ich den Beitrag entdeckt wo es hieß dass man ein „paar“ viele Minuten warten muss. Weder in der Begrüßungsnachricht von @discobot noch in der Fehlermeldung „Du kannst 0 Beiträge schreiben“ steht das. :confused: Naja jetzt bin ich hier.

Hallo! Um herauszufinden, was ich kann, schreibe @discobot Hilfe anzeigen.

Ja das ist möglich und ja dafür solltest du Generics nehmen die du auch bei Methoden verwenden kannst

Prinzipiell sollte es funktionieren wenn du

public void doSomething( (Car implements Cabriolet) car ) {
// do something
}

zu

public <C extends Cabriolet> void doSomething(C car ) {
// do something
}

änderst.
Was Generics angeht muss ich aber auch hin und wieder etwas rumprobieren :smiley:

1 „Gefällt mir“

wenn es zwei Bedingungen wären, Klasse Car und Interface Cabriolet unabhängig, was hier wenig realistisch klingt, dann gibt es noch

public class Test2 {
    public static void main(String[] args)  {
        Test2 t = new Test2();
        t.doSomething(new CabOnly()); // Fehlermeldung
        t.doSomething(new CarOnly()); // Fehlermeldung
        t.doSomething(new CarCab());
    }

    public <C extends Car & Cabriolet> void doSomething(C car) { }
}

class CabOnly implements Cabriolet { }
class CarOnly extends Car { }
class CarCab extends Car implements Cabriolet { }
class Car { }
interface Cabriolet { }

wobei das nicht schön aussieht, selten doppelte Typisierung genutz, besser zu vermeinden,

nur Interface Cabriolet zu verlangen reicht sicherlich, wenn das kein Car ist, selber schuld,
zu verwendene Methoden müssten freilich alle im Interface stehen

1 „Gefällt mir“

Ich frage mich ja wozu?

Wenn Cabriolet sowieso ein Interface ist braucht man das Generic-geraffel nicht.

Die Schreibweise <C extends Interface> benötigt man ja nur dann, wenn man den Typ C bei der Instanziierung der Klasse weiter einschränken will, also

 MyGenericType<GolfCabrio> kannNurMitGolfCabrios = ...

dann ein SlkCabrio Objekt nicht akzeptieren soll, obwohl es auch das Cabriolet Interface implementiert. Das ist aber ehr selten.

bye
TT

Ich verstehe nicht was du sagst.

Ich habe eine Klasse Autohaus. Dem kann man Autos hinzufügen.
Wenn ein Auto aber auch Methoden zur Bedienung des Daches bereitstellt, möchte ich diese auf andere Weise handhaben.

Das übergebene Objekt soll sowohl Methoden von Car, wie auch von Cabriolet zur Verfügung stellen.
Auto bietet ein paar Standardmethoden an, Cabriolet hat zwar ein paar der “gleichen” Methoden (vom Sinn), aber spezifischere Übergabeparameter.
Deshalb möchte ich ein Cabriolet anders behandeln als ein normales Auto ohne dabei die genaue Implementierung einzuschränken.
Von mir aus kann es auch ein Kran sein der Cabriolet implementiert und der von LKW erbt, der von Mercedes erbt, der von Auto erbt.

Generics machen aber genau diese Einschränkung.

Mit Hilfe der Generics kannst Du in Deiner Autohaus<GolfCabrio>-Instanz GolfCabrios vorführen, aber keine anderen, weder ein SLK-Cabrio noch einen Z1, obwohl beide auch das Cabrio-Interface haben.

Wenn du nur das Cabrio-Interface verlangen würdest, ohne Generic, wäre dein Autohaus für alle Cabrios offen.

Beispiel:

interface Cabriolet{}

abstract class Car{}

class GolfCabrio extends Car implements Cabriolet {}
class Z1Cabrio extends Car implements Cabriolet {}

class AutoHaus<C extends Cabriolet>{
  void demostriereVerdeckAuf(C cabrio){}
}

AutoHaus<GolfCabrio> autoHaus = new AutoHaus<>();
autoHaus.demostriereVerdeckAuf(new GolfCabrio()); // OK
autoHaus.demostriereVerdeckAuf(new Z1Cabrio()); // CompileFehler

bye
TT

an

public void doSomething(Cabriolet car )

hatte ich bei bei Antwort von Clayn gestern auch nicht gedacht, freudscher Generic-Fehler,

aber nur fürs Protokoll, mit meiner Methode, so praxisfern sie auch aussieht, klappt das Beispiel mit Golf, SLK, Z1 durchaus,
Klassenparameter, der einmal gesetzt fest gewählt ist ist eine Sache,
wen sollte es wundern dass ein Z1 nicht in AutoHaus GolfCabrio geht?,
warum sollte jemand überhaupt konkret die Klasse GolfCabrio für ein AutoHaus aussuchen? davon war doch bisher noch nicht die Rede

das Generics nur bei einer Methode, und nie bestimmte Klassenwahl, sieht eben etwas anders aus:

public class Autohaus {
    public static void main(String[] args)  {
        Autohaus a = new Autohaus();
        a.doSomething(new Fahrrad()); // Fehlermeldung
        a.doSomething(new LKW()); // Fehlermeldung
        a.doSomething(new GolfCap());
        a.doSomething(new SLK());
        a.doSomething(new GolfCap());
    }

    public <C extends Car & Cabriolet> void doSomething(C car) { 
        car.dusche();
        car.fahre();
    }
}

class Fahrrad implements Cabriolet { 
    public void dusche() {}   
}
class LKW extends Car { }
class GolfCap extends Car implements Cabriolet {
    public void dusche() {} 
}
class SLK extends Car implements Cabriolet {
    public void dusche() {} 
}
class Z1 extends Car implements Cabriolet {
    public void dusche() {} 
}
class Car {
    public void fahre() {}    
}
interface Cabriolet { 
    public void dusche();    
}

besser aber in der Tat, alle benötigten Methoden in nur einem benötigten Interface verfügbar zu haben, Cabriolet ist auch ein Auto:

public class Autohaus {
    public static void main(String[] args)  {
        Autohaus a = new Autohaus();
        a.doSomething(new LKW()); // Fehlermeldung
        a.doSomething(new GolfCap());
        a.doSomething(new SLK());
        a.doSomething(new GolfCap());
    }

    public void doSomething(Cabriolet car) { 
        car.dusche();
        car.fahre();
    }
}

class LKW extends Car { }
class GolfCap extends Car implements Cabriolet {
    public void dusche() {} 
}
class SLK extends Car implements Cabriolet {
    public void dusche() {} 
}
class Z1 extends Car implements Cabriolet {
    public void dusche() {} 
}
class Car implements CarDing {
    public void fahre() {}    
}
interface CarDing { 
    public void fahre();    
}
interface Cabriolet extends CarDing { 
    public void dusche();    
}

Auto war bei mir Anfangs auch ein Interface. Auto hat einen Methode die das Autohaus zurück gibt auf das es zugelassen wird. Der Wert darf sich aber niemals ändern, bis das Autohaus das Auto explizit freigibt.
Und dafür brauchte ich package-private Methoden und Logik die nur Autohaus ausführen darf.
Code-Sicherheit wenn man so will. Weil das Auto soll nicht einfach sein eigenes Autohaus ändern, ganz wichtig!! Dann funktioniert der Code nicht mehr, hat auch was mit Garbage-Collection zu tun.
Wenn ein Auto entscheidet kaputt zu gehen, dann muss das entsprechende Autohaus informiert werden, das Auto sauber aus allen Listen zu entfernen.

Aber wenn ich aus Cabriolet dann eine Klasse mache die von Auto erbt, und eine Klasse Roadster die von Auto erbt, dann kann ich keine Klasse mehr machen die von Cabriolet wie auch von Roadster erbt…

Aber irgendwie bin ich jetzt verwirrter als vorher :sweat_smile:
Ist Programmarchitektur immer so ein schwieriges Thema? Gibt es Lehrbücher oder „Verhaltensregeln“ bei der OOP Programmierung? Wenn das Projekt größer wird, erschlägt mich OOP.

naja, alle Methoden und Daten in Auto, die nur das Autohaus kennen soll, kennt und könnte ändern wohl mindestens auch die Klasse Auto selber, soviel zu ‘Weil das Auto soll nicht einfach sein eigenes Autohaus ändern, ganz wichtig!!’,
oder meinst du nur Subklassen von Auto, die keinen Zugriff auf diesen Anteil ihrer Superklasse haben sollen?

vielleicht entsprechende Buchhaltung irgendwo anders hinterlegen, in Autohaus oder noch einer Verwaltung über alle Autohäuser, in echten Autos der Welt ist ja auch nicht die Besitz-Historie hinterlegt, oh, Fahrzeugschein…


für welchen Einsatz planst du eigentlich die Methode?,
eine Methode, die nur bestimmte Autos wie Cabrio zuläßt, kann auch nur genau mit Cabrio aufgerufen werden im Code,

wenn etwa eine andere Klasse wie ein Mensch ein beliebiges Auto hat, und sein Auto an das Autohaus geben will, weiß diese andere Klasse gar nicht ob es ein Cabrio ist oder was anderes, will nicht zwischen 5 verschiedenen Methoden wählen und nach welchem Kriterium?
auch nicht 5 verschiedene Instanzattribute für mögliche Autotypen haben

typischer scheint mir bei sowas defensive weitreichende Ansätze a la
autohaus.anlieferung(AnyCar car)
und in der Klasse Mensch auch eine Variable AnyCar, unbekannt ob Cabrio oder sonst was

in der Autohaus-Methode kann dann bei Bedarf das Car genauer untersuchen,
z.B. nach Typ-Enum oder Liste von Tags oder notfalls auch instanceOf-Tests auf Basisklasse/ Interface,

wenn nicht die richtige Autohaus-Basisklasse, dann ablehnen/ verschrotten,
wenn Tag Cabrio, dann Cabrio-Zusatzverarbeitung starten usw.

1 „Gefällt mir“

Erstaunlich viele Antworten in kurzer Zeit, kann leider gerade nicht im Detail drübergehen, aber falls es noch nicht erwähnt wurde: Ein wichtiger unterschied ist, ob man

interface Car { }
interface Cabriolet { }
class GolfCabrio implements Car, Cabriolet {}

schreibt, oder

interface Car { }
interface Cabriolet extends Car { }
class GolfCabrio implements Cabriolet {}

Bei echten Beispielen kann es schwierig werden, die in diesem Fall damit verbundene Frage zu beantworten: Ist jedes Cabriolet auch immer ein Car? (Dann und nur dann solle man die zweite Lösung verwenden…)

1 „Gefällt mir“

[OT]

Das Problem ist, dass Dein Ansatz schon nicht OOP ist. Im waren Leben kannst Du dein Auto auch nicht nach dem Autohaus fragen, das es verkauft hat oder wem es gerade gehört. Das geht nur über den Umweg Zulassungsschein oder anhand des Kennzeichen über die Zulassungsstelle.

Die „unnatürliche“ Abkürzung, die Du hier einführen willst, macht Dir das Design kaputt.

Kann man so machen … ist dann halt Kacke.

[/OT]

Nachdem ich das ganze etwas genauer gelesen habe (aber noch nicht alles bis in jedes Detail), noch ein paar relevante Fragen:

Es gibt grob drei Möglichkeiten:

  1. Die Methode ist spezfisch für ein Cabriolet. Das wäre dann

    void openRoof(Cabriolet cabriolet) { 
        cabriolet.openYourRoof();
    }
    

    Recht einfach.

  2. Die Methode ist spezfisch für ein Car. Das wäre dann

    void drive(Car car) { 
        car.driveAround();
    }
    

    Auch recht einfach.

  3. Die Methode braucht etwas, was ein Car ist, aber auch ein Cabriolet.

    void driveWithOpenRoof(??? thing) {
        thing.openYourRoof();
        thing.driveAround();
    }
    

Beim letzteren hängt die Methodendeklaration von der schon angedeuteten Frage ab: Gilt Cabriolet extends Car oder nicht? Wenn Cabriolet extends Car gilt, kann man dann einfach schreiben

void driveWithOpenRoof(Cabriolet thing) {
   thing.openYourRoof();
   thing.driveAround();
}

weil jedes Cabriolet auch ein Car ist, und man dann auf jedem Cabriolet auch die Methode driveAround aufrufen kann (die ja in Car definiert ist).

Wenn aber du sagst, dass z.B. ein Crane auch ein Cabriolet sein könnte (er aber eben kein Car wäre - wenn also Cabriolet extends Car nicht gilt), dann müßte man die Methode schreiben als

   void <C extends Car & Cabriolet> driveWithOpenRoof(C thing) {
       thing.openYourRoof();
       thing.driveAround();
   }

Der entscheidende Punkt ist wohl in der Aussage

Deshalb möchte ich ein Cabriolet anders behandeln als ein normales Auto

Wenn Cabriolet extends Car gilt, dann ist das nicht möglich. (Siehe Liskovsches Substitutionsprinzip – Wikipedia ).


Also nochmal: Gilt Cabriolet extends Car oder nicht?

1 „Gefällt mir“

So als weiterer Gedanke:

Über die Vererbung steuere ich das Verhalten der erbenden Objekte.

Du willst aber das Verhalten außerhalb der Vererbungshierarchie danach ausrichten, in welchem Vererbungszweig sich das zu behandelnde Objekt befindet. Da kommst Du um einen (wie auch immer gearteten) Vergleich des Typs nicht herum.

Verhalten anderer Objekte kann leider nicht über die eigene Vererbungshierarchie festgelegt werden.

Man könnte aber beispielsweise eine Liste möglicher Aktionen anbieten, die fahrzeugspezifisch sind:

interface VehicleAction{
   void doIt();
}


interface  Vehicle {
  void addActionsBeforeDeparture(Collection<VehicleAction> vehicleActions) ;
  void addActionsWhileDriving(Collection<VehicleAction> vehicleActions) ;
 //...
}

class Car implements Vehicle{
    public   void addActionsBeforeDeparture(Collection<VehicleAction> vehicleActions){
       vehicleActions.add(()->turnRadioOn());
       vehicleActions.add(()->tearDownWindow());
    }
}

class Cabriolet implements Vehicle{
    private final Vehicle baseVehicle;
    public Cabriolet(Vehicle baseVehicle){ this.baseVehicle=baseVehicle;}
    public void addActionsBeforeDeparture(Collection<VehicleAction> vehicleActions){
      baseVehicle.addActionsBeforeDeparture(vehicleActions);
      vehicleActions.add(()->openCanopy());
    }
}

Der Fahrer kann dann diese Aktionen in beliebiger Reihenfolge ausführen:

List<Vehicle> vehicles 
      = Arrays.asList(  
          new Cabrio(new Golf()), 
          new Bmw3er(),  
          new Cabriolet(new SLK()));
for(Vehicle vehicle : vehicles) {
    Collection<VehicleAction> actions = new ArrayList();
    vehicle.addActionsBeforeDeparture(actions);
    for(VehicleAction action : actions)
       action.doIt()
}    

bye
TT