当前位置: 首页 > 知识库问答 >
问题:

Java:内部类访问对方的私有变量-封装外部API的好做法?

闻人河
2023-03-14

这有点涉及Java(Java 8)内类的设计问题。所有示例代码都在我的文本下面

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

我有一个Controller类,它负责完成工作,并决定哪个燃烧器需要从哪个间歇泉获取油,但我不希望使用API的类(如间歇泉和燃烧器)的逻辑泄漏到控制器中(也是因为API仍然会随着时间的推移发生一些变化)。

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

问题是,我已经把类泵和发动机作为燃料设施的内部类。

首先,这是为了能够go pump.activate()而不是fuelFacility.activatePump(...)的语法或者别的什么。

进一步的是,为了连接油API中的间歇泉和燃烧器,您需要间歇泉和燃烧器对象,但我不想将它们暴露在外部,所以为了有某种“连接泵和发动机”的方法,我必须允许泵访问发动机的燃烧器变量,发动机访问泵的间歇泉+变量,或者FuelFacility访问这两个变量。在下面的示例中,我有一个Engine.ConnectTopUMP(pump)方法,这基本上是它在我的实际代码中的工作方式。

队友们觉得这有点奇怪;他们说,通过类访问私有变量打破了封装,特别是从“外部”(即从在Controller类中工作的角度)查看代码的程序员会假设,一旦您获得了Engine和Pump对象,它们就不再依赖于例如原始FuelFacility正在使用的OilAPI对象(尽管这应该是最终的,正如我在下面所做的那样),也不再依赖于彼此。

现在,从那以后,我设法改变了他们的想法--基本上,这只是一种他们不习惯的做事方式,但这并不是一种糟糕的练习。

然而,现在我正忙于修改一些其他代码,以与此类似的方式工作,我只想在继续之前确保我正在做的是良好的实践吗?有没有更好的处事方法?谢谢你的建议!

代码:

油类空气污染指数(不受本人控制):

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

燃料设施(为封装石油API而创建的I类):

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

控制器(由我拥有,位于我们的代码库中):

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

共有1个答案

孟思远
2023-03-14

让内部类访问彼此的私人领域本身并不是必要的坏事。您的主要目标似乎是保护Controller免受对OilAPI的更改。在此设计中,fuelfacilitypumpengineoilapigeyserburner非常接近,所以我不确定您是否真正保护了controllerfuelfacility应该更多地针对controller的需要而不是oilapi的功能进行设计。在您的示例中,您没有在pumpengine上调用activate,但我假设您最终希望这样做。首先,我要声明一些接口:

public interface PumpEngineConnection {
  public void activate();
}

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

controller通过这些接口工作,并且不知道它实际使用了什么实现。然后您可以对fuelfacility进行oilapifuelfacility实现。它返回的PumpEngineConnection实现将是一个专门设计用于使用OilapiFuelFacility的实现。您可以使用内部类来完成此操作:

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位于同一包中的包私有类可能更有意义。可能是OilAPI的不同版本可以使用相同的GeyserBurnerConnection类。

最后,控制器可能如下所示:

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类传入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检索的。根据OilAPI的内部结构,这可能是一个问题。

 类似资料:
  • 为什么这段代码不起作用 在这段代码工作的时候? 在第一段代码中,当我试图通过内部类“a”的对象引用内部类“a”的实例变量“x”时,我得到一个错误,说我是在静态上下文中使用内部类。在其他方法中执行相同操作时没有错误。

  • 问题内容: 在内部类中,可以访问外部类的变量,但不能访问方法的局部变量。我了解了无法访问方法的局部变量的部分,但我想知道为什么外部类变量可以访问? 我的理解是,由于内部类与外部类绑定,因此只要父级可用,子级就可以访问其父级变量。我对么? 问题答案: 假设您的外部类在内部类的范围内(非静态)被称为,以获取该字段。 例如, 其中Outer是类的名称,并标识该字段。 您也可以直接抓取它,但是如果由于阴影

  • 在内部类中,外部类的变量是可访问的,但方法的局部变量不是。我理解了关于方法的局部变量不可访问的部分,但我想知道为什么外部类变量是可访问的? 我的理解是,由于内部类与外部类绑定,因此只要父类可用,子类就可以访问其父变量。我说得对吗?

  • 问题内容: 我确实阅读了许多讨论内部类的主题,并且给人的印象是内部类可以访问封闭类的变量和方法。在下面的代码中,我有一个外部类和一​​个内部类,在测试类中,我创建了一个外部类的实例,然后从中创建了一个内部类的实例。但是我无法通过内部类引用访问String变量。救命? 问题答案: 内部类可以通过自己的方法访问外部类的方法和属性。看下面的代码:

  • 问题内容: 嗨,我正在浏览有关内部类的SCJP书,发现了这一说法,类似这样。 方法本地类只能引用已标记的本地变量 在解释中,指定的原因与本地类对象和堆上的局部变量的范围和生存期有关,但我无法理解。我在这里想念任何东西吗? 问题答案: 原因是,在创建方法本地类实例时,编译器实际上会将其引用的所有方法本地变量复制到其中。这就是为什么只能访问变量的原因。甲变量或参考是不变的,所以它停留在同步与其方法本地

  • 问题内容: 如果我有一个包私有的Java类(用“ class”声明,而不是“ public class”),那么将内部方法声明为public或protected或package- private确实没有区别,对吗?那么我应该使用哪个,或者什么时候该使用呢?我有点困惑。 问题答案: 如果我有一个包私有的Java类(用“ class”声明,而不是“ public class”),那么将内部方法声明为p