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

时间:2017-07-26 10:00:54

标签: java api private inner-classes encapsulation

这是一个涉及Java内部类(Java 8)的设计问题。所有示例代码都在我的文本

之下

作为一个例子,让我们说我有一些机器需要将燃油从油喷泉泵送到某种燃烧器,我可以使用名为OilAPI的外部API控制燃烧器。

我有一个控制器类正在完成工作并决定哪个燃烧器需要从哪个间歇泉获取油,但我不想要使用像Geyser和Burner这样的API类的逻辑泄漏进入控制器(因为API仍会随着时间的推移而经历一些变化)。

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

问题是,我已将Pump和Engine类作为内部类放在FuelFacility中。

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

此外,为了在Oil API中连接间歇泉和燃烧器,你需要Geyser和Burner对象,但我不想在外部暴露它们,所以为了得到某种& #34;连接泵和发动机"方法,我必须允许Pump访问Engine的燃烧器变量,使用Engine来访问Pump的Geyser +变量,或者让FuelFacility访问这两个变量。在下面的例子中,我有一个Engine.connectToPump(pump)方法,它基本上就是它在我的实际代码中的工作方式。

我的队友认为这有点奇怪;他们说,跨类访问私有变量会破坏封装,尤其是程序员从"外部" (即,从在Controller类中工作的角度来看)假设一旦你获得了Engine和Pump对象,他们将不再依赖于例如哪个OilAPI对象是最初的FuelFacility所使用的(虽然这应该是最终的,正如我在下面所做的那样),也不是彼此。

现在,从那时起,我已经设法改变了一些想法 - 基本上这只是一种他们不习惯的事情,但它并不坏实践。

然而,现在我正在忙着改变其他一些代码,以类似的方式工作,我只是想确保在继续之前,我正在做什么好的做法?有更好的做事方式吗?建议表示赞赏!

CODE:

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

燃料设施(我创建的类用于封装Oil 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);
        }
    }
}

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

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 个答案:

答案 0 :(得分:2)

让内部课程互相访问'私人领域本身并不是必要的。您的主要目标似乎是保护Controller免受OilAPI的更改。在此设计中,FuelFacilityPumpEngine非常靠近我OilAPIGeyserBurner不确定你真的保护Controller那么多。 FuelFacility应该针对Controller所需要的内容而不是OilAPI进行更多设计。在您的示例中,您不能在activatePump上致电Engine,但我假设您最终想要这样做。首先,我首先声明一些接口:

public interface PumpEngineConnection {
  public void activate();
}

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

Controller通过这些接口工作,不知道它实际使用了哪些实现。然后,您可以OilAPIFuelFacility实施FuelFacility。它返回的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引用oil中的OilAPIFuelFacility成员也是完全合理的。

也就是说,GeyserBurnerConnectionOilAPIFuelFacility在同一个包中的包私有类更有意义。可能是OilAPI的不同版本可能使用相同的GeyserBurnerConnection类。

最后,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的实现方式。在实践中,我们使用依赖注入框架或外部OilAPIFuelFacility类传递Main

我意识到您的示例可能会从您实际需要的内容中简化。尽管如此,您应该考虑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用于连接通过Pump检索到的oil2。取决于可能存在问题的OilAPI的内部结构。