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
。在此设计中,FuelFacility
、Pump
和 Engine
非常接近 OilAPI
、Geyser
和 Burner
,我不确定你实际上保护了 Controller
这么多。 FuelFacility
的设计应该更多地满足 Controller
的需求,而不是 OilAPI
的需求。在您的示例中,您不会在 Pump
或 Engine
上调用 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
一起使用才有意义。同样,GeyserBurnerConnection
从 OilAPIFuelFacility
.
引用 oil
成员是完全合理的
也就是说,GeyserBurnerConnection
在与 OilAPIFuelFacility
相同的包中成为包私有 class 可能更有意义。可能是不同版本的OilAPI
可以使用相同的GeyserBurnerConnection
class.
最后,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();
}
}
请注意,它完全不知道它实际使用的 FuelFacility
和 PumpEngineConnection
的实现。实际上,我们会通过依赖注入框架或外部 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
的内部结构,这可能是个问题。
这是一个设计问题,涉及 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
。在此设计中,FuelFacility
、Pump
和 Engine
非常接近 OilAPI
、Geyser
和 Burner
,我不确定你实际上保护了 Controller
这么多。 FuelFacility
的设计应该更多地满足 Controller
的需求,而不是 OilAPI
的需求。在您的示例中,您不会在 Pump
或 Engine
上调用 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
一起使用才有意义。同样,GeyserBurnerConnection
从 OilAPIFuelFacility
.
oil
成员是完全合理的
也就是说,GeyserBurnerConnection
在与 OilAPIFuelFacility
相同的包中成为包私有 class 可能更有意义。可能是不同版本的OilAPI
可以使用相同的GeyserBurnerConnection
class.
最后,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();
}
}
请注意,它完全不知道它实际使用的 FuelFacility
和 PumpEngineConnection
的实现。实际上,我们会通过依赖注入框架或外部 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
的内部结构,这可能是个问题。