2017-07-26 1 views
1

Dies ist ein bisschen eine Design-Frage mit inneren Klassen in Java (Java 8). Der gesamte Beispielcode ist unterhalb meines TextesJava: Innere Klassen, die auf die privaten Variablen des jeweils anderen zugreifen - eine gute Methode zum Kapseln externer APIs?

Als Beispiel, habe ich einige Maschinen, die das Pumpen von Kraftstoff aus einem Öl-Geysir zu einer Art von Brenner, die ich mit einer externen API namens OilAPI steuern kann.

Ich habe eine Controller-Klasse, die die Arbeit macht und entscheidet, welcher Brenner Öl von welchem ​​Geysir bekommen muss, aber ich möchte nicht, dass die API Klassen wie Geyser und Burner in den Controller laufen (auch da die API noch einige Änderungen im Laufe der Zeit erfährt).

Jetzt, um es zu verkapseln, so erstelle ich eine Klasse namens FuelFacility, die die gesamte Logik der OilAPI enthält.

Die Sache ist, ich habe die Klassen Pumpe und Motor als innere Klassen in FuelFacility gesetzt.

Zunächst ist dies für die Syntax von Pump.activate() anstelle von FuelFacility.activatePump (...) oder was auch immer gehen.

Weiter ist, dass, um einen Geyser und Brenner in der Oil API zu verbinden, Sie sowohl die Geyser und Brenner Objekte benötigen, aber ich möchte sie nicht extern aussetzen, also um eine Art "Connect Pumpe und Motor "muss ich entweder der Pumpe erlauben, auf die Variable Brenner des Motors zuzugreifen, der Engine, um auf die Variable Geyser + der Pumpe zuzugreifen, oder der FuelFacility, um auf diese beiden Variablen zuzugreifen. Im folgenden Beispiel habe ich eine Engine.connectToPump (pump) -Methode, die im Grunde genommen in meinem tatsächlichen Code funktioniert.

Meine Teamkollegen dachten, dass dies ein bisschen seltsam ist; sie sagten, dass der Zugriff auf die privaten Variablen über die Klassen die Kapselung unterbricht, und insbesondere, dass ein Programmierer, der den Code von "außerhalb" betrachtet (dh vom Standpunkt der Arbeit in der Controller-Klasse), davon ausgehen würde, dass er einmal erhalten hat Die Engine- und Pump-Objekte wären nicht mehr abhängig von zB welches OilAPI-Objekt das ursprüngliche FuelFacility verwendet (obwohl dies wie im Folgenden beschrieben endgültig sein sollte) oder aufeinander.

Nun, seitdem habe ich es geschafft, ihre Meinung ein wenig zu ändern - das ist im Grunde nur eine Art Dinge zu tun, an die sie nicht gewöhnt sind, aber das ist keine schlechte Übung.

Aber jetzt bin ich damit beschäftigt, einige andere Codes zu ändern, um auf eine ähnliche Art und Weise zu arbeiten, und ich möchte nur sicherstellen, bevor ich fortfahre, was mache ich gute Praxis? Gibt es einen besseren Weg, Dinge zu tun? Beratung ist erwünscht!

Code:

Oil API (nicht unter meiner Kontrolle):

public class OilAPI { 
    private final Pipes pipes = new Pipes(); 

    public static class Geyser {} 
    public static class Burner {} 

    public static class Pipes { 
     public void createConnectionBetweenGeyserAndBurner(Geyser g, Burner b) { 
      // Connects geyser and burner 
     } 
    } 

    public Geyser getGeyserWithId(String id) { 
     // Actually retrieves a specific instance 
     return new Geyser(); 
    } 

    public Burner getBurnerWithId(String id) { 
     // Actually retrieves a specific instance 
     return new Burner(); 
    } 

    public void activateGeyser(Geyser g) { 
     // do stuff 
    } 

    public void activateBurner(Burner b) { 
     // do stuff 
    }  

