Java:内部类访问彼此的私有变量-封装外部API的良好实践?

Java: Inner classes accessing each other's private variables - good practice for encapsulating external API?

这是一个设计问题,涉及 Java 中的内部 classes (Java 8)。所有示例代码都在我的文字下方

举个例子,假设我有一些机器,涉及将燃料从喷油器泵送到某种燃烧器,我可以使用一个名为 OilAPI 的外部 API 来控制它。

我有一个控制器 class 负责工作并决定哪个燃烧器需要从哪个间歇泉获取油,但我不想要使用 API 的 class 像 Geyser 和 Burner 一样泄漏到控制器中(也是因为 API 随着时间的推移仍然会发生一些变化)。

现在,为了封装它,我创建了一个名为 FuelFacility 的 class,其中包含 OilAPI 的所有逻辑。

问题是,我将 classes 泵和引擎作为内部 classes 放在 FuelFacility 中。

首先,这是为了能够使用 Pump.activate() 而不是 FuelFacility.activatePump(...) 或其他任何语法。

此外,为了在油中连接 Geyser 和 Burner API,您需要 Geyser 和 Burner 对象,但我不想将它们暴露在外部,所以为了拥有某种 "Connect Pump and Engine" 方法,我必须允许 Pump 访问 Engine 的 Burner 变量,允许 Engine 访问 Pump 的 Geyser +变量,或者允许 FuelFacility 访问这两个变量。在下面的示例中,我有一个 Engine.connectToPump(pump) 方法,这基本上是它在我的实际代码中的工作方式。

我的队友觉得这有点奇怪;他们说跨 classes 访问私有变量会破坏封装,尤其是程序员从 "outside" 中查看代码(即,从在控制器中工作的角度来看 class) 会假设一旦您获得了 Engine 和 Pump 对象,它们将不再依赖于例如原始 FuelFacility 使用的 OilAPI 对象(尽管这应该保持最终状态,正如我在下面所做的那样),也不相互影响。

现在,从那时起,我已经设法稍微改变了他们的想法 - 这基本上只是一种他们不习惯的做事方式,但这并不是坏习惯。

但是,现在我正忙于修改其他一些代码以使其以与此类似的方式工作,我只想在继续之前确保我正在做的事情是好的做法吗?有没有更好的做事方式?不胜感激!

代码:

石油API(不受我控制):

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);
    }
}

燃料设施(class我创建来封装石油API):

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);
        }
    }
}

Controller(归我所有,位于我们的代码库中):

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);
    }
}

内部 classes 访问彼此的私有字段本身并不一定是坏事。您的主要目标似乎是保护 Controller 免受更改为 OilAPI。在此设计中,FuelFacilityPumpEngine 非常接近 OilAPIGeyserBurner,我不确定你实际上保护了 Controller 这么多。 FuelFacility 的设计应该更多地满足 Controller 的需求,而不是 OilAPI 的需求。在您的示例中,您不会在 PumpEngine 上调用 activate,但我假设您最终想要这样做。首先,我将从声明一些接口开始:

public interface PumpEngineConnection {
  public void activate();
}

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

Controller 通过这些接口工作,并不知道它实际使用的是什么实现。然后你可以OilAPIFuelFacility实现FuelFacility。 returns 的 PumpEngineConnection 实现将是专门设计用于 OilAPIFuelFacility 的实现。您可以使用内部 class:

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);
     }
  }
}

每个 GeyserBurnerConnection 隐含地获取对创建它的 OilAPIFuelFacility 的引用。这是合理的,因为只有将 PumpEngineConnection 与创建它的 FuelFacility 一起使用才有意义。同样,GeyserBurnerConnectionOilAPIFuelFacility.

引用 oil 成员是完全合理的

也就是说,GeyserBurnerConnection 在与 OilAPIFuelFacility 相同的包中成为包私有 class 可能更有意义。可能是不同版本的OilAPI可以使用相同的GeyserBurnerConnectionclass.

最后,Controller 看起来像这样:

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();
  }
}

请注意,它完全不知道它实际使用的 FuelFacilityPumpEngineConnection 的实现。实际上,我们会通过依赖注入框架或外部 Main class.

传入 OilAPIFuelFacility

我意识到您的示例可能已从您实际需要做的事情中进行了简化。尽管如此,你真的应该考虑 Controller 需要什么而不是 OilAPI 做什么。

最后,我应该指出,我大体上同意你的同事对你的设计的担忧。考虑这个片段:

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);

会发生什么?然后 oil1 用于连接通过 oil2 检索到的 Pump。取决于 OilAPI 的内部结构,这可能是个问题。