面向对象视角下遍历私有数据成员的代码放置位置抉择
这确实是个很典型的面向对象设计权衡问题——既要完成业务逻辑,又要守住封装、低耦合的原则,咱们一步步拆解来看:
先排除最不合适的选项:选项3
把代码放在DataTypeA里是最不推荐的,因为这会让DataTypeA被迫依赖它原本完全不需要感知的DataTypeC,直接违反了单一职责原则。DataTypeA的核心职责是存储键值对,硬塞进去和structureC相关的逻辑,会让类的职责混乱,后续维护也会变得困难,比如以后DataTypeC的结构变了,DataTypeA也要跟着改,完全没必要。
优化选项2:让Data类做协调者,同时隐藏实现细节
选项2的核心思路是对的——Data类作为同一主题数据的容器,天然适合协调structureA和structureC的操作,但我们可以通过抽象接口来避免暴露它们的实现细节:
步骤1:给DataTypeA和DataTypeC定义抽象接口
不要让Data类直接依赖具体的ConcurrentHashMap子类或者DataTypeC,而是依赖抽象行为:
// 定义DataTypeA的抽象行为:提供键值对遍历能力 public interface KeyStringStore { void forEachEntry(BiConsumer<Integer, String> action); } // 让DataTypeA实现这个接口,内部用ConcurrentHashMap的能力来实现 public class DataTypeA extends ConcurrentHashMap<Integer, String> implements KeyStringStore { @Override public void forEachEntry(BiConsumer<Integer, String> action) { this.forEach(action); } } // 定义DataTypeC的抽象行为:根据key提供关联值 public interface RelatedValueProvider { // 把Object换成你实际需要的类型 Object getRelatedValue(Integer key); } public class DataTypeC implements RelatedValueProvider { // 内部的具体实现 @Override public Object getRelatedValue(Integer key) { // 根据key返回对应的关联值逻辑 } }
步骤2:在Data类中基于抽象接口实现协调逻辑
现在Data类只依赖抽象接口,完全不知道structureA是ConcurrentHashMap、structureC的内部结构是什么:
public class Data { private KeyStringStore structureA; private DataTypeB structureB; private RelatedValueProvider structureC; // 通过构造器注入依赖,保证灵活性 public Data(KeyStringStore structureA, DataTypeB structureB, RelatedValueProvider structureC) { this.structureA = structureA; this.structureB = structureB; this.structureC = structureC; } // 对外提供统一的处理方法 public void processStructureAWithRelatedValues() { structureA.forEachEntry((key, valueA) -> { Object valueC = structureC.getRelatedValue(key); // 执行具体的业务操作 performBusinessOperation(key, valueA, valueC); }); } // 把具体业务逻辑封装成私有方法,保持代码清晰 private void performBusinessOperation(Integer key, String valueA, Object valueC) { // 这里写你要执行的操作逻辑 } }
如果后续业务逻辑需要灵活变化,还可以把处理逻辑作为参数传入,让Data类更通用:
// 支持外部传入自定义处理逻辑 public void processStructureAWithRelatedValues(TriConsumer<Integer, String, Object> customProcessor) { structureA.forEachEntry((key, valueA) -> { Object valueC = structureC.getRelatedValue(key); customProcessor.accept(key, valueA, valueC); }); }
选项1的改进思路(如果必须放在外部类)
如果你确实需要把处理逻辑放在外部类,那可以让Data类提供抽象的数据组合能力,而不是直接暴露内部结构:
// 先定义一个简单的三元组类,用来封装关联数据 public record DataTriple<K, V1, V2>(K key, V1 valueA, V2 valueC) {} public class Data { // ... 其他代码 // 对外提供组合后的数据流,外部类只需要处理这个流 public Stream<DataTriple<Integer, String, Object>> streamRelatedEntries() { return ((DataTypeA)structureA).entrySet().stream() .map(entry -> new DataTriple<>( entry.getKey(), entry.getValue(), structureC.getRelatedValue(entry.getKey()) )); } }
然后外部类就可以这样使用:
public class ExternalProcessor { public void processData(Data data) { data.streamRelatedEntries().forEach(triple -> { // 执行操作逻辑 }); } }
不过这种方式的封装性不如选项2,因为Data类还是得暴露一点内部转换的逻辑,而且外部类虽然不知道structureA的实现,但还是需要处理组合后的数据,不如让Data类承担协调职责更内聚。
总结
最优方案是优化后的选项2:让Data类作为同一主题数据的协调者,通过抽象接口隐藏structureA和structureC的实现细节,既符合封装原则,又保持了类职责的清晰。选项3直接排除,选项1可以作为特殊场景下的备选,但不是最优解。
内容的提问来源于stack exchange,提问作者Victoriia