    public void createConnectionBetweenGeyserAndBurner(Geyser g, Burner b) { 
     pipes.createConnectionBetweenGeyserAndBurner(g,b); 
    } 
} 

Kraftstoff Einrichtung (Klasse I das Öl API verkapseln erstellt):

public class FuelFacility { 
    private final OilAPI oil; 

    FuelFacility(OilAPI oil) { 
     this.oil = oil; 
    } 

    public Pump getPumpForId(String id) { 
     OilAPI.Geyser geyser = oil.getGeyserWithId(id); 
     return new Pump(geyser); 
    } 

    public Engine getEngineForId(String id) { 
     OilAPI.Burner burner = oil.getBurnerWithId(id); 
     return new Engine(burner); 
    } 

    public class Pump { 
     private final OilAPI.Geyser geyser; 
     private Pump(OilAPI.Geyser geyser) { 
      this.geyser = geyser; 
     } 

     public void activate() { 
      oil.activateGeyser(geyser); 
     } 
    } 

    public class Engine { 
     private final OilAPI.Burner burner; 
     private Engine(OilAPI.Burner burner) { 
      this.burner = burner; 
     } 

     public void connectToPump(Pump pump) { 
      oil.createConnectionBetweenGeyserAndBurner(pump.geyser, burner); 
     } 

     public void activate() { 
      oil.activateBurner(burner); 
     } 
    } 
} 

Steuerung (von mir Besitz und sitzt innerhalb unserer Code-Basis):

public class Controller { 

    public static void main(String[] args) { 
     // We actually get these from a database 
     String engineId = "engineId"; 
     String pumpId = "pumpId"; 

     OilAPI oil = new OilAPI(); 

     FuelFacility facility = new FuelFacility(oil); 
     FuelFacility.Engine engine = facility.getEngineForId(engineId); 
     FuelFacility.Pump pump = facility.getPumpForId(pumpId); 
     engine.connectToPump(pump); 
    } 
} 

Antwort

1

mit inneren Klassen jeweils anderen privaten Bereichen Zugang ist nicht notwendig, an sich schlecht. Es scheint, dass Ihr Hauptziel darin besteht, Controller vor Änderungen an OilAPI zu schützen. In diesem Design, FuelFacility, Pump, und Engine sind so nah an OilAPI, Geyser und Burner, dass ich nicht sicher bin, dass Sie wirklich schützen Controller all das viel. FuelFacility sollte mehr für das, was Controller Bedürfnisse als was OilAPI tut konzipiert sein. In Ihrem Beispiel rufen Sie nicht activate auf der Pump oder Engine, aber ich nehme an, dass Sie dies letztendlich tun möchten. Zuerst würde ich, indem er erklärt, einige Schnittstellen starten:

public interface PumpEngineConnection { 
    public void activate(); 
} 

public interface FuelFacility { 
    public PumpEngineConnection connect(String pumpId, String engineId); 
} 

Controller über diese Schnittstellen arbeitet und nicht weiß, was Implementierungen, die es tatsächlich nutzt. Dann können Sie eine OilAPIFuelFacility Implementierung von FuelFacility machen. Die PumpEngineConnection Implementierung, die es zurückgibt, ist eine, die speziell dafür entwickelt wurde, mit OilAPIFuelFacility zu arbeiten. Man könnte dies mit einer inneren Klasse tun:

public class OilAPIFuelFacility implements FuelFacility { 
    private final OilAPI oil; 
    public OilAPIFuelFacility(OilAPI oil){ this.oil = oil; } 

    @Override 
    public PumpEngineConnection connect(String pumpId, String engineId){ 
    Geyser geyser = oil.getGeyserWithId(pumpId); 
    Burner burner = oil.getBurnerWithId(engineId); 
    oil.createConnectionBetweenGeyserAndBurner(geyser, burner); 
    return this.new GeyserBurnerConnection(geyser, burner); 
    } 

    private class GeyserBurnerConnection implements PumpEngineConnection { 
    private final Geyser geyser; 
    private final Burner burner; 

    private GeyserBurnerConnection(Geyser geyser, Burner burner){ 
     this.geyser = geyser; 
     this.burner = burner; 
    } 

    @Override 
    public void activate() { 
     OilAPIFuelFacility.this.oil.activateGeyser(this.geyser); 
     OilAPIFuelFacility.this.oil.activateBurner(this.burner); 
    } 
    } 
} 

Jeder GeyserBurnerConnection wird implizit einen Verweis auf die OilAPIFuelFacility, die es erstellt. Das ist sinnvoll, weil es nur Sinn macht, einen PumpEngineConnection mit dem FuelFacility zu verwenden, der es verursachte. Ebenso ist es für GeyserBurnerConnection vollkommen vernünftig, das oil Mitglied von OilAPIFuelFacility zu beziehen.

Das heißt, es könnte mehr Sinn machen für GeyserBurnerConnection, eine Paket-private Klasse in dem gleichen Paket wie OilAPIFuelFacility zu sein. Es kann sein, dass verschiedene Versionen der OilAPI die gleiche GeyserBurnerConnection Klasse verwenden können.

Schließlich könnte die Controller etwa wie folgt aussehen:

import com.example.fuelfacility.FuelFacility; 
import com.example.fuelfacility.PumpEngineConnection; 
public Controller { 
    private final FuelFacility fuelFacility; 

    public Controller(FuelFacility fuelFacility){ 
    this.fuelFacility = fuelFacility; 
    } 

    public void example(){ 
    String pumpId = "pumpId"; 
    String engineId = "engineId"; 

    PumpEngineConnection connection = fuelFacility.connect("pumpId", "engineId"); 
    connection.activate(); 
    } 
} 

Beachten Sie, dass es von dem, was die Umsetzung von FuelFacility völlig unwissend ist und PumpEngineConnection es tatsächlich nutzt. In der Praxis würden wir OilAPIFuelFacility mit einem Dependency-Injection-Framework oder einer externen Main-Klasse übergeben.

Ich realisiere, dass Ihr Beispiel wahrscheinlich vereinfacht ist, was Sie eigentlich tun müssen. Dennoch sollten Sie wirklich darüber nachdenken, was Controller braucht und nicht was OilAPI tut.

Abschließend möchte ich darauf hinweisen, dass ich den Bedenken Ihrer Kollegen bezüglich Ihres Designs generell zustimme. Betrachte dieses Codefragment:

OilAPI oil1 = new OilAPI(); 
OilAPI oil2 = new OilAPI(); 
FuelFacility fuel1 = new FuelFacility(oil1); 
FuelFacility fuel2 = new FuelFacility(oil2); 
Engine engine = fuel1.getEngineForId("engineId"); 
Pump pump = fuel2.getPumpForId("pumpId"); 
engine.connectToPump(pump); 

Was passiert? Dann wird oil1 verwendet, um eine Pump zu verbinden, die über oil2 abgerufen wurde. Je nach den Interna von OilAPI wäre das wahrscheinlich ein Problem.

+0

Danke für die Antwort! "In diesem Design sind FuelFacility, Pump und Engine so nah an OilAPI, Geyser und Burner, dass ich nicht sicher bin, ob Sie Controller tatsächlich so gut schützen." Ja, das ist nur in diesem Beispiel, in der Praxis sind sie ein bisschen anders. Aber die Art von Dingen, für die ich mich einsetzte, war zum Teil eine Anti-Korruptionsschicht: http://www.markhneedham.com/blog/2009/07/07/domain-driven-design-anti-corruption- layer/ https://softwareengineering.stackexchange.com/questions/184464/what-is-an-anti-corruption-layer-and-how-is-it-used –

Verwandte Themen